File Coverage

blib/lib/Bib/Tools.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             ############################################################
2             #
3             # Bib::Tools - For managing collections of Bib::CrossRef references.
4             #
5             ############################################################
6              
7             package Bib::Tools;
8              
9 1     1   17692 use 5.8.8;
  1         4  
  1         35  
10 1     1   3 use strict;
  1         2  
  1         27  
11 1     1   2 use warnings;
  1         2  
  1         30  
12 1     1   3 no warnings 'uninitialized';
  1         1  
  1         32  
13              
14             require Exporter;
15 1     1   489 use Bib::CrossRef;
  1         69759  
  1         53  
16 1     1   9 use LWP::UserAgent;
  1         1  
  1         18  
17 1     1   3 use JSON qw/decode_json/;
  1         1  
  1         6  
18 1     1   164 use URI::Escape qw(uri_escape_utf8 uri_unescape);
  1         1  
  1         69  
19 1     1   5 use HTML::Entities qw(decode_entities encode_entities);
  1         1  
  1         39  
20 1     1   651 use HTML::TreeBuilder::XPath;
  1         71893  
  1         15  
21 1     1   370 use XML::Simple qw(XMLin);
  0            
  0            
22             use BibTeX::Parser qw(new next);
23             use IO::File;
24             use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS @ISA);
25              
26             #use LWP::Protocol::https;
27             #use Data::Dumper;
28              
29             $VERSION = '0.14';
30             @ISA = qw(Exporter);
31             @EXPORT = qw();
32             @EXPORT_OK = qw(
33             sethtml clearhtml add_details add_google add_google_search add_orcid add_fromfile add_dblp add_pubmed
34             send_resp print print_nodoi num num_nodoi getref getref_nodoi append
35             );
36             %EXPORT_TAGS = (all => \@EXPORT_OK);
37              
38             sub new {
39             my $self;
40             # defaults
41             $self->{refs} = []; # the references
42             $self->{nodoi_refs} = [];
43             $self->{duprefs} = [];
44             $self->{html}=0;
45             $self->{ratelimit}=5; # limit of 5 crossref queries per sec
46             $self->{last} = {};
47             bless $self;
48            
49             my $ratelimit = $_[1];
50             if (defined($ratelimit) && ($ratelimit>=0)) {$self->{ratelimit}=$ratelimit};
51             return $self;
52             }
53              
54             sub sethtml {
55             my $self = shift @_;
56             $self->{html}=1;
57             }
58              
59             sub clearhtml {
60             my $self = shift @_;
61             $self->{html}=0;
62             }
63              
64             sub _err {
65             my ($self, $str) = @_;
66             if ($self->{html}) {
67             print "

",$str,"

";
68             } else {
69             print $str,"\n";
70             }
71             }
72              
73             sub _split_duplicates {
74             # split list of references into three lists: one with refs that have unique doi's, one with no doi's
75             #and one with all the rest (with duplicate doi's)
76             my $self = shift @_;
77             my @refs=@{$self->{refs}};
78            
79             my @newrefs;
80             foreach my $ref (@refs) {
81             my $doi = $ref->doi();
82             if (!defined($doi) || length($doi)==0) {push @{$self->{nodoi_refs}}, $ref; next; }# skip entries with no DOI
83             my $found = 0;
84             foreach my $ref2 (@newrefs) {
85             if ($ref2->doi() eq $doi) {
86             $found = 1;
87             }
88             }
89             if (!$found) {
90             push @newrefs, $ref;
91             } else {
92             push @{$self->{duprefs}}, $ref;
93             }
94             }
95             $self->{refs} = \@newrefs;
96             }
97              
98             sub append {
99             # add new reference to end of existing list
100             my $self = shift @_;
101             my $ref = shift @_;
102             push @{$self->{refs}}, $ref;
103             }
104              
105             sub add_details {
106             # given an array of raw strings, try to convert into paper references
107            
108             my $self = shift @_;
109             foreach my $cites (@_) {
110             $self->{last} = Bib::CrossRef->new();
111             $self->{last}->parse_text($cites);
112             $self->append($self->{last});
113             sleep 1/(0.001+$self->{ratelimit}); # rate limit queries to crossref
114             }
115             $self->_split_duplicates();
116             }
117              
118             sub add_google {
119             # scrape paper details from google scholar personal page -- nb: no doi info on google, so use crossref.org to obtain this
120             # nb: doesn't work with google scholar search results
121            
122             my $self = shift @_;
123             my $url = shift @_;
124             my $ua = LWP::UserAgent->new;
125             $ua->agent('Mozilla/5.0');
126             my $req = HTTP::Request->new(GET => $url);
127             my $res = $ua->request($req);
128             if ($res->is_success) {
129             my $tree= HTML::TreeBuilder::XPath->new;
130             $tree->parse($res->decoded_content);
131             my @atitles=$tree->findvalues('//tr[@class="gsc_a_tr"]/td/a[@class="gsc_a_at"]');
132             my @authors=$tree->findvalues('//tr[@class="gsc_a_tr"]/td/div[@class="gs_gray"][1]');
133             my @jtitles=$tree->findvalues('//tr[@class="gsc_a_tr"]/td/div[@class="gs_gray"][2]');
134             my $len1 = @atitles; my $len2 = @authors; my $len3 = @jtitles;
135             if (($len1 != $len2) || ($len1 != $len3) || ($len2 != $len3)) {$self->_err("Problem parsing google page: mismatched $len1 titles/$len2 authors/$len3 journals.");return []}
136             for (my $i = 0; $i<$len1; $i++) {
137             # these are already utf8
138             $authors[$i] = decode_entities($authors[$i]);
139             $atitles[$i] = decode_entities($atitles[$i]);
140             $jtitles[$i] = decode_entities($jtitles[$i]);
141             my $temp = $authors[$i].", ".$atitles[$i].", ".$jtitles[$i];
142             my $r = Bib::CrossRef->new;
143             $r->parse_text($temp);
144             $jtitles[$i] =~ m/\s([0-9][0-9][0-9][0-9])$/;
145             my $year=$1;
146             if ((length($year)==4) && ($r->date ne $1)) {
147             $r->_setscore(0.5); # mismatch in year, probably bad
148             }
149             $self->append($r);
150             }
151             } else {
152             $self->_err("Problem with $url: ".$res->status_line);
153             }
154             }
155              
156             sub add_google_search {
157             # scrape paper details from google scholar search results -- *not* from persons scholar home page
158              
159             my $self = shift @_;
160             my $url = shift @_;
161             my $ua = LWP::UserAgent->new;
162             $ua->agent('Mozilla/5.0');
163             my $req = HTTP::Request->new(GET => $url);
164             my $res = $ua->request($req);
165             if ($res->is_success) {
166             my $tree= HTML::TreeBuilder::XPath->new;
167             $tree->parse($res->decoded_content);
168             my @atitles=$tree->findvalues('//div[@class="gs_ri"]/h3/a');
169             my @authors=$tree->findvalues('//div[@class="gs_a"]');
170             my $len1 = @atitles; my $len2 = @authors;
171             if ($len1 != $len2) {$self->_err("Problem parsing google page: mismatched $len1 titles/$len2 authors.");return [];}
172             my @cites=();
173             for (my $i = 0; $i<$len1; $i++) {
174             $authors[$i] = decode_entities($authors[$i]);
175             $atitles[$i] = decode_entities($atitles[$i]);
176             my $str = $authors[$i].", ".$atitles[$i];
177             if (length($str)>5) { # a potentially useful entry ?
178             push @cites, $authors[$i].", ".$atitles[$i];
179             }
180             }
181             $self->add_details(@cites);
182             } else {
183             $self->_err("Problem with $url: ".$res->status_line);
184             }
185             }
186              
187             sub add_dblp {
188             # get details using DBLP XML API
189            
190             my $self = shift @_;
191             my $url = shift @_;
192             my $maxnum = shift @_; if (!defined($maxnum)) {$maxnum=-1;}
193            
194             my $ua = LWP::UserAgent->new;
195             $ua->agent('Mozilla/5.0');
196             my $req = HTTP::Request->new(GET => $url);
197             my $res = $ua->request($req);
198             if ($res->is_success) {
199             my $xs = XML::Simple->new();
200             my $data = $xs->XMLin($res->decoded_content);
201             my @cites; my @ctemp;
202             if (defined $data->{'r'}) {
203             # a person page
204             @cites = $data->{'r'};
205             } elsif (defined $data->{'article'}) {
206             # its xml for a single article
207             $ctemp[0] = $data;
208             push @cites, \@ctemp;
209             }
210             my $num=0;
211             foreach my $c (@{$cites[0]}) {
212             $num++; if ($maxnum>0 && $num>$maxnum) {last;} # mainly for testing
213             my $cite;
214             my @k = keys %{$c};
215             $cite = $c->{$k[0]};
216             my $ee = $cite->{'ee'};
217             if ($ee =~ m/dx.doi.org/) {
218             # we have a DOI, lets call crossref
219             $ee =~ s/http:\/\/dx.doi.org\///;
220             $self->add_details($ee);
221             next; # move on to next record
222             }
223             my $temp = $cite->{'year'};
224             $temp .= ' '.$cite->{'title'};
225             if (defined $cite->{'journal'}) {
226             $temp .= ' '.$cite->{'journal'};
227             } elsif (defined $cite->{'booktitle'}) {
228             $temp .= ' '.$cite->{'booktitle'};
229             }
230             my $auth='';
231             if (ref($cite->{'author'}) eq "HASH") {
232             $auth = $cite->{'author'};
233             } else {
234             foreach my $au (@{$cite->{'author'}}) { $auth .= $au.", ";}
235             }
236             my $r = Bib::CrossRef->new;
237             $r->parse_text($auth.' '.$temp);
238             if ($r->score >= 1) {
239             # found an ok match, lets use it
240             $self->append($r);
241             next; # move on
242             }
243             #next; # stop here for now
244            
245             # we got a poor match, lets use the rest of the dblp data
246             $r = Bib::CrossRef->new;
247             if (exists $cite->{'publtype'}) {
248             $r->_setgenre($cite->{'publtype'});
249             } elsif ($k[0] =~ m/article/) {
250             $r->_setgenre('article');
251             } elsif ($k[0] =~ m/inproceedings/) {
252             $r->_setgenre('proceeding');
253             } elsif ($k[0] =~ m/informal/) {
254             $r->_setgenre('preprint');
255             } else {
256             $r->_setgenre($k[0]);
257             }
258             $r->_setdate($cite->{'year'});
259             $r->_setatitle($cite->{'title'});
260             if (defined $cite->{'journal'}) {
261             $r->_setjtitle($cite->{'journal'});
262             } elsif (defined $cite->{'booktitle'}) {
263             $r->_setjtitle($cite->{'booktitle'});
264             }
265             $auth='';
266             if (ref($cite->{'author'}) eq "HASH") {
267             $r->_setauthcount(1);
268             $r->_setauth(1,$cite->{'author'});
269             $auth = $cite->{'author'};
270             } else {
271             my $count = 0;
272             foreach my $au (@{$cite->{'author'}}) {
273             $count++;
274             $r->_setauth($count, $au);
275             $auth .= $au.", ";
276             }
277             $r->_setauthcount($count);
278             }
279             if (defined $cite->{'volume'}) {$r->_setvolume($cite->{'volume'});}
280             if (defined $cite->{'number'}) {$r->_setissue($cite->{'number'});}
281             if (defined $cite->{'pages'}) {
282             my @bits = split('-',$cite->{'pages'});
283             if (defined $bits[0]) {$r->_setspage($bits[0]);}
284             if (defined $bits[1]) {$r->_setepage($bits[1]);}
285             }
286             if (($cite->{'ee'} =~ m/^http:\/\//)) {
287             $r->_seturl($cite->{'ee'});
288             }
289             $r->_setscore(1);
290             $r->_setquery($auth." ".$temp);
291             # add manually constructed record
292             $self->append($r);
293             }
294             $self->_split_duplicates();
295             } else {
296             $self->_err("Problem with $url: ".$res->status_line);
297             }
298             }
299              
300             sub _get_orcid_doi {
301             my $cite = shift @_;
302            
303             my $doi='';
304             if (ref($cite->{'work-external-identifiers'}->{'work-external-identifier'}) eq "HASH") {
305             # a single value
306             my $id = $cite->{'work-external-identifiers'}->{'work-external-identifier'};
307             if ($id->{'work-external-identifier-type'} =~ m/doi/) {
308             # and its a DOI
309             $doi = $id->{'work-external-identifier-id'};
310             } else {
311             #print("Note: no DOI in ORCID for:".$cite->{'work-title'}->{'title'}.", only ".$id->{'work-external-identifier-type'}."=".$id->{'work-external-identifier-id'}."\n");
312             }
313             } else {
314             # multiple values
315             my $found = 0; my $types;
316             foreach my $id (@{$cite->{'work-external-identifiers'}->{'work-external-identifier'}}) {
317             if ($id->{'work-external-identifier-type'} =~ m/doi/) {
318             # its a DOI
319             $doi = $id->{'work-external-identifier-id'};
320             $found=1;
321             last; # exit loop
322             }
323             $types = $types.$id->{'work-external-identifier-type'}.'='.$id->{'work-external-identifier-id'}." ";
324             }
325             if (!$found) {
326             #print("Note: no DOI in ORCID for:".$cite->{'work-title'}->{'title'}.", only $types \n");
327             }
328             }
329             return $doi;
330             }
331              
332             sub _get_orcid_auth {
333             my $cite = shift @_;
334              
335             my $auth='';
336             if (ref($cite->{'work-contributors'}->{'contributor'}) eq "HASH") {
337             # single author
338             my $au = $cite->{'work-contributors'}->{'contributor'};
339             if ($au->{'contributor-attributes'}->{'contributor-role'} =~ m/author/) {$auth = $au->{'credit-name'}->{'content'};}
340             } else {
341             # multiple authors
342             foreach my $au (@{$cite->{'work-contributors'}->{'contributor'}}) {
343             if ($au->{'contributor-attributes'}->{'contributor-role'} =~ m/author/) {$auth .= $au->{'credit-name'}->{'content'}.", ";}
344             }
345             }
346             return $auth;
347             }
348              
349             sub add_orcid {
350             # get paper details from orcid using API
351            
352             my $self = shift @_;
353             my $orcid_id = shift @_;
354             my $ua = LWP::UserAgent->new;
355             my $req = HTTP::Request->new(GET => "http://pub.orcid.org/$orcid_id/orcid-works/");
356             my $res = $ua->request($req);
357             if ($res->is_success) {
358             my $xs = XML::Simple->new();
359             # the orcid response is utf8 xml
360             my $data = $xs->XMLin($res->decoded_content);
361             my @cites = $data->{'orcid-profile'}->{'orcid-activities'}->{'orcid-works'}->{'orcid-work'};
362             foreach my $cite (@{$cites[0]}) {
363             my $ref={};
364             my $doi = _get_orcid_doi($cite);
365             if (defined $doi) {
366             # use DOI to search.crossref.org
367             my $r = Bib::CrossRef->new;
368             $r->parse_text($doi);
369             $self->append($r);
370             next; # move on
371             }
372             # use title etc to search.crossref.org
373             my $temp='';
374             if (exists $cite->{'publication-date'}->{'year'}) {$temp = $cite->{'publication-date'}->{'year'};}
375             if (exists $cite->{'work-title'}->{'title'}) {$temp .= ' '.$cite->{'work-title'}->{'title'};}
376             if (exists $cite->{'journal-title'}) {$temp = ' '.$cite->{'journal-title'};}
377             my $auth = _get_orcid_auth($cite);
378             my $r = Bib::CrossRef->new;
379             $r->parse_text($auth.' '.$temp);
380             if ($r->score >= 1) {
381             # found an ok match, lets use it
382             $self->append($r);
383             next; # move on
384             }
385            
386             #next; # stop here for now
387              
388             # for a poor match, try to extract rest of info from orcid
389             $r = Bib::CrossRef->new;
390            
391             if (exists $cite->{'work-title'}->{'title'}) {$r->_setatitle($cite->{'work-title'}->{'title'});}
392             if (exists $cite->{'journal-title'}) {$r->_setstitle($cite->{'journal-title'});}
393             if (exists $cite->{'publication-date'}->{'year'}) {$r->_setdate($cite->{'publication-date'}->{'year'});}
394             if (exists $cite->{'work-type'}) {$r->_setgenre($cite->{'work-type'});}
395            
396             my $authcount=0; $auth='';
397             if (ref($cite->{'work-contributors'}->{'contributor'}) eq "HASH") {
398             # single author
399             my $au = $cite->{'work-contributors'}->{'contributor'};
400             if ($au->{'contributor-attributes'}->{'contributor-role'} =~ m/author/) {
401             $authcount++;
402             $r->_setauth($authcount,$au->{'credit-name'}->{'content'});
403             $auth = $au->{'credit-name'}->{'content'};
404             }
405             } else {
406             # multiple authors
407             foreach my $au (@{$cite->{'work-contributors'}->{'contributor'}}) {
408             if ($au->{'contributor-attributes'}->{'contributor-role'} =~ m/author/) {
409             $authcount++;
410             $r->_setauth($authcount,$au->{'credit-name'}->{'content'});
411             $auth .= $au->{'credit-name'}->{'content'}.", ";
412             }
413             }
414             }
415             $r->_setauthcount($authcount);
416            
417             if ($cite->{'work-citation'}->{'work-citation-type'} =~ m/bibtex/) {
418             # we have a bibtex reference, extract some extra info
419             # -- seems better to use crossref as this content can be messy
420             my $bibtex = $cite->{'work-citation'}->{'citation'};
421             open my $fh, '<', \$bibtex;
422             my $parser = BibTeX::Parser->new($fh);
423             my $entry = $parser->next;
424             if ($entry->parse_ok) {
425             if (defined $entry->field('volume')) {$r->_setvolume($entry->field('volume'))};
426             if (defined $entry->field('issue')) {$r->_setissue($entry->field('issue'))};
427             if (defined $entry->field('pages')) {
428             my $pages = $entry->field('pages');
429             (my $s, my $e) = ($pages =~ /([0-9]+)-+([0-9]+)/ );
430             if (defined $s) { $r->_setspage($s); }
431             if (defined $e) { $r->_setepage($e); }
432             }
433             if (defined $entry->field('journal')) {
434             $r->_setjtitle($entry->field('journal'));
435             } elsif (defined $entry->field('booktitle')) {
436             $r->_setjtitle($entry->field('booktitle'));
437             }
438             my $jtitle = $r->jtitle;
439             if (defined $jtitle ) {
440             # tidy up
441             $jtitle =~ s/[\{\}]//g;
442             $jtitle =~ s/\\textquotesingle/\\'/g;
443             $r->_setjtitle($jtitle);
444             }
445             }
446             }
447             $r->_setscore(1);
448             $r->_setquery($auth." ".$temp);
449             # add manually constructed record
450             $self->append($r);
451             }
452             $self->_split_duplicates();
453             } else {
454             $self->_err("Problem with orcid.org: ".$res->status_line);
455             }
456             }
457              
458             sub _find_pubmed {
459             my $c = shift @_;
460             my $name = shift @_;
461             my $term = shift @_;
462             foreach my $item (@{$c}) {
463             if ($item->{'Name'} eq $name) {
464             return $item->{$term};
465             }
466             }
467             return undef;
468             }
469              
470             sub add_pubmed {
471             # add results from a pubmed query
472             my ($self,$q) = @_;
473              
474             my $ua = LWP::UserAgent->new;
475             $q =~ s/\s+/+/g;
476             my $req = HTTP::Request->new(GET => "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?usehistory=y&db=pubmed&term=".$q);
477             my $res = $ua->request($req);
478             if ($res->is_success) {
479             my $web = $1 if ($res->decoded_content =~ /(\S+)<\/WebEnv>/);
480             my $key = $1 if ($res->decoded_content =~ /(\d+)<\/QueryKey>/);
481             $req = HTTP::Request->new(GET => "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&query_key=$key&WebEnv=$web");
482             $res = $ua->request($req);
483             if ($res->is_success) {
484             my $xs = XML::Simple->new();
485             my $data = $xs->XMLin($res->decoded_content);
486             my @cites = $data->{'DocSum'};
487             foreach my $cite (@{$cites[0]}) {
488             my $c = $cite->{'Item'};
489             if (ref($c) ne "ARRAY") {next;}
490             my $r = Bib::CrossRef->new;
491             my $doi = _find_pubmed($c,'DOI','content');
492             if (defined $doi) {
493             # PubMed is reliable, no need to call crossref
494             # my $r = Bib::CrossRef->new;
495             # $r->parse_text($doi);
496             # $self->append($r);
497             # next; # move on
498             $r->_setdoi($doi);
499             $r->_seturl('http://dx.doi.org/'.$doi);
500             }
501             $r->_setjtitle(_find_pubmed($c,'FullJournalName','content'));
502             $r->_setatitle(_find_pubmed($c,'Title','content'));
503             my $date = _find_pubmed($c,'PubDate','content');
504             $date =~ m/^([0-9][0-9][0-9][0-9])/;
505             $r->_setdate($1); # extract the year
506             $r->_setvolume(_find_pubmed($c,'Volume','content'));
507             $r->_setissue(_find_pubmed($c,'Issue','content'));
508             my $p = _find_pubmed($c,'Pages','content');
509             my @bits = split('-',$p);
510             $r->_setspage($bits[0]); $r->_setepage($bits[1]);
511            
512             my $aulist = _find_pubmed($c,'AuthorList','Item');
513             my $authcount=0;
514             if (ref($aulist) ne "ARRAY") {
515             $authcount = 1;
516             $r->_setauth($authcount,$aulist->{'content'});
517             } else {
518             foreach my $au (@{$aulist}) {
519             $authcount++;
520             $r->_setauth($authcount,$au->{'content'});
521             }
522             }
523             $r->_setauthcount($authcount);
524             my $g = _find_pubmed($c,'FullJournalName','Item');
525             $r->_setgenre($g->{'content'});
526             $r->_setscore(1);
527             #$r->_setquery($auth." ".$temp);
528             # add manually constructed record
529             $self->append($r);
530             }
531             }
532             $self->_split_duplicates();
533             return;
534             }
535             $self->_err("Problem with http://eutils.ncbi.nlm.nih.gov: ".$res->status_line);
536             }
537              
538             sub add_fromfile {
539             # read free text references from a file, one reference per line
540             # takes file handle as input
541             my $self = shift @_;
542             my $fh = shift @_;
543             my @cites;
544             while (my $line=<$fh>) {
545             chomp($line);
546             if (length($line)<5) {next;} # skip non-informative lines
547             push @cites, $line;
548             }
549             $self->add_details(@cites);
550             }
551              
552             sub num {
553             # number of references with DOIs
554             my $self = shift @_;
555            
556             my $len = @{$self->{refs}};
557             return $len;
558             }
559              
560             sub num_nodoi {
561             # number of references without DOIs
562             my $self = shift @_;
563            
564             my $len = @{$self->{nodoi_refs}};
565             return $len;
566             }
567              
568             sub getref {
569             # get i'th reference with a DOI
570             my ($self, $i) = @_;
571             return ${$self->{refs}}[$i];
572             }
573              
574             sub getref_nodoi {
575             # get i'th reference without a DOI
576             my ($self, $i) = @_;
577            
578             return ${$self->{nodoi_refs}}[$i];
579             }
580              
581             sub print {
582             # display a list of references
583             my $self = shift @_;
584             my $id = shift @_;
585            
586             if ($self->num==0) {return ''};
587             my $out='';
588             if ($self->{html}) {$out.=$self->getref(0)->printheader($id);}
589             for (my $i=0; $i< $self->num; $i++) {
590             if ($self->{html}) {$self->getref($i)->sethtml;} else {$self->getref($i)->clearhtml;}
591             $out .= $self->getref($i)->print($i+1);
592             $out .= "\n";
593             }
594             if ($self->{html}) {$out.=$self->getref(0)->printfooter;}
595             return $out;
596             }
597              
598             sub print_nodoi {
599             # display a list of references
600             my $self = shift @_;
601             my $id = shift @_;
602            
603             if ($self->num_nodoi==0) {return ''};
604             my $out='';
605             if ($self->{html}) {$out.=$self->getref_nodoi(0)->printheader($id);}
606             for (my $i=0; $i< $self->num_nodoi; $i++) {
607             if ($self->{html}) {$self->getref_nodoi($i)->sethtml;} else {$self->getref_nodoi($i)->clearhtml;}
608             $out .= $self->getref_nodoi($i)->print($i+1);
609             }
610             if ($self->{html}) {$out.=$self->getref_nodoi(0)->printfooter;}
611             }
612              
613             sub send_resp {
614             # generate simple web page with results ...
615            
616             my $self = shift @_;
617              
618             if ($self->num==0 && $self->num_nodoi==0) {return ''};
619             my $html = $self->{html};
620             $self->sethtml; # force use of html
621             my $out='';
622             #$out.="Content-Type: text/html;\n\n"; # html header
623             $out.=sprintf "%s", '',"\n";
624             $out.=sprintf "%s", '';
625             $out.=sprintf "%s", '',"\n";
626             $out.=sprintf "%s", $self->print('doi');
627             if ($self->num_nodoi>0) {
628             $out.=sprintf "%s", '

These have no DOIs:

',"\n";
629             $out.=sprintf "%s", $self->print_nodoi('nodoi');
630             }
631             $out.=sprintf "%s", '
';
632             $out.=sprintf "%s", '';
633             $self->{html} = $html; # restore previous setting
634             return $out;
635             }
636              
637             1;
638              
639             =pod
640            
641             =head1 NAME
642            
643             Bib::Tools - For managing collections of Bib::CrossRef references.
644            
645             =head1 SYNOPSIS
646              
647             use strict;
648             use Bib::Tools;
649            
650             # Create a new object
651              
652             my $refs = Bib::Tools->new();
653            
654             # Add some bibliometric info e.g. as text, one reference per line
655              
656             $text=<<"END";
657             10.1109/lcomm.2011.040111.102111
658             10.1109/tnet.2010.2051038
659             END
660             open $fh, '<', \$text;
661             $refs->add_fromfile($fh);
662            
663             or
664            
665             $text=<<"END";
666             Dangerfield, I., Malone, D., Leith, D.J., 2011, Incentivising fairness and policing nodes in WiFi, IEEE Communications Letters, 15(5), pp500-502
667             D. Giustiniano, D. Malone, D.J. Leith and K. Papagiannaki, 2010. Measuring transmission opportunities in 802.11 links. IEEE/ACM Transactions on Networking, 18(5), pp1516-1529
668             END
669             open $fh, '<', \$text;
670             $refs->add_fromfile($fh);
671              
672             # or as text scraped from a google scholar personal home page
673              
674             $refs->add_google('http://scholar.google.com/citations?user=n8dX1fUAAAAJ');
675              
676             # or as text obtained from ORCID (www.orcid.org)
677              
678             $refs->add_orcid('0000-0003-4056-4014');
679              
680             # or as text from PubMed
681              
682             $refs->add_pubmed('mills kh[author]');
683            
684             # or as text from DBLP
685              
686             $refs->add_dblp('http://www.informatik.uni-trier.de/~ley/pers/xx/l/Leith:Douglas_J=');
687              
688             # Bib:Tools will use Bib:CrossRef to try to resolve the supplied text into full citations. It will try to
689             detect duplicates using DOI information, so its fairly safe to import from multiple sources without creating
690             clashes. Full citations without DOI information are kept separately from those with a DOI for better quality
691             control.
692              
693             # The resulting list of full citations containing DOI's can be printed out in human readable form using
694              
695             print $refs->print;
696              
697             # and the list of full citations without DOI's
698              
699             print $refs->print_nodoi;
700              
701             # or the complete citation list can also be output as a simple web page using
702              
703             print $refs->send_resp;
704              
705             =head1 METHODS
706            
707             =head2 new
708              
709             my $refs = Bib::Tools->new();
710              
711             Creates a new Bib::Tools object. Queries to crossref via Bib::CrossRef are rate limited. To change the ratelimit pass this as
712             an option to new e.g $refs = Bib::Tools->new(3) sets the rate limit to 3 queries per second.
713              
714             =head2 add_google
715              
716             $refs->add_google($url);
717            
718             Scrapes citation information from a google scholar personal home page (*not* a search page, see below) and tries
719             resolve it into full citations using crossref.
720              
721             =head2 add_google_search
722              
723             $refs->add_google_search($url);
724            
725             Scrapes citation information from a google scholar search page and tries to resolve into full citations. A different
726             method is needed for search and home pages due to the different html tags used.
727              
728             =head2 add_orcid
729              
730             $refs->add_orcid($orcid_id);
731            
732             Uses the ORCID API to extract citations for the specified user identifier. If possible, the DOI is obtained and then resolved using crossref.
733              
734             =head2 add_dblp
735              
736             $refs->add_dblp($url);
737            
738             Uses DBLP XML API to extract citations. If possible, the DOI is obtained and then resolved using crossref. E.g.
739              
740             $refs->add_dblp('http://www.informatik.uni-trier.de/~ley/pers/xx/l/Leith:Douglas_J=');
741            
742             =head2 add_pubmed
743            
744             $refs->add_dblp($query);
745            
746             Uses PubMed API to extract citations listed in response to a query. E.g.
747              
748             $refs->add_pubmed('mills kh[author]');
749            
750             =head2 add_details
751              
752             $refs->add_details(@lines);
753            
754             Given a array of strings, one per citation, tries to resolve these into full citations.
755              
756             =head2 add_fromfile
757              
758             $refs->add_fromfile($fh);
759            
760             Given a file handle to a text file, with one citation per line, tries to resolve these into full citations.
761            
762             =head2 print
763              
764             my $info = $refs->print;
765              
766             Display the list of full citations that have DOIs in human readable form.
767              
768             =head2 print_nodoi
769              
770             my $info = $refs->print_nodoi;
771              
772             Display the list of full citations without DOIs in human readable form.
773              
774             =head2 sethtml
775              
776             $refs->sethtml
777            
778             Set the output format to be html
779              
780             =head2 clearhtml
781              
782             $refs->clearhtml
783            
784             Set the output format to be plain text
785              
786             =head2 send_resp
787              
788             my $info = $refs->send_resp;
789              
790             =head2 num
791              
792             my $num = $refs->num;
793            
794             Returns the number of full citations that have DOIs
795              
796             =head2 num_nodoi
797              
798             my $num = $refs->num_nodoi;
799            
800             Returns the number of full citations without DOIs
801              
802             =head2 getref
803              
804             my $ref = $refs->getref($i);
805            
806             Returns the $i citation from the list with DOIs. This can be used to walk the list of citations.
807              
808             =head2 getref_nodoi
809              
810             my $ref = $refs->getref_nodoi($i);
811              
812             Returns the $i citation from the list without DOIs
813              
814             =head2 append
815              
816             my $ref = Bib::CrossRef->new;
817             $refs->append($ref);
818              
819             Adds a Bib::CrossRef to end of a Bib::Tools list of objects
820              
821             =head1 EXPORTS
822            
823             You can export the following functions if you do not want to use the object orientated interface:
824              
825             sethtml clearhtml add_details add_google add_google_search add_orcid add_fromfile add_dblp add_pubmed
826             send_resp print print_nodoi num num_nodoi getref getref_nodoi append
827              
828             The tag C is available to easily export everything:
829            
830             use Bib::Tools qw(:all);
831              
832             =head1 WEB INTERFACE
833              
834             A simple web interface to Bib::Tools is contained in the examples folder. This consists of three files: query.html, handle_query.pl and post.js.
835              
836             =head2 query.html
837              
838            
839            
840            

Import References

841            
842            
843            
Use ORCID id:
e.g. 0000-0003-4056-4014
844            

845            
846            
(to import from Scopus, follow these instructions)
847            
Use Google Scholar personal page:
848             e.g. http://scholar.google.com/citations?user=n8dX1fUAAAAJ
849            

850            
851            
Use Google Scholar search page:
852             e.g. http://scholar.google.com/scholar?q=andr%C3%A9s+garcia+saavedra
853            

854            
855            
Use DBLP page:
856             e.g. 'http://www.informatik.uni-trier.de/~ley/pers/xx/l/Leith:Douglas_J=
857            
858            
Use PubMed query:
859             e.g. mills kh[author]
860            
861            
862            

Enter references, one per line:


863             (can be slow, be patient)
864            
865            
866              
867             =head2 handle_query.pl
868              
869             #!/usr/bin/perl
870             use Bib::CrossRef;
871             use Bib::Tools;
872             use CGI;
873              
874             # send html header
875             print "Content-Type: text/html;\n\n";
876              
877             my $q = CGI->new;
878             my $refs = Bib::Tools->new;
879             my $orcid = scalar $q->param('orcid');
880             $orcid =~ /([0-9\-]+)$/; # extract id out of url
881             $orcid = $1;
882             if (length($orcid) > 5) {
883             $refs->add_orcid($1);
884             }
885              
886             my $google = scalar $q->param('google'); #NB: CGI has already carried out URL decoding
887             if (length($google) > 5) {
888             if (!($google =~ m/^http/)) { $google = "http://".$google;}
889             $refs->add_google($google);
890             }
891              
892             my $google2 = scalar $q->param('google2'); #NB: CGI has already carried out URL decoding
893             if (length($google2) > 5) {
894             if (!($google2 =~ m/^http/)) { $google2 = "http://".$google2;}
895             $refs->add_google_search($google2);
896             }
897              
898             my $dblp = scalar $q->param('dblp');
899             if (length($dblp) > 5) {
900             if (!($dblp =~ m/^http/)) { $dblp = "http://".$dblp;}
901             if ($dblp =~ m/http:\/\/dblp.uni-trier.de\/pers\/xx\/l\/.+/) {
902             # looks like a valid dblp url
903             $refs->add_dblp($dblp);
904             } else {
905             print "

DBLP url looks invalid: ", $dblp,"

";
906             }
907             }
908              
909             my $pubmed = scalar $q->param('pubmed');
910             if (length($pubmed) > 5) {
911             $refs->add_pubmed($pubmed);
912             }
913            
914             my @values = $q->multi_param('refs');
915             foreach my $value (@values) {
916             open my $fh, "<", \$value; #NB: CGI has already carried out URL decoding
917             $refs->add_fromfile($fh);
918             }
919              
920             $refs->sethtml;
921             print $refs->send_resp;
922              
923             =head2 post.js
924              
925             function GetCellValues(dataTable) {
926             var table = document.getElementById(dataTable);
927             if (table == null) return;
928             var i = 0; var Obj = [];
929             var names = table.rows[0];
930             for (var r = 1; r < table.rows.length; r++) {
931             if (table.rows[r].id == 'cite') {
932             var row = table.rows[r].cells;
933             var check = table.rows[r].getElementsByTagName('Input');
934             if (check.length>0){
935             Obj[i] = {};
936             for (var c = 3; c < row.length; c++){
937             var tag = names.cells[c].textContent;
938             Obj[i][tag] =row[c].textContent;
939             }
940             i = i+1;
941             }
942             }
943             }
944             var jsonString = JSON.stringify(Obj);
945             document.getElementById('out').innerHTML = document.getElementById('out').innerHTML+jsonString;
946             // or POST using ajax
947             }
948              
949             =head1 VERSION
950            
951             Ver 0.14
952            
953             =head1 AUTHOR
954            
955             Doug Leith
956            
957             =head1 BUGS
958            
959             Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
960            
961             =head1 COPYRIGHT
962            
963             Copyright 2015 D.J.Leith.
964            
965             This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
966            
967             See http://dev.perl.org/licenses/ for more information.
968            
969             =cut
970              
971              
972             __END__