File Coverage

blib/lib/REST/Client/CrossRef.pm
Criterion Covered Total %
statement 287 421 68.1
branch 100 188 53.1
condition 17 30 56.6
subroutine 36 48 75.0
pod 16 18 88.8
total 456 705 64.6


line stmt bran cond sub pod time code
1             package REST::Client::CrossRef;
2 1     1   170595 use strict;
  1         2  
  1         34  
3 1     1   6 use warnings;
  1         2  
  1         27  
4 1     1   526 use Moo;
  1         10969  
  1         6  
5            
6 1     1   2064 use JSON;
  1         9653  
  1         6  
7 1     1   557 use URI::Escape;
  1         1289  
  1         60  
8 1     1   497 use REST::Client;
  1         45531  
  1         39  
9            
10             #use Data::Dumper;
11 1     1   10 use Carp;
  1         3  
  1         55  
12 1     1   466 use Log::Any;
  1         8085  
  1         5  
13 1     1   523 use HTTP::Cache::Transparent;
  1         14580  
  1         7  
14            
15             #use JSON::MultiValueOrdered;
16             #use YAML;
17 1     1   519 use JSON::Path;
  1         50585  
  1         9  
18            
19 1     1   620 use namespace::clean;
  1         9273  
  1         8  
20            
21             =head1 NAME
22            
23             REST::Client::CrossRef - Read data from CrossRef using its REST API
24            
25             =cut
26            
27             our $VERSION = '0.009';
28            
29             =head1 VERSION
30            
31             Version 0.009
32            
33             =cut
34            
35             =head1 DESCRIPTION
36            
37             This module use L to read the data from the CrossRef repository.
38            
39             =cut
40            
41             =head1 SYNOPSIS
42            
43             use Log::Any::Adapter( 'File', './log.txt', 'log_level'=> 'info');
44             use REST::Client::CrossRef;
45            
46             #the mail address is added in the request's header
47             #return the data without transformation
48            
49             my $cr = REST::Client::CrossRef->new(
50             mailto => 'you@somewhre.com',
51             spit_raw_data => 1,
52             );
53            
54             #cache the data with HTTP::Cache::Transparent
55             $cr->init_cache(
56             { BasePath => ".\cache",
57             NoUpdate => 60 * 60,
58             verbose => 0
59             });
60            
61             my $data = $cr->journal_from_doi('10.1088/0004-637X/722/2/971');
62            
63             print Dumper($data), "\n"; #$data is a hash ref of the json data converted to perl
64            
65             #unfold the data to something like
66             # field1/subfield1/subfield2 : value
67             #add an undef value after each item fields
68             #output only the fields given with keys_to_keep, with the same ordering
69            
70             my $cr = REST::Client::CrossRef->new(
71             mailto => 'you@somewhere.com',
72             add_end_flag => 1,
73             keys_to_keep => [
74             ['author'], ['title'], ['container-title'],
75             ['volume'],['issue'], ['page'],['issued/date-parts'], ['published-print/date-parts']
76             ],);
77            
78             my $data = $cr->article_from_doi('10.1088/0004-637X/722/2/971');
79            
80             for my $row (@$data) {
81             if (! $row) {
82             print "\n";
83             next;
84             }
85             while ( my ($f, $v) = each %$row) {
86             print "$f : $v \n";
87             }
88             }
89            
90            
91             #display the item's fields in alphabetic order
92             #add 'end of data' field after each item
93            
94             my $cr = REST::Client::CrossRef->new(
95             mailto => 'you@somewhre.com',
96             add_end_flag => 1,
97             sort_output => 1,
98             );
99            
100             $cr->init_cache(
101             { BasePath => "C:\\Windows\\Temp\\perl",
102             NoUpdate => 60 * 60,
103             verbose => 0
104             });
105            
106             my @fields = (qw/author title/);
107             my @values = (qw/allan electron/);
108            
109             #return 100 items by page
110            
111             $cr->rows(100);
112             my $data = $cr->query_articles( \@fields, \@values );
113             while () {
114             last unless $data;
115            
116             for my $row (@$data) {
117             print "\n" unless ($row);
118             for my $field (keys %$row) {
119             print $field, ": ", $row->{$field}. "\n";
120             }
121             }
122             $data = $cr->get_next();
123             }
124            
125             Example output:
126            
127             author : Wilke, Ingrid;
128             MacLeod, Allan M.;
129             Gillespie, William A.;
130             Berden, Giel;
131             Knippels, Guido M. H.;
132             van der Meer, Alexander F. G.;
133             container-title : Optics and Photonics News
134             issue : 12
135             issued/date-parts : 2002, 12, 1,
136             page : 16
137             published-online/date-parts : 2002, 12, 1,
138             published-print/date-parts : 2002, 12, 1,
139             title : Detectors: Time-Domain Terahertz Science Improves Relativistic Electron-Beam Diagnostics
140             volume : 13
141            
142             my $cr = REST::Client::CrossRef->new(
143             mailto => 'dokpe@unifr.ch',
144             spit_raw_data => 0,
145             add_end_flag => 1,
146             json_path => [
147             ['$.author[*]'],
148             ['$.title'],
149             ['$.container-title'],
150             ['$.volume'], ['$.issue'], ['$.page'],
151             ['$.issued..date-parts'],
152             ['$.published-print..date-parts']
153             ],
154             json_path_callback => { '$.items[*].author[*]' => \&unfold_authors },
155             );
156            
157             sub unfold_authors {
158             my ($data_ar) = @_;
159             my @res;
160             for my $aut (@$data_ar) {
161             my $line;
162             if ( $aut->{affiliation} ) {
163             my @aff;
164             for my $hr ( @{$aut->{affiliation}} ) {
165             my @aff = values %$hr;
166             $aff[0] =~ s/\r/ /g;
167             $line .= " " . $aff[0];
168             }
169             }
170             my $fn = (defined $aut->{given}) ?( ", " . $aut->{given} . "; " ): "; ";
171             push @res, $aut->{family} . $fn . ($line // "");
172             }
173             return \@res;
174             }
175            
176             my $data = $cr->article_from_doi($doi);
177             next unless $data;
178             for my $row (@$data) {
179             if ( !$row ) {
180             print "\n";
181             next;
182             }
183             while ( my ( $f, $v ) = each %$row ) {
184             print "$f : $v \n";
185             }
186             }
187            
188             Example of output:
189             $.author[*] : Pelloni, Michelle; University of Basel, Department of Chemistry, Mattenstrasse 24a, BPR 1096, CH 4002 Basel, Switzerland
190             Cote, Paul; School of Chemistry and Biochemistry, University of Geneva, Quai Ernest Ansermet 30, CH-1211 Geneva, Switzerland
191             ....
192             Warding, Tom.; University of Basel, Department of Chemistry, Mattenstrasse 24a, BPR 1096, CH 4002 Basel, Switzerland
193             $.title : Chimeric Artifact for Artificial Metalloenzymes
194             $.container-title : ACS Catalysis
195             $.volume : 8
196             $.issue : 2
197             $.page : 14-18
198             $.issued..date-parts : 2018, 1, 24
199             $.published-print..date-parts : 2018, 2, 2
200            
201             my $cr = REST::Client::CrossRef->new( mailto => 'you@somewher.com'
202             ,keys_to_keep => [["breakdowns/id", "id"], ["location"], [ "primary-name", "breakdowns/primary-name", "name" ]],
203             );
204            
205             $cr->init_cache(
206             { BasePath => "C:\\Windows\\Temp\\perl",
207             NoUpdate => 60 * 60,
208             verbose => 0
209             });
210            
211             $cr->rows(100);
212            
213             my $rs_ar = $cr->get_members;
214            
215             while () {
216             last unless $rs_ar;
217             for my $row_hr (@$rs_ar) {
218             for my $k (keys %$row_hr) {
219             print $k . " : " . $row_hr->{$k} . "\n";
220             }
221             }
222             $rs_ar = $cr->get_next();
223             }
224            
225             Example of items in the output above
226            
227             id : 5007
228             location : W. Struve 1 Tartu 50091 Estonia
229             primary-name : University of Tartu Press
230            
231             id : 310
232             location : 23 Millig Street Helensburgh Helensburgh Argyll G84 9LD United Kingdom
233             primary-name : Westburn Publishers
234            
235             id : 183
236             location : 9650 Rockville Pike Attn: Lynn Willis Bethesda MD 20814 United States
237             primary-name : Society for Leukocyte Biology
238            
239             =cut
240            
241             has baseurl => ( is => 'ro', default => sub {'https://api.crossref.org'} );
242             has modified_since => ( is => 'ro' );
243            
244             #has version => (is => 'ro', default => sub {'v1'} );
245            
246             has rows => (
247             is => 'rw',
248             default => sub {0},
249             isa => sub { croak "rows must be under 1000" unless $_[0] < 1000 }
250             );
251             has code => ( is => 'rw' );
252             has sleep => ( is => 'rw', default => sub {0} );
253             has log => ( is => 'lazy' );
254             has client => ( is => 'lazy' );
255             has decoder => ( is => 'lazy' );
256            
257             =head2 C<$cr = REST::Client::CrossRef-Enew( ... mailto =E your@email.here, ...)>
258            
259             The email address is placed in the header of the page.
260             See L
261            
262             =cut
263            
264             has mailto => ( is => 'lazy', default => sub {0} );
265            
266             =head2 C<$cr = REST::Client::CrossRef-Enew( ... sort_output =E1, ...)>
267            
268             Rows can be sorted using the key name with sort_ouput => 1.
269             Default to 0.
270             In effect only if C is false.
271            
272             =cut
273            
274             has sort_output => ( is => 'lazy', default => sub {0} );
275             has test_data => ( is => 'lazy', default => sub {0} );
276            
277             =head2 C<$cr = REST::Client::CrossRef-Enew( ... spit_raw_data =E1, ...)>
278            
279             Display the data as a hashref if 0 or as an array ref of hasref,
280             where each hashref is a row of key => value that can be sorted with sort_ouput => 1.
281             C default to 0.
282            
283             =cut
284            
285             has spit_raw_data => ( is => 'lazy', default => sub {0} );
286             has cursor => ( is => 'rw' );
287             has page_start_at => ( is => 'rw', default => sub {0} );
288            
289             =head2 C<$cr = REST::Client::CrossRef-Enew( ... add_end_flag =E1, ...)>
290            
291             Add undef after an item's fields.
292             Default to 1.
293            
294             =cut
295            
296             has add_end_flag => ( is => 'lazy', default => sub {1} );
297            
298             =head2 C<$cr = REST::Client::CrossRef-Enew( ... keys_to_keep =E [[key1, key1a, ...], [key2], ... ], ...)>
299            
300             An array ref of array ref, the inner array ref give a key name and the possible alternative keys for the same value,
301             for example [ "primary-name", "breakdowns/primary-name", "name" ] in the member road (url ending with /members).
302             The keys enumeration starts below C, or C - C if the result is a list.
303             This filters the values that are returned and preserves the ordering of the array ref given in argument.
304             The ouput is an array ref of hash ref, each hash having the key and the values.
305             Values are flattened as string. In effect only if spit_raw_data is false.
306            
307             =cut
308            
309             has keys_to_keep => ( is => 'lazy' );
310            
311             =head2 C<$cr = REST::Client::CrossRef-Enew( ... json_path =E [[$path1, path1a, ...], [path2], ... ], ...)>
312            
313             An array ref of array ref, the inner array refs give a L
314             and the possible alternative path for the same value. See also L.
315             The json path starts below C, or C - C if the result is a list.
316             The output, ordering, filtering and flattening is as above. In effect only if spit_raw_data is false.
317            
318             =cut
319            
320             has json_path => ( is => 'lazy' );
321            
322             =head2 C<$cr = REST::Client::CrossRef-Enew( ... json_path_callback =E {$path =E \&some_function }>
323            
324             An hash ref that associates a JSON path and a function that will be run on the data return by C<$jpath-Evalues($json_data)>.
325             The function must accept an array ref as first argument and must return an array ref.
326            
327             =cut
328            
329             has json_path_callback => ( is => 'lazy' );
330            
331             =head2 C<$cr = REST::Client::CrossRef-Enew( ... json_path_safe =E "0", ... )>
332            
333             To turn off the message C set this to 0.
334             Default to 1.
335            
336             =cut
337            
338             has json_path_safe => (is => 'lazy', default=> sub{1});
339            
340             =head2 C<$cr = REST::Client::CrossRef-Enew( ... version =E "v1", ... )>
341            
342             To use a defined version of the api.
343             See L
344            
345             =cut
346            
347             has version => ( is => 'ro' );
348            
349             sub _build_client {
350 2     2   20 my ($self) = @_;
351 2         15 my $client = REST::Client->new();
352            
353             #HTTP::Cache::Transparent::init( { BasePath => './cache', NoUpdate => 15*60, verbose=>1 } );
354             #$self->cache()
355            
356 2 50       9411 if ( $self->version ) {
357 0         0 $self->log->notice( "< Crossref-API-Version: " . $self->version );
358 0         0 $client->addHeader( 'api-version', $self->version );
359             }
360 2 50       44 if ( defined $self->mailto ) {
361            
362             #my $authorization = 'Bearer ' . $self->key;
363 2         37 $self->log->notice( "< Mailto: " . $self->mailto );
364 2         117 $client->addHeader( 'mailto', $self->mailto );
365            
366             }
367            
368 2         57 $client;
369             }
370            
371             sub _build_decoder {
372 4     4   39 my $self = shift;
373 4         400 return JSON->new;
374            
375             #return JSON::MultiValueOrdered->new;
376            
377             }
378            
379             sub _build_log {
380 4     4   40 my ($self) = @_;
381 4         27 Log::Any->get_logger( category => ref($self) );
382             }
383            
384             =head2 C<$cr-Einit_cache( @args )> C<$cr-Einit_cache( $hash_ref )>
385            
386             See L.
387             The array of args is passed to the object constructor.
388             The log file shows if the data has been fetch from the cache and if the server has been queryied to detect any change.
389            
390             =cut
391            
392             sub init_cache {
393 0     0 1 0 my ( $self, @args ) = @_;
394 0         0 my $href;
395 0 0       0 if ( ref $args[0] eq "HASH" ) {
396 0         0 $href = $args[0];
397             }
398             else {
399 0         0 my %h;
400 0         0 %h = @args;
401 0         0 $href = \%h;
402             }
403 0         0 HTTP::Cache::Transparent::init($href);
404             }
405            
406             sub _build_filter {
407 4     4   11 my ( $self, $ar ) = @_;
408            
409             #die Dumper $self->{keys_to_keep};
410             # die "ar:" . Dumper $ar;
411 4         7 my %filter;
412 4         9 for my $filter_name (qw(keys_to_keep json_path)) {
413            
414             # my %keys_to_keep;
415 8 100       68 next if ( !exists $ar->{$filter_name} );
416            
417             #print "_build_filter: $filter_name\n";
418 3         8 my $pos;
419             my %pos_seen;
420 3         0 my %key_seen;
421            
422 3         6 for my $ar ( @{ $self->{$filter_name} } ) {
  3         7  
423 6         10 $pos++;
424 6         12 for my $k (@$ar) {
425 6         14 $filter{$k} = $pos - 1;
426 6         14 $pos_seen{ $pos - 1 } = 0;
427 6         17 $key_seen{ $pos - 1 } = $k;
428             }
429            
430             }
431 3         8 $self->{pos_seen} = \%pos_seen;
432 3         6 $self->{key_seen} = \%key_seen;
433 3         70 $self->{$filter_name} = \%filter;
434             }
435            
436             }
437            
438             sub BUILD {
439            
440 4     4 0 35 my ( $self, $ar ) = @_;
441             croak "Can't use both keys_to_keep and json_path"
442 4 50 66     16 if ( $ar->{json_path} && $ar->{keys_to_keep} );
443 4         60 $self->_build_filter($ar);
444             }
445            
446             sub _crossref_get_request {
447 9     9   53 my ( $self, $path, $query_ar, %param ) = @_;
448 9 100       152 return 1 if ( $self->test_data() );
449 7 50       126 my $url = sprintf "%s%s%s", $self->baseurl,
450             $self->version ? "/" . $self->version : "", $path;
451            
452 7         21 my @params = ();
453            
454 7 50       26 if ($query_ar) {
455            
456 0         0 for my $p (@$query_ar) {
457            
458             #print "$p\n";
459 0         0 push @params, $p;
460            
461             }
462             }
463 7         26 for my $name ( keys %param ) {
464 14         26 my $value = $param{$name};
465            
466             #location:United Kingdom space is uri_escape twice
467             #push @params, uri_escape($name) . "=" . uri_escape($value) if ($value);
468 14 100       69 push @params, $name . "=" . $value if ($value);
469            
470             }
471 7 100       144 push @params, "rows=" . $self->rows if ( $self->rows );
472 7 100       201 push @params, "cursor=" . $self->cursor if ( $self->cursor );
473            
474             #if the first url as &offset=1 we missed the first item
475             #1 in page_start_at means "paginate with offset"
476             #offset : page_start_at -1
477 7 50 33     39 push @params, "offset=" . ( $self->page_start_at - 1 )
478             if ( $self->page_start_at && $self->page_start_at > $self->rows );
479 7 100       50 $url .= '?' . join( "&", @params ) if @params > 0;
480            
481             #die Dumper @params;
482             # The server asked us to sleep..
483 7 50       38 if ( $self->sleep > 0 ) {
484 0         0 $self->log->notice( "sleeping: " . $self->sleep . " seconds" );
485 0         0 sleep $self->sleep;
486 0         0 $self->sleep(0);
487             }
488 7         120 $self->log->notice(" ");
489 7         160 $self->log->notice("requesting: $url");
490 7         185 my $response = $self->client->GET($url);
491 7         12828662 my $val = $response->responseHeader('Backoff');
492 7 50       507 my $backoff = defined $val ? $val : 0;
493 7         34 $val = $response->responseHeader('Retry-After');
494 7 50       375 my $retryAfter = defined $val ? $val : 0;
495 7         42 my $code = $response->responseCode();
496            
497 7         505 $self->log->notice("> Code: $code");
498 7         351 $self->log->notice("> Backoff: $backoff");
499 7         233 $self->log->notice("> Retry-After: $retryAfter");
500 7         99 for my $k (qw/X-Cached X-Content-Unchanged X-No-Server-Contact/) {
501 21 50       989 $self->log->notice( "> $k: " . $response->responseHeader($k) )
502             if $response->responseHeader($k);
503             }
504            
505 7 50 33     520 if ( $backoff > 0 ) {
    50          
506 0         0 $self->sleep($backoff);
507             }
508             elsif ( $code eq '429' || $code eq '503' ) {
509 0 0       0 $self->sleep( defined $retryAfter ? $retryAfter : 60 );
510 0         0 return;
511             }
512            
513             #$self->log->notice( "> Content: " . substr($response->responseContent, 0, 50) );
514 7         166 $self->log->notice( "> Content: " . $response->responseContent );
515            
516 7         3002 $self->code($code);
517            
518 7 50       43 return unless $code eq '200';
519            
520 7         61 $response;
521             }
522            
523             #pagination using cursor
524             #page_start_set to 0 to insure that get_next called this function again
525             sub _get_metadata {
526 8     8   46 my ( $self, $path, $query_ar, $filter, $select ) = @_;
527 8 100       158 $self->log->notice( "test_data: ",
528             ( $self->test_data() ? " 1 " : " 0 " ) );
529 8         2348 $self->page_start_at(0);
530 8         40 my $response =
531             $self->_crossref_get_request( $path,
532             $query_ar, ( filter => $filter, select => $select ) );
533            
534             # print Dumper $response;
535 8 50       49 return unless $response;
536            
537             #my $hr = decode_json $response->responseContent;
538            
539 8 100       248 my $hr =
540             $self->test_data()
541             ? $self->_decode_json( $self->test_data() )
542             : $self->_decode_json( $response->responseContent );
543 8         34 my $res_count = $hr->{message}->{'total-results'};
544            
545             #print $res_count;
546 8 100 66     65 if ( defined $res_count && $res_count > 0 ) {
547            
548             # my $keys;
549             # my $data_ar = $hr->{message}->{items};
550 7         19 my $returned_items = @{ $hr->{message}->{items} };
  7         35  
551 7         59 $self->_set_cursor( $hr->{message}, $returned_items );
552            
553             #push @$keys, "items";
554             #die $self->spit_raw_data;
555             #$self->_display_data($hr);
556             }
557 8         261 return $self->_display_data($hr);
558             }
559            
560             #pagination using page_start_at + offset
561             #curosr set to undef to insure that get_next called this function again
562             sub _get_page_metadata {
563 1     1   4 my ( $self, $path, $param_ar ) = @_;
564            
565 1         2 my $response;
566             my $out;
567 1         5 $self->cursor(undef);
568 1 50       4 if ($param_ar) {
569 0         0 my $filter = join( ",", @$param_ar );
570            
571 0         0 $response =
572             $self->_crossref_get_request( $path, undef,
573             ( filter => $filter ) );
574             }
575             else {
576 1         3 $response = $self->_crossref_get_request($path);
577             }
578            
579 1 50       13 if ($response) {
580            
581             #my $hr = decode_json $response->responseContent;
582 1 50       16 my $hr =
583             $self->test_data()
584             ? $self->_decode_json( $self->test_data() )
585             : $self->_decode_json( $response->responseContent );
586 1         4 my $res_count = $hr->{message}->{'total-results'};
587            
588 1 50       4 if ( defined $res_count ) {
589            
590             # print "from metadata: ", $res_count, "\n";
591 0 0       0 if ( $res_count > 0 ) {
592            
593             #die Dumper $hr->{message}->{items};
594 0         0 my $returned_items_count = @{ $hr->{message}->{items} };
  0         0  
595            
596 0         0 $self->{last_page_items_count} = $returned_items_count;
597 0         0 $out = $self->_display_data($hr);
598            
599             }
600             }
601             else { #singleton
602 1         3 $out = $self->_display_data($hr);
603            
604             }
605             }
606            
607 1         3 return $out;
608            
609             }
610            
611             sub _display_data {
612 9     9   32 my ( $self, $hr ) = @_;
613            
614 9 100       187 return $hr if ( $self->spit_raw_data );
615 8         142 my $formatter = REST::Client::CrossRef::Unfolder->new();
616 8 50 66     61 return if ($hr->{message}->{'total-results'} && $hr->{message}->{'total-results'}==0);
617 8         15 my $data_ar;
618 8 100       29 if ( $hr->{message}->{items} ) {
619 6         37 $data_ar = $hr->{message}->{items};
620             }
621             else {
622 2         4 $data_ar = [ $hr->{message} ];
623             }
624            
625 8         18 my @data;
626 8 100       35 if ( defined $self->{json_path} ) {
    50          
627            
628 6         21 my %result;
629 6         13 my %keys = %{ $self->{json_path} };
  6         47  
630 6         18 my %selectors;
631 6         234 $JSON::Path::Safe=$self->json_path_safe;
632 6         87 for my $path ( keys %keys ) {
633            
634             #print $path, "\n";
635 18         216 $selectors{$path} = JSON::Path->new($path);
636             }
637            
638 6         58 for my $data_hr (@$data_ar) {
639            
640 30         78 for my $path ( keys %selectors ) {
641            
642             #my @val = $jpath->values( $hr->{message} );
643 90         345 my @val = $selectors{$path}->values($data_hr);
644            
645 90 50 33     222459 if ( $self->{json_path_callback}
    50          
646             && $self->{json_path_callback}->{$path} )
647             {
648 0         0 my @data;
649 0         0 my $cb = $self->{json_path_callback}->{$path};
650 0 0       0 eval { @data = @{ $cb->( \@val ) } if(@val); };
  0         0  
  0         0  
651 0 0       0 carp "Json callback failed : $@\n" if ($@);
652 0         0 $result{$path} = join( "\n", @data );
653            
654             }
655             elsif (@val) {
656            
657 90         165 my %res_part;
658             %res_part =
659 90         188 %{ $formatter->_unfold_array( \@val, [$path] ) };
  90         332  
660 90         469 @result{ keys %res_part } = values %res_part;
661             }
662            
663             }
664            
665             push @data,
666 30         70 @{ $self->_sort_output( $self->{json_path}, \%result ) };
  30         136  
667             }
668            
669             }
670             elsif ( defined $self->{keys_to_keep} ) {
671            
672 2         4 my $new_ar;
673            
674             #$data_ar :array ref of rows items
675 2         7 $formatter->set_keys_to_keep( $self->{keys_to_keep} );
676            
677 2         5 for my $data_hr (@$data_ar) {
678            
679             #https://www.perlmonks.org/?node_id=1224994
680             #my $result_hr = {};
681             #$formatter->_unfold_hash($data_hr, undef, $result_hr);
682 2         6 my $result_hr = $formatter->_unfold_hash($data_hr);
683            
684             # $self->log->debug("display_data\n", Dumper $result_hr);
685             push @data,
686 2         5 @{ $self->_sort_output( $self->{keys_to_keep}, $result_hr ) };
  2         6  
687            
688             }
689            
690             }
691             else { #neither json_path nor keys_to_keep defined, spit_raw_data set to 0
692            
693 0         0 for my $data_hr (@$data_ar) {
694 0         0 my $val_hr = $formatter->_unfold_hash($data_hr);
695 0         0 my @keys;
696 0 0       0 if ( $self->{sort_output} ) {
697 0         0 @keys = sort { lc($a) cmp lc($b) } keys %$val_hr;
  0         0  
698             }
699             else {
700 0         0 @keys = keys %$val_hr;
701             }
702 0         0 for my $k (@keys) {
703 0         0 push @data, { $k, $val_hr->{$k} };
704             }
705            
706 0 0       0 push @data, undef if $self->add_end_flag;
707             }
708            
709             }
710 8         558 return \@data;
711             }
712            
713             sub _sort_output {
714 32     32   112 my ( $self, $filter_hr, $result_hr ) = @_;
715            
716 32         51 my @data;
717            
718 32 50       78 if ($filter_hr) {
719            
720 32         57 my %keys_to_keep = %{$filter_hr};
  32         124  
721 32         71 my %pos_seen = %{ $self->{pos_seen} };
  32         141  
722 32         63 my %key_seen = %{ $self->{key_seen} };
  32         122  
723 32         127 $pos_seen{$_} = 0 foreach ( keys %pos_seen );
724            
725 32         59 my @item_data;
726 32         129 for my $key ( keys %$result_hr ) {
727            
728 244         373 my $pos = $keys_to_keep{$key};
729 244 100       450 next unless defined $pos;
730            
731             # print "pos undef for $key\n" unless defined $pos;
732             #$key_unseen{$pos}= $key;
733 93         150 $pos_seen{$pos} = 1;
734             my $value =
735             ( defined $result_hr->{$key} )
736 93 50       244 ? $result_hr->{$key}
737             : "";
738 93         1877 $self->log->debug( $key, " - ", $value );
739 93         1058 $item_data[$pos] = { $key => $value };
740            
741             }
742            
743 32         115 my @unseen = grep { !$pos_seen{$_} } keys %pos_seen;
  93         215  
744            
745 32         100 for my $pos (@unseen) {
746 0         0 $item_data[$pos] = { $key_seen{$pos}, "" };
747            
748             }
749 32         79 push @data, @item_data;
750 32 100       574 push @data, undef if $self->add_end_flag;
751            
752             }
753             else {
754 0         0 my @keys;
755 0 0       0 if ( $self->{sort_output} ) {
756 0         0 @keys = sort { lc($a) cmp lc($b) } keys %$result_hr;
  0         0  
757             }
758             else {
759 0         0 @keys = keys %$result_hr;
760             }
761 0         0 for my $k (@keys) {
762            
763 0         0 push @data, { $k, $result_hr->{$k} };
764             }
765            
766 0 0       0 push @data, undef if $self->add_end_flag;
767             }
768            
769 32         501 return \@data;
770            
771             }
772            
773             =head2 C<$cr-Erows( $row_value )>
774            
775             Set the rows parameter that determines how many items are returned in one page
776            
777             =cut
778            
779             =head2 C<$cr-Eworks_from_doi( $doi, $filter, $select )>
780            
781             Retrive the metadata from the work road (url ending with works) using the article's doi.
782             Return undef if the doi is not found.
783             You may pass a C<$filter> hash ref C<{filter1 =E value1, ...}>
784             L for a list of filters.
785             You may pass a C<$select> string with the format C<"field1,field2,..."> to return only these fields.
786             Fields that may be use for selection are (October 2018):
787             abstract, URL, member, posted, score, created, degree, update-policy, short-title, license, ISSN,
788             container-title, issued, update-to, issue, prefix, approved, indexed, article-number, clinical-trial-number,
789             accepted, author, group-title, DOI, is-referenced-by-count, updated-by, event, chair, standards-body, original-title,
790             funder, translator, archive, published-print, alternative-id, subject, subtitle, published-online, publisher-location,
791             content-domain, reference, title, link, type, publisher, volume, references-count, ISBN, issn-type, assertion,
792             deposited, page, content-created, short-container-title, relation, editor.
793             Use keys_to_keep or json_path to define an ordering in the ouptut. Use select to filter the fields to be returned from the server.
794            
795             =cut
796            
797             sub works_from_doi {
798 0     0 1 0 my ( $self, $doi, $filter, $select ) = @_;
799 0 0       0 croak "works_from_doi: need doi" unless defined $doi;
800 0         0 my @filters;
801 0         0 for my $fname (keys %$filter) {
802 0         0 push @filters, $fname . ":" . uri_escape( $filter->{$fname});
803             }
804 0         0 push @filters, "doi:" . uri_escape($doi);
805 0         0 my $filter_str = join(",", @filters);
806 0         0 $self->_get_metadata( "/works", undef, $filter_str , $select );
807             }
808            
809             =head2 C<$cr-Eworks_from_orcid( $orcid, $filter, $select )>
810            
811             Retrive the metadata or undef from the work road using author's orcid.
812             C<$filter> and C<$select> as above.
813            
814             =cut
815            
816             sub works_from_orcid {
817 0     0 1 0 my ( $self, $id, $filter, $select ) = @_;
818 0 0       0 croak "works_from_doi: need orcid" unless defined $id;
819 0         0 my @filters;
820 0         0 for my $fname (keys %$filter) {
821 0         0 push @filters, $fname . ":" . uri_escape( $filter->{$fname});
822             }
823 0 0       0 my $url =
824             $id =~ /^https*:\/\/orcid.org\// ? $id
825             : "http://orcid.org/" . $id;
826 0         0 push @filters, "orcid:" . uri_escape($url);
827 0         0 my $filter_str = join(",", @filters);
828 0         0 $self->_get_metadata( "/works", undef, $filter_str , $select );
829             }
830            
831             =head2 C<$cr-Ejournal_from_doi( $doi )>
832            
833             A shortcut for C
834            
835             =cut
836            
837             sub journal_from_doi {
838 0     0 1 0 my ( $self, $doi ) = @_;
839 0 0       0 croak "journal_from_doi: need doi" unless defined $doi;
840 0         0 $self->_get_metadata( "/works", undef, "doi:$doi",
841             "container-title,page,issued,volume,issue" );
842            
843             }
844            
845             =head2 C<$cr-Earticle_from_doi( $doi )>
846            
847             A shortcut for C
848            
849             =cut
850            
851             sub article_from_doi {
852 1     1 1 585 my ( $self, $doi ) = @_;
853 1 50       5 croak "article_from_doi: need doi" unless defined $doi;
854 1         6 $self->_get_metadata( "/works", undef, "doi:$doi",
855             "title,container-title,page,issued,volume,issue,author,published-print,published-online"
856             );
857             }
858            
859             =head2 C<$cr-Earticle_from_funder( $funder_id, {name=E'smith'}, $select )>
860            
861             Retrive the metadata from the works road for a given funder, searched with an author's name or filtered by any valid filter name.
862             For example C<{'has-orcid'=E 'true', 'has-affiliation'=E'true'}>.
863             C<$select> default to "title,container-title,page,issued,volume,issue,published-print,DOI". Use * to retrieve all fields.
864            
865             =cut
866            
867             sub articles_from_funder {
868 1     1 0 16 my ( $self, $id, $href, $select ) = @_;
869            
870 1 50       4 croak "articles_from_funder: need funder id" unless defined $id;
871 1 50       5 $select = (
872             $select
873             ? $select
874             : "title,container-title,page,issued,volume,issue,published-print,DOI"
875             );
876 1 50       6 $self->{select} = $select eq "*" ? undef : $select;
877 1         4 $self->{path} = "/funders/$id/works";
878 1         5 $self->cursor("*");
879 1         2 my @filters;
880             my $query;
881 1         4 for my $k ( keys %$href ) {
882 2 50       49 if ( $k eq "name" ) {
    50          
883 0         0 $query = [ "query.author=" . uri_escape( $href->{$k} ) ];
884 0         0 $self->{param} = $query;
885             #return $self->_get_metadata( "/funders/$id/works", $query, undef, $self->{select} );
886             }
887             elsif ( $k eq "orcid" ) {
888             my $url =
889             $href->{$k} =~ /^https*:\/\/orcid.org\//
890             ? $href->{$k}
891 0 0       0 : "http://orcid.org/" . $href->{$k};
892 0         0 push @filters, "orcid:" . uri_escape($url);
893             #$self->{filter} = "orcid:" . uri_escape($url);
894             #return $self->_get_metadata( "/funders/$id/works", undef,$self->{filter}, $self->{select} );
895             }
896             else { #croak "articles_from_funder : unknown key : $k";
897             #$self->{filter} = $k . ":" . uri_escape( $href->{$k});
898             #return $self->_get_metadata( "/funders/$id/works", undef,$self->{filter}, $self->{select} );
899 2         10 push @filters, $k . ":" . uri_escape( $href->{$k});
900             }
901             }
902 1         14 $self->{filter} = join(",", @filters);
903            
904             return $self->_get_metadata( "/funders/$id/works", $query, $self->{filter},
905 1         6 $self->{select} );
906            
907             }
908            
909             =head2 C<$cr-Eget_types()>
910            
911             Retrieve all the metadata from the types road.
912            
913             =cut
914            
915             sub get_types {
916 1     1 1 8 my $self = shift;
917 1         3 $self->_get_metadata("/types");
918             }
919            
920             =head2 C<$cr-Eget_members()>
921            
922             Retrieve all the metadata (> 10'000 items) from the members road.
923            
924             =cut
925            
926             sub get_members {
927 0     0 1 0 my $self = shift;
928            
929 0         0 $self->page_start_at(1);
930 0         0 $self->{path} = "/members";
931 0         0 $self->_get_page_metadata("/members");
932            
933             }
934            
935             =head2 C<$cr-Emember_from_id( $member_id )>
936            
937             Retrieve a members from it's ID
938            
939             =cut
940            
941             sub member_from_id {
942 1     1 1 9 my ( $self, $id ) = @_;
943 1 50       4 croak "member_from_id: need id" unless ($id);
944 1         23 my $rows = $self->rows();
945 1         22 $self->rows(0);
946 1         10 my $rs = $self->_get_page_metadata("/members/$id");
947 1         21 $self->rows($rows);
948 1         9 return $rs;
949            
950             }
951            
952             =head2 C<$cr-Eget_journals()>
953            
954             Retrieve all the metadata (> 60'000 items) from the journals road.
955            
956             =cut
957            
958             sub get_journals {
959 0     0 1 0 my $self = shift;
960 0         0 $self->{path} = "/journals";
961 0         0 $self->page_start_at(1);
962 0         0 $self->_get_page_metadata("/journals");
963            
964             }
965            
966             =head2 C<$cr-Eget_licences()>
967            
968             Retrieve all the metadata (> 700 items) from the licenses road.
969            
970             =cut
971            
972             sub get_licences {
973 0     0 1 0 my $self = shift;
974 0         0 $self->{path} = "/licences";
975 0         0 $self->page_start_at(1);
976 0         0 $self->_get_page_metadata("/licences");
977            
978             }
979            
980             =head2 C<$cr-Equery_works( $fields_array_ref, $values_array_ref, $select_string )>
981            
982             See L for the fields that can be searched.
983             You may omit the "query." part in the field name.
984             The corresponding values are passed in a second array, in the same order.
985             Beware that searching with first and family name is treated as an OR not and AND:
986             C will retrieve all the works where and author has Tom in the name field or all works where an author has Smith in the name field.
987             See C above for the fields that can be selected.
988             Use keys_to_keep or json_path to define an ordering in the ouptut. Use select to filter the fields to be returned from the server.
989             =cut
990            
991             sub query_works {
992 0     0 1 0 my ( $self, $field_ar, $value_ar, $select ) = @_;
993 0         0 my $i;
994             my @params;
995 0         0 for my $field (@$field_ar) {
996 0 0       0 croak "unknown field $field"
997             unless ( $field
998             =~ /(?:container-)*title$|author$|editor$|chair$|translator$|contributor$|bibliographic$|affiliation$/
999             );
1000 0 0       0 $field = "query." . $field unless ( $field =~ /^query\./ );
1001 0         0 push @params, $field . "=" . uri_escape( $value_ar->[ $i++ ] );
1002             }
1003 0         0 $self->cursor("*");
1004 0         0 $self->{path} = "/works";
1005 0         0 $self->{param} = \@params;
1006 0         0 $self->{select} = $select;
1007 0         0 $self->_get_metadata( "/works", \@params, undef, $select );
1008            
1009             }
1010            
1011             =head2 C<$cr-Equery_articles( $fields_array_ref, $values_array_ref )>
1012            
1013             A shortcut for C<$cr-Equery_works($fields_array_ref, $values_array_ref, "title,container-title,page,issued,volume,issue,author,published-print,published-online")>
1014            
1015             =cut
1016            
1017             sub query_articles {
1018 0     0 1 0 my ( $self, $field_ar, $value_ar ) = @_;
1019 0         0 $self->query_works( $field_ar, $value_ar,
1020             "title,container-title,page,issued,volume,issue,author,published-print,published-online"
1021             );
1022             }
1023            
1024             =head2 C<$cr-Equery_journals( $fields_array_ref, $values_array_ref )>
1025            
1026             A shortcut for C<$cr-Equery_works($fields_array_ref, $values_array_ref, "container-title,page,issued,volume,issue">
1027            
1028             =cut
1029            
1030             sub query_journals {
1031 0     0 1 0 my ( $self, $field_ar, $value_ar ) = @_;
1032 0         0 $self->query_works( $field_ar, $value_ar,
1033             "container-title,page,issued,volume,issue" );
1034            
1035             }
1036            
1037             =head2 C<$cr-Eget_next()>
1038            
1039             Return the next set of data in the /works, /members, /journals, /funders, /licences roads,
1040             Return undef after the last set.
1041            
1042             =cut
1043            
1044             sub get_next {
1045 5     5 1 999 my $self = shift;
1046 5 50       136 $self->log->debug( "get_next cursor: ",
1047             ( defined $self->cursor ? " defined " : " undef" ) );
1048 5         155 $self->log->debug( "get_next page_start_at: ", $self->page_start_at );
1049 5         54 my $res;
1050 5 50       24 if ( $self->cursor ) {
1051             $res = $self->_get_metadata(
1052             $self->{path}, $self->{param},
1053             $self->{filter}, $self->{select}
1054 5         39 );
1055             }
1056 5         46 my $last_start = $self->page_start_at;
1057            
1058             #as long as the count of items returned is equal to ->rows
1059             #there should be a next page to ask for: increment page_start_at to page_start_at + row
1060 5 50 33     22 if ( $last_start && $self->{last_page_items_count} >= $self->rows ) {
1061 0         0 $self->page_start_at( $last_start + $self->rows );
1062 0         0 $res = $self->_get_page_metadata( $self->{path}, $self->{param} );
1063             }
1064 5         66 return $res;
1065             }
1066            
1067             =head2 C<$cr-Eagencies_from_dois( $dois_array_ref )>
1068            
1069             Retrieve the Registration agency (CrossRef, mEdra ...) using an array ref of article doi.
1070             L
1071            
1072             =cut
1073            
1074             sub agencies_from_dois {
1075 0     0 1 0 my ( $self, $dois_ar ) = @_;
1076 0         0 my @results;
1077            
1078             # die Dumper $dois_ar;
1079 0         0 my $rows = $self->rows;
1080 0         0 $self->rows(0);
1081 0         0 for my $doi (@$dois_ar) {
1082            
1083             #print "looking for $doi\n";
1084 0         0 my $response =
1085             $self->_crossref_get_request( "/works/" . $doi . "/agency" );
1086 0 0       0 if ($response) {
1087 0         0 my $hr = $self->_decode_json( $response->responseContent );
1088            
1089             # my @items = $hr->{message}->{items};
1090 0         0 my $res = $self->_display_data($hr);
1091 0 0       0 return $res if ($self->spit_raw_data);
1092 0         0 push @results, $res;
1093            
1094             }
1095            
1096             }
1097 0         0 $self->rows($rows);
1098            
1099 0         0 return \@results;
1100             }
1101            
1102             =head2 C<$cr-Efunders_from_location( $a_location_name )>
1103            
1104             Retrieve the funder from a country. Problem is that there is no way of having a list of country name used.
1105             These locations has been succefully tested: United Kingdom, Germany, Japan, Morocco, Switzerland, France.
1106            
1107             =cut
1108            
1109             sub funders_from_location {
1110 0     0 1 0 my ( $self, $loc ) = @_;
1111 0 0       0 croak "funders_from_location : need location" unless $loc;
1112 0         0 my $rows = $self->rows;
1113            
1114             #$self->rows(0);
1115 0         0 my $data;
1116             my @params;
1117 0         0 push @params, "location:" . uri_escape($loc);
1118 0         0 $self->page_start_at(1);
1119 0         0 $self->{path} = "/funders";
1120 0         0 $self->{param} = \@params;
1121 0         0 $self->{select} = undef;
1122 0         0 $self->_get_page_metadata( "/funders", \@params );
1123            
1124             #$self->rows($rows);
1125             #return $data;
1126             }
1127            
1128             sub _set_cursor {
1129 7     7   35 my ( $self, $msg_hr, $n_items ) = @_;
1130 7         63 my %msg = %$msg_hr;
1131 7 100 66     232 if ( exists $msg{'next-cursor'} && $n_items >= $self->rows ) {
1132            
1133             # print "_set_cursor: ", uri_escape( $msg{'next-cursor'} ), "\n";
1134 6         94 $self->cursor( uri_escape( $msg{'next-cursor'} ) );
1135             }
1136             else {
1137             # print "_set_cursor: undef\n";
1138 1         6 $self->cursor(undef);
1139             }
1140             }
1141            
1142             sub _decode_json {
1143 9     9   335 my ( $self, $json ) = @_;
1144 9         205 my $data = $self->decoder->decode($json);
1145 9         1111 return $data;
1146            
1147             }
1148            
1149             package REST::Client::CrossRef::Unfolder;
1150            
1151             #use Data::Dumper;
1152 1     1   4801 use Carp;
  1         3  
  1         63  
1153 1     1   6 use Log::Any;
  1         12  
  1         10  
1154            
1155             sub new {
1156 8     8   24 my ($class) = shift;
1157 8         102 my $self = { logger => Log::Any->get_logger( category => "unfolder" ), };
1158 8         1886 return bless $self, $class;
1159            
1160             }
1161            
1162             sub log {
1163 885     885   1197 my $self = shift;
1164 885         2701 return $self->{logger};
1165             }
1166            
1167             # This setting of the array ref could be removed since the ordering in display_data
1168             # also remove the keys that are not wanted. But the hash builded is smaller
1169             # with adding only the key that are needed.
1170             sub set_keys_to_keep {
1171 2     2   4 my ( $self, $ar_ref ) = @_;
1172 2         8 $self->{keys_to_keep} = $ar_ref;
1173            
1174             }
1175            
1176             sub _unfold_hash {
1177 40     40   65 my ( $self, $raw_hr, $key_ar, $result_hr ) = @_;
1178            
1179 40 100       76 $self->log->debug( "unfold_hash1: ",
1180             ( $result_hr ? scalar %$result_hr : 0 ) );
1181 40         179 for my $k ( keys %$raw_hr ) {
1182            
1183             # $self->log->debug( "key: ", $k );
1184            
1185 214         343 push @$key_ar, $k;
1186            
1187 214 100       477 if ( ref $raw_hr->{$k} eq "HASH" ) {
    100          
1188            
1189             $result_hr =
1190 24         52 $self->_unfold_hash( $raw_hr->{$k}, $key_ar, $result_hr );
1191            
1192 24 50       40 $self->log->debug( "1 size ",
1193             $result_hr ? scalar %$result_hr : 0 );
1194             }
1195             elsif ( ref $raw_hr->{$k} eq "ARRAY" ) {
1196             $result_hr =
1197 30         72 $self->_unfold_array( $raw_hr->{$k}, $key_ar, $result_hr );
1198            
1199 30 50       49 $self->log->debug( "2 size ",
1200             $result_hr ? scalar %$result_hr : 0 );
1201            
1202             $result_hr->{ $key_ar->[$#$key_ar] } =~ s/,\s$//
1203 30 100       130 if ( defined $result_hr->{ $key_ar->[$#$key_ar] } );
1204            
1205             }
1206            
1207             else {
1208            
1209             $self->log->debug( "ref: ", ref $raw_hr->{$k} )
1210 160 100       299 if ( ref $raw_hr->{$k} );
1211 160         339 my $key = join( "/", @$key_ar );
1212            
1213 160 100 66     482 if ( defined $self->{keys_to_keep}
1214             && defined $self->{keys_to_keep}->{$key} )
1215             {
1216 1         2 $result_hr->{$key} = $raw_hr->{$k}
1217            
1218             }
1219             else {
1220             $self->log->debug( "key : ", $key, " value: ",
1221 159         269 $raw_hr->{$k} );
1222 159         576 $result_hr->{$key} = $raw_hr->{$k};
1223             }
1224            
1225             }
1226            
1227 214         431 my $tmp = pop @$key_ar;
1228            
1229             }
1230            
1231 40 50       78 $self->log->debug( "_unfold_hash3: ",
1232             $result_hr ? scalar(%$result_hr) : 0 );
1233 40         122 return $result_hr;
1234             }
1235            
1236             sub _unfold_array {
1237 168     168   359 my ( $self, $ar, $key_ar, $res_hr ) = @_;
1238            
1239 168 100       355 $self->log->debug( "_unfold_array0: ", $res_hr ? scalar(%$res_hr) : 0 );
1240 168         591 my $last_key = join( "/", @{$key_ar} );
  168         465  
1241 168         331 my $key = $key_ar->[$#$key_ar];
1242            
1243 168         334 $self->log->debug( "_unfold array1 key: ", $key );
1244 168 100       635 if ( $key eq "author" ) {
1245 1         19 my @first;
1246             my @groups;
1247 1         0 my $first;
1248 1         0 my @all;
1249 1         6 for my $aut (@$ar) {
1250 2 100       8 if ( $aut->{sequence} eq 'first' ) {
1251 1 50       5 if ( $aut->{family} ) {
    0          
1252             $first =
1253             "\n"
1254             . $aut->{family}
1255             . (
1256             defined $aut->{given} ? ", " . $aut->{given} : " " )
1257 1 50       6 . $self->_unfold_affiliation( $aut->{affiliation} );
1258 1         20 push @first, $first;
1259             }
1260             elsif ( $aut->{name} ) {
1261             $first = "\n"
1262             . $aut->{name}
1263 0         0 . $self->_unfold_affiliation( $aut->{affiliation} );
1264 0         0 push @groups, $first;
1265            
1266             }
1267            
1268             }
1269             else {
1270 1 50       4 if ( $aut->{family} ) {
    0          
1271             push @all,
1272             "\n"
1273             . $aut->{family}
1274             . (
1275             defined $aut->{given} ? ", " . $aut->{given} : " " )
1276 1 50       20 . $self->_unfold_affiliation( $aut->{affiliation} );
1277             }
1278             elsif ( $aut->{name} ) {
1279             push @groups,
1280             "\n"
1281             . $aut->{name}
1282 0         0 . $self->_unfold_affiliation( $aut->{affiliation} );
1283            
1284             }
1285             }
1286            
1287             }
1288            
1289 1         3 unshift @all, @first;
1290 1         2 unshift @all, @groups;
1291 1         12 $res_hr->{$key} = join( "", @all );
1292            
1293             }
1294            
1295             else {
1296            
1297 167         373 for my $val (@$ar) {
1298            
1299 208 100       507 if ( ref $val eq "HASH" ) {
    100          
1300 14         35 $res_hr = $self->_unfold_hash( $val, $key_ar, $res_hr );
1301 14         22 my $last = $#$key_ar;
1302             $res_hr->{ $key_ar->[$last] } =~ s/,\s$//
1303 14 50       31 if ( defined $res_hr->{ $key_ar->[$last] } );
1304            
1305 14 50       22 $self->log->debug( "_unfold_array2: ",
1306             $res_hr ? scalar(%$res_hr) : 0 );
1307             }
1308             elsif ( ref $val eq "ARRAY" ) {
1309 48         119 $res_hr = $self->_unfold_array( $val, $key_ar, $res_hr );
1310            
1311 48 50       114 $self->log->debug( "_unfold_array3: ",
1312             $res_hr ? scalar(%$res_hr) : 0 );
1313            
1314             }
1315             else {
1316            
1317 146 100 100     479 if ( defined $self->{keys_to_keep}
1318             && defined $self->{keys_to_keep}->{$last_key} )
1319             {
1320 2 50       5 if ( defined $val ) {
1321 2         30 $res_hr->{$last_key} .= $val . ", ";
1322             }
1323             else {
1324 0         0 $res_hr->{$last_key} = "";
1325             }
1326            
1327             }
1328             else {
1329 144         467 $res_hr->{$last_key} .= $val;
1330             }
1331            
1332             }
1333             } #for
1334            
1335             }
1336            
1337 168 50       407 $self->log->debug( "_unfold_array4: ", $res_hr ? scalar(%$res_hr) : 0 );
1338 168         753 return $res_hr;
1339             }
1340            
1341             sub _unfold_affiliation {
1342 2     2   7 my ( $self, $ar ) = @_;
1343 2         12 my $line = ";";
1344 2         4 my @aff;
1345 2         5 for my $hr (@$ar) {
1346            
1347             # my @k = keys %$hr;
1348 0         0 my @aff = values %$hr;
1349 0         0 $aff[0] =~ s/\r/ /g;
1350 0         0 $line .= " " . $aff[0];
1351             }
1352            
1353 2         8 return $line;
1354             }
1355            
1356             =head1 INSTALLATION
1357            
1358             To install this module type the following:
1359             perl Makefile.PL
1360             make
1361             make test
1362             make install
1363            
1364             On windows use nmake or dmake instead of make.
1365            
1366             =head1 DEPENDENCIES
1367            
1368             The following modules are required in order to use this one
1369            
1370             Moo => 2,
1371             JSON => 2.90,
1372             URI::Escape => 3.31,
1373             REST::Client => 273,
1374             Log::Any => 1.049,
1375             HTTP::Cache::Transparent => 1.4,
1376             Carp => 1.40,
1377             JSON::Path => 0.420
1378            
1379             =head1 BUGS
1380            
1381             See below.
1382            
1383             =head1 SUPPORT
1384            
1385             Any questions or problems can be posted to me (rappazf) on my gmail account.
1386            
1387             The current state of the source can be extract using Mercurial from
1388             L
1389            
1390             =head1 AUTHOR
1391            
1392             F. Rappaz
1393             CPAN ID: RAPPAZF
1394            
1395             =head1 COPYRIGHT
1396            
1397             This program is free software; you can redistribute
1398             it and/or modify it under the same terms as Perl itself.
1399            
1400             The full text of the license can be found in the
1401             LICENSE file included with this module.
1402            
1403             =head1 SEE ALSO
1404            
1405             L Catmandu is a toolframe, *nix oriented.
1406            
1407             L Import data from CrossRef using the CrossRef search, not the REST Api, and convert the XML result into something simpler.
1408            
1409             =cut
1410            
1411             1;
1412