File Coverage

blib/lib/PICA/Record.pm
Criterion Covered Total %
statement 310 360 86.1
branch 118 158 74.6
condition 41 57 71.9
subroutine 44 49 89.8
pod 31 31 100.0
total 544 655 83.0


line stmt bran cond sub pod time code
1             package PICA::Record;
2             {
3             $PICA::Record::VERSION = '0.585';
4             }
5             #ABSTRACT: Perl module for handling PICA+ records
6 13     13   119119 use strict;
  13         27  
  13         468  
7 13     13   8418 use utf8;
  13         98  
  13         82  
8 13     13   482 use 5.10.0;
  13         44  
  13         720  
9              
10 13     13   76 use base qw(Exporter);
  13         26  
  13         2678  
11             our @EXPORT = qw(readpicarecord writepicarecord);
12             our @EXPORT_OK = qw(picarecord pgrep pmap);
13             our %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
14              
15             our $XMLNAMESPACE = 'info:srw/schema/5/picaXML-v1.0';
16              
17             our @CARP_NOT = qw(PICA::Field PICA::Parser);
18              
19 13     13   13735 use POSIX qw(strftime);
  13         104143  
  13         354  
20 13     13   20952 use PICA::Field;
  13         35  
  13         741  
21 13     13   3404 use PICA::Parser;
  13         36  
  13         587  
22 13     13   205 use Scalar::Util qw(looks_like_number);
  13         27  
  13         943  
23 13     13   11205 use URI::Escape;
  13         18092  
  13         793  
24 13     13   85 use XML::Writer;
  13         26  
  13         289  
25 13     13   19584 use Encode;
  13         154595  
  13         1247  
26 13     13   111 use PerlIO;
  13         31  
  13         115  
27 13     13   368 use Carp qw(croak confess);
  13         28  
  13         1462  
28              
29             use overload
30 8     8   40 'bool' => sub { ! $_[0]->empty },
31 13     13   85 '""' => sub { $_[0]->string };
  13     20   29  
  13         188  
  20         2728  
32              
33 13     13   6724 use sort 'stable';
  13         3498  
  13         94  
34              
35              
36             # private method to append a field
37             my $append_field = sub {
38             my ($self, $field) = @_;
39             # confess('append_failed') unless ref($field) eq 'PICA::Field';
40             if ( $field->tag eq '003@' ) {
41             $self->{_ppn} = $field->sf('0');
42             if ( $self->field('003@') ) {
43             $self->update( '003@', $field );
44             return 0;
45             }
46             }
47             # TODO: limit occ and iln, epn
48             return 0 if $field->empty;
49              
50             push(@{ $self->{_fields} }, $field);
51             return 1;
52             };
53              
54             # private method to compile and cache a regular expression
55             my %field_regex;
56             my $get_regex = sub {
57             my $reg = shift;
58              
59             return $reg if ref($reg) eq 'Regexp';
60              
61             my $regex = $field_regex{ $reg };
62              
63             if (!defined $regex) {
64             # Compile & stash
65             $regex = qr/^$reg$/;
66             $field_regex{ $reg } = $regex;
67             }
68              
69             return $regex;
70             };
71              
72              
73              
74             sub new {
75 535     535 1 9929 my $class = shift; # if $_[0] and UNIVERSAL::isa( $_[0], 'PICA::Record'
76             # shift if defined $_[0] and $_[0] eq $class; # called as function
77              
78 535   33     995 $class = $class || ref($class); # Handle cloning
79 535         2139 my $self = bless {
80             _fields => [],
81             _ppn => undef
82             }, $class;
83              
84 535 100       1145 return $self unless @_;
85              
86 522         653 my $first = $_[0];
87              
88 522 100       782 if (defined $first) {
89              
90 521 100 100     2132 if ($#_ == 0 and ref(\$first) eq 'SCALAR') {
    100 100        
91 7         40 my @lines = split("\n", $first);
92 7         34 my @l2 = split("\x1E", $first);
93 7 100       24 if (@l2 > @lines) { # normalized
94 1         5 @lines = @l2;
95             }
96              
97 7         13 foreach my $line (@lines) {
98 62         98 $line =~ s/^\x1D//; # start of record
99 62 100       199 next if $line =~ /^\s*$/; # skip empty lines
100              
101 57         218 my $field = PICA::Field->parse($line);
102 57 50       157 $append_field->( $self, $field ) if $field;
103             }
104 512         3095 } elsif (ref($first) eq 'GLOB' or eval { $first->isa('IO::Handle') }) {
105             PICA::Parser->parsefile( $first, Limit => 1, Field => sub {
106 3102     3102   5709 $append_field->( $self, shift );
107 3102         6952 return;
108 6         73 });
109             } else {
110 508         1124 $self->append( @_ );
111             }
112             } else {
113 1         351 croak('Undefined parameter in PICA::Record->new');
114             }
115              
116 521         1252 return $self;
117             } # new()
118              
119              
120             sub copy {
121 0     0 1 0 my $self = shift;
122 0         0 return PICA::Record->new( $self );
123             }
124              
125              
126             sub field {
127 57     57 1 5377 my $self = shift;
128 57 100       303 my $limit = looks_like_number($_[0]) ? shift : 0;
129 57         131 my @specs = @_;
130              
131 57 100       155 my $test = ref($specs[-1]) eq 'CODE' ? pop @specs : undef;
132 57 100 100     192 @specs = (".*") if $test and not @specs;
133              
134 57 50       140 return unless @specs;
135 57         92 my @list = ();
136              
137 57         116 for my $tag ( @specs ) {
138 59         160 my $regex = $get_regex->($tag);
139              
140 59         239 for my $maybe ( $self->fields ) {
141 309 100       830 if ( $maybe->tag() =~ $regex ) {
142 43         63 local $_ = $maybe;
143 43 100 100     133 if ( not $test or $test->($maybe) ) {
144 38 100       105 return $maybe unless wantarray;
145 35         46 push( @list, $maybe );
146 35 100       103 if ($limit > 0) {
147 5 100       23 return @list unless --$limit;
148             }
149             }
150             }
151             }
152             }
153              
154 52         291 return @list;
155             } # field()
156              
157             # Shortcut
158             *f = \&field;
159              
160              
161             sub subfield {
162 567     567 1 6554 my $self = shift;
163 567 100       1420 my $limit = looks_like_number($_[0]) ? shift : 0;
164 567 50       1220 return unless defined $_[0];
165              
166 567         629 my @list = ();
167              
168 567         1002 while (@_) {
169 574         601 my $tag = shift;
170 574         481 my $subfield;
171            
172 574 50       2139 croak "Not a field or full pattern: $tag"
173             unless $tag =~ /^([^\$_]{3,})([\$_]([^\$_]+))?/;
174 574 100       1017 if (defined $2) {
175 560         1154 ($tag, $subfield) = ($1, $3);
176             } else {
177 14         23 $subfield = shift;
178             }
179              
180 574 50       943 croak("Missing subfield for $tag")
181             unless defined $subfield;
182              
183 574         1023 my $tag_regex = $get_regex->($tag);
184 574         1216 for my $f ( $self->fields ) {
185 810 100       2078 if ( $f->tag() =~ $tag_regex ) {
186 582         1489 my @s = $f->subfield($subfield);
187 582 100       1105 if (@s) {
188 580 100       2662 return shift @s unless wantarray;
189 35 100       79 if ($limit > 0) {
190 10 100       26 if (scalar @s >= $limit) {
191 5         23 push @list, @s[0..($limit-1)];
192 5         27 return @list;
193             }
194 5         10 $limit -= scalar @s;
195             }
196 30         105 push( @list, @s );
197             }
198             }
199             }
200             }
201              
202 17 100       61 return $list[0] unless wantarray;
203 15         71 return @list;
204             } # subfield()
205              
206             # Shortcut
207             *sf = \&subfield;
208              
209              
210             sub values {
211 8     8 1 3365 my $self = shift;
212 8         21 my @values = $self->subfield( @_ );
213 8         38 return @values;
214             }
215              
216              
217             sub fields() {
218 685     685 1 2928 my $self = shift;
219 685 50       1333 croak("You called all_fields() but you probably want field()") if @_;
220 685         682 return @{$self->{_fields}};
  685         4288  
221             }
222              
223              
224             sub size {
225 4     4 1 10 my $self = shift;
226 4         5 return 1 * @{$self->{_fields}};
  4         24  
227             }
228              
229              
230             sub occurrence {
231 1     1 1 2 my $self = shift;
232 1 50       6 return unless $self->{_fields}->[0];
233 1         8 return $self->{_fields}->[0]->occurrence;
234             }
235              
236             sub occ {
237 1     1 1 8 return shift->occurrence;
238             }
239              
240              
241             sub main {
242 2     2 1 3 my $self = shift;
243 2         7 my @fields = $self->field("0...(/..)?");
244              
245 2         7 return PICA::Record->new(@fields);
246             }
247              
248              
249             sub holdings {
250 7     7 1 593 my ($self, $iln) = @_;
251              
252 7         18 my %holdings = ();
253 7         13 my @fields = ();
254 7         8 my $prevtag;
255 7         12 my $curiln = '';
256 7         9 my @pending;
257            
258 7         13 foreach my $f (@{$self->{_fields}}) {
  7         30  
259 9187 100       18754 next if $f->tag =~ /^0/;
260              
261 9035 100 66     19069 if ($f->tag eq '101@' and defined $f->sf('a')) {
    50          
262 175         408 $curiln = $f->sf('a');
263 175   50     937 $holdings{$curiln} //= [ ];
264 175 50       331 if (@pending) {
265 0         0 push @{ $holdings{$curiln} }, @pending;
  0         0  
266 0         0 @pending = ();
267             }
268 175         183 push @{ $holdings{$curiln} }, $f;
  175         370  
269             } elsif( $curiln eq '' ) {
270 0         0 push @pending, $f;
271             } else {
272 8860         8121 push @{ $holdings{$curiln} }, $f;
  8860         15826  
273             }
274              
275 9035         10798 push @fields, $f;
276 9035         17751 $prevtag = $f->tag;
277             }
278              
279 7 50       27 push @{ $holdings{$curiln} }, @pending if @pending;
  0         0  
280              
281 7 100       22 if ($iln) {
282 2         470 %holdings = ($iln => $holdings{$iln});
283 2 50       10 %holdings = () unless $holdings{$iln};
284             }
285              
286 7         50 foreach my $iln (keys %holdings) {
287 65         633 @{$holdings{$iln}} = sort {
  3578         7776  
288 65         297 my ($ta,$tb) = ($a->tag, $b->tag);
289 3578         11034 $ta =~ s{^(2...)/(..)$}{2$2$1};
290 3578         10203 $tb =~ s{^(2...)/(..)$}{2$2$1};
291 3578         4998 $ta cmp $tb;
292 65         62 } @{$holdings{$iln}};
293             }
294              
295 265   50     5205 my @h = sort { ($a->iln // '0') <=> ($b->iln // '0') }
  65   50     356  
296 7         37 map { PICA::Record->new( @$_ ) } CORE::values(%holdings);
297              
298 7 100       1119 return $iln ? $h[0] : @h;
299             }
300              
301              
302             sub items {
303 5     5 1 1593 my $self = shift;
304              
305 5         11 my @copies = ();
306 5         9 my @fields = ();
307 5         8 my $prevocc;
308              
309 5         10 foreach my $f (@{$self->{_fields}}) {
  5         16  
310 3332 100       6921 next unless $f->tag =~ /^[^0]/;
311              
312 3269 100       6560 if ($f->tag =~ /^1/) {
313 181         179 $prevocc = undef;
314 181 100       388 push @copies, PICA::Record->new(@fields) if (@fields);
315 181         335 @fields = ();
316             } else {
317 3088 50       6277 next unless $f->tag =~ /^2...\/(..)/;
318              
319 3088 100 100     11734 if (!($prevocc && $prevocc eq $1)) {
320 385         471 $prevocc = $1;
321 385 100       1157 push @copies, PICA::Record->new(@fields) if (@fields);
322 385         617 @fields = ();
323             }
324              
325 3088         4891 push @fields, $f;
326             }
327             }
328 5 50       29 push @copies, PICA::Record->new(@fields) if (@fields);
329 5         3909 return @copies;
330             }
331              
332              
333             sub empty {
334 50     50 1 673 my $self = shift;
335 50         68 foreach my $field (@{$self->{_fields}}) {
  50         169  
336 36 50       113 return 0 if !$field->empty;
337             }
338 14         106 return 1;
339             }
340              
341              
342             sub ppn {
343 4     4 1 510 my $self = shift;
344 4 100       15 if ( @_ ) {
345 1         3 my $ppn = shift;
346 1 50       5 if (defined $ppn) {
347 1         12 $append_field->( $self, PICA::Field->new('003@', '0' => $ppn) )
348             } else {
349 0         0 $self->remove('003@');
350             }
351             }
352 4         28 return $self->{_ppn};
353             }
354              
355              
356             sub epn {
357 4     4 1 15 my $self = shift;
358             #for(my $i=0; $i<@_; $i++) {
359             # # TODO: add EPNs
360             #}
361 4         13 return $self->subfield('203@/..$0');
362             }
363              
364              
365             sub iln {
366             # TODO: set ILN with this method and check uniqueness
367 531     531 1 1326 my $self = shift;
368 531         880 return $self->subfield('101@$a');
369             }
370              
371              
372             sub append {
373 516     516 1 571 my $self = shift;
374             # TODO: this method can be simplified by use of ->new (see appendif)
375              
376 516         501 my $c = 0;
377              
378 516         965 while (@_) {
379             # Append a field (whithout creating a copy)
380 530   100     2547 while (@_ and UNIVERSAL::isa($_[0],'PICA::Field') ) {
381 7278         11406 $c += $append_field->( $self, shift );
382             }
383             # Append a whole record (copy all its fields)
384 530   100     1329 while (@_ and UNIVERSAL::isa($_[0],'PICA::Record')) {
385 5         7 my $record = shift;
386 5         13 for my $field ( $record->fields ) {
387 22         110 $c += $append_field->( $self, $field->copy );
388             }
389             }
390 530 100       1388 if (@_) {
391 36         76 my @params = (shift); # tag
392 36   66     262 while (@_ and defined $_[0] and length($_[0]) == 1) {
      100        
393 41         60 push @params, shift; # subfield
394 41         179 push @params, shift; # value
395             }
396 36 50       200 $c += $append_field->( $self, PICA::Field->new( @params ) ) if @params > 1;
397             }
398             }
399              
400             #use Data::Dumper;
401             #print Dumper(\@_)."\n";
402             # local $Carp::CarpLevel = 1;
403              
404              
405 516         734 return $c;
406             }
407              
408              
409             sub appendif {
410 3     3 1 13 my $self = shift;
411 3         11 my $append = PICA::Record->new( @_ );
412 3         10 for my $field ( $append->fields ) {
413 2         8 $field = $field->purged();
414 2 50       7 $append_field->( $self, $field ) if $field;
415             }
416 3         14 $self;
417             }
418              
419              
420             sub update {
421 8     8 1 1025 my $self = shift;
422 8         12 my $tag = shift;
423              
424 8 50       24 croak("Not a valid tag: $tag")
425             unless PICA::Field::parse_pp_tag( $tag );
426              
427 8         11 my $replace;
428              
429 8 100       20 return unless @_; # ignore
430              
431 7 100 100     68 if ( not defined $_[0] ) {
    100          
432 1         2 $replace = shift;
433             } elsif ( UNIVERSAL::isa( $_[0], 'PICA::Field' ) or ref($_[0]) eq 'CODE' ) {
434 4         8 $replace = shift;
435             } else {
436 2         9 $replace = PICA::Field->new($tag, @_);
437             }
438              
439 7         16 my $regex = $get_regex->($tag);
440              
441 7         18 for my $field ( $self->fields ) {
442 33 100       87 if ( $field->tag() =~ $regex ) {
443 7         12 my $rep = $replace;
444 7 100       32 if ( UNIVERSAL::isa( $replace, 'CODE' ) ) {
445 2         4 $rep = $rep->( $field );
446 2 100       10 $rep = undef unless UNIVERSAL::isa( $rep, 'PICA::Field' );
447             }
448 7 100       18 if (defined $rep) {
449 6 100       15 $self->{_ppn} = $rep->sf('0') if $rep->tag eq '003@';
450 6         23 $field->replace( $rep );
451             }
452 7 100       33 return unless ref($replace) eq 'CODE';
453             }
454             }
455             }
456              
457              
458             sub remove {
459 5     5 1 29 my $self = shift;
460 5         13 my @specs = @_;
461              
462 5 50       15 return 0 if !@specs;
463 5         12 my $c = 0;
464              
465 5         10 for my $tag ( @specs ) {
466 6         14 my $regex = $get_regex->($tag);
467              
468 6         11 my $i=0;
469 6         17 for my $maybe ( $self->fields ) {
470 47 100       123 if ( $maybe->tag() =~ $regex ) {
471 32 100       83 $self->{_ppn} = undef if $maybe->tag() eq '003@';
472 32         45 splice( @{$self->{_fields}}, $i, 1);
  32         71  
473 32         69 $c++;
474             } else {
475 15         73 $i++;
476             }
477             }
478             } # for $tag
479              
480 5         16 return $c;
481             }
482              
483              
484             sub sort {
485 2     2 1 1053 my $self = shift;
486              
487 2         10 my $main = $self->main;
488 2         7 my @holdings = $self->holdings;
489              
490 2         4 @{$self->{_fields}} = sort { $a->tag cmp $b->tag } @{$main->{_fields}};
  2         6  
  1         6  
  2         10  
491              
492 2         5 foreach my $h ( @holdings ) {
493 4         5 push @{$self->{_fields}}, @{$h->{_fields}};
  4         6  
  4         10  
494             }
495              
496 2         19 $self;
497             }
498              
499              
500             sub add_headers {
501 0     0 1 0 my ($self, %params) = @_;
502              
503 0         0 my $eln = $params{eln};
504 0 0       0 croak("add_headers needs an ELN") unless defined $eln;
505              
506 0         0 my $status = $params{status};
507 0 0       0 croak("add_headers needs status") unless defined $status;
508              
509 0 0       0 my @timestamp = defined $params{timestamp} ? @{$params{timestamp}} : localtime;
  0         0  
510             # TODO: Test timestamp
511              
512 0         0 my $hdate = strftime ("$eln:%d-%m-%g", @timestamp);
513 0         0 my $htime = strftime ("%H:%M:%S", @timestamp);
514              
515             # Pica3: 000K - Unicode-Kennzeichen
516 0         0 $self->append( "001U", '0' => 'utf8' );
517              
518             # PICA3: 0200 - Kennung und Datum der Ersterfassung
519             # http://www.gbv.de/vgm/info/mitglieder/02Verbund/01Erschliessung/02Richtlinien/01KatRicht/0200.pdf
520 0         0 $self->append( "001A", '0' => $hdate );
521              
522             # PICA3: 0200 - Kennung und Datum der letzten Aenderung
523             # http://www.gbv.de/vgm/info/mitglieder/02Verbund/01Erschliessung/02Richtlinien/01KatRicht/0210.pdf
524 0         0 $self->append( "001B", '0' => $hdate, 't' => $htime );
525              
526             # PICA3: 0230 - Kennung und Datum der Statusaenderung
527             # http://www.gbv.de/vgm/info/mitglieder/02Verbund/01Erschliessung/02Richtlinien/01KatRicht/0230.pdf
528 0         0 $self->append( "001D", '0' => $hdate );
529              
530             # PCIA3: 0500 - Bibliographische Gattung und Status
531             # http://www.gbv.de/vgm/info/mitglieder/02Verbund/01Erschliessung/02Richtlinien/01KatRicht/0500.pdf
532 0         0 $self->append( "002@", '0' => $status );
533             }
534              
535              
536             sub string {
537 34     34 1 211 my ($self, %args) = @_;
538              
539 34 50       158 $args{endfield} = "\n" unless defined($args{endfield});
540              
541 34         65 my @lines = ();
542 34         44 for my $field ( @{$self->{_fields}} ) {
  34         543  
543 715         2458 push( @lines, $field->string(%args) );
544             }
545 34         518 return join('', @lines);
546             }
547              
548              
549             sub normalized() {
550 6     6 1 26 my $self = shift;
551 6         8 my $prefix = shift;
552 6 50       14 $prefix = "" if (!$prefix);
553              
554 6         11 my @lines = ();
555 6         6 for my $field ( @{$self->{_fields}} ) {
  6         15  
556 6         35 push( @lines, $field->normalized() );
557             }
558              
559 6         33 return "\x1D\x0A" . $prefix . join( "", @lines );
560             }
561              
562              
563             sub xml {
564 0     0 1 0 my $self = shift;
565 0         0 my $writer = $_[0];
566 0         0 my ($string, $sref);
567              
568             # write to a string
569 0 0       0 if (not UNIVERSAL::isa( $writer, 'XML::Writer' )) {
570 0         0 my %params = @_;
571 0 0       0 if (not defined $params{OUTPUT}) {
572 0         0 $sref = \$string;
573 0         0 $params{OUTPUT} = $sref;
574             }
575 0         0 $writer = PICA::Writer::xmlwriter( %params );
576             }
577              
578 0 0       0 if ( UNIVERSAL::isa( $writer, 'XML::Writer::Namespaces' ) ) {
579 0         0 $writer->startTag( [$PICA::Record::XMLNAMESPACE, 'record'] );
580             } else {
581 0         0 $writer->startTag( 'record' );
582             }
583 0         0 for my $field ( @{$self->{_fields}} ) {
  0         0  
584 0         0 $field->xml( $writer );
585             }
586 0         0 $writer->endTag();
587              
588 0 0       0 return defined $sref ? $$sref : undef;
589             }
590              
591              
592             sub html {
593 0     0 1 0 my $self = shift;
594 0         0 my %options = @_;
595              
596 0         0 my @html = ("
\n");
597 0         0 for my $field ( @{$self->{_fields}} ) {
  0         0  
598 0         0 push @html, $field->html( %options );
599             }
600 0         0 push @html, "";
601              
602 0         0 return join("", @html) . "\n";
603             }
604              
605              
606             sub write {
607 3     3 1 1971 my $record = shift;
608 3         40 my $writer = PICA::Writer->new( @_ );
609 3 50       15 return $writer unless $writer;
610 3         13 $writer->write( $record )->end;
611             }
612              
613              
614              
615             sub pgrep (&@) {
616 2     2 1 18 my $block = shift;
617 2 100 66     17 my $record = (@_ == 1 and UNIVERSAL::isa( $_[0],'PICA::Record' ))
618             ? $_[0] : PICA::Record->new( @_ );
619 2         3 my @fields;
620              
621 2         5 for my $f ( $record->fields ) {
622 6         20 local $_ = $f;
623 6 100       12 push @fields, $f if $block->();
624             }
625              
626 2         11 return PICA::Record->new( @fields );
627             }
628              
629              
630             sub pmap (&@) {
631 1     1 1 12 my $block = shift;
632 1 50 33     11 my $record = (@_ == 1 and UNIVERSAL::isa( $_[0],'PICA::Record' ))
633             ? $_[0] : PICA::Record->new( @_ );
634 1         2 my @fields;
635              
636 1         5 for my $f ( $record->fields ) {
637 3         4 local $_ = $f;
638 3         9 my @r = $block->();
639 3 50 33     27 if (@r == 1 and UNIVERSAL::isa( $_[0],'PICA::Field' )) {
640 0         0 push @fields, $r[0];
641             } else {
642 3         9 push @fields, PICA::Field->new( @r );
643             }
644             }
645              
646 1         5 return PICA::Record->new( @fields );
647             }
648              
649              
650             sub readpicarecord {
651 3     3 1 9 my ($file, %options) = @_;
652 3 50 33     17 if ( wantarray and defined $options{Limit} ) {
653 0         0 return PICA::Parser->parsefile( $file, %options )->records();
654             }
655 3         8 $options{Limit} = 1;
656 3         31 my ($record) = PICA::Parser->parsefile( $file, %options )->records();
657 3 50 33     163 return undef unless $record and not $record->empty;
658 3         29 return $record;
659             }
660              
661              
662             *writepicarecord = *write;
663              
664              
665             sub picarecord {
666 0     0 1   return PICA::Record->new( @_ );
667             }
668              
669             1;
670              
671             __END__