File Coverage

blib/lib/PICA/Field.pm
Criterion Covered Total %
statement 265 294 90.1
branch 104 124 83.8
condition 15 27 55.5
subroutine 29 31 93.5
pod 21 21 100.0
total 434 497 87.3


line stmt bran cond sub pod time code
1             package PICA::Field;
2             {
3             $PICA::Field::VERSION = '0.585';
4             }
5             #ABSTRACT: Perl extension for handling PICA+ fields
6 13     13   67523 use strict;
  13         27  
  13         514  
7              
8 13     13   69 use base qw(Exporter);
  13         25  
  13         1104  
9              
10 13     13   70 use Carp qw(croak);
  13         26  
  13         647  
11 13     13   12695 use XML::Writer;
  13         235089  
  13         549  
12 13     13   4927 use PICA::Record;
  13         38  
  13         820  
13 13     13   8919 use PICA::Writer;
  13         44  
  13         4387  
14              
15             our @EXPORT = qw(parse_pp_tag);
16              
17             our $SUBFIELD_INDICATOR = "\x1F"; # 31
18             our $START_OF_FIELD = "\x1E"; # 30
19             our $END_OF_FIELD = "\x0A"; # 10
20              
21             our $FIELD_TAG_REGEXP = qr/[012][0-9][0-9][A-Z@]$/;
22             our $FIELD_OCCURRENCE_REGEXP = qr/[0-9][0-9]$/;
23             our $SUBFIELD_CODE_REGEXP = qr/^[0-9a-zA-Z]$/;
24              
25             use overload
26 166     166   430 'bool' => sub { ! $_[0]->empty; },
27 13     13   105 '""' => sub { $_[0]->string; };
  13     19   29  
  13         132  
  19         71  
28              
29 13     13   8806 use sort 'stable';
  13         13262  
  13         97  
30              
31              
32             sub new($) {
33 4154     4154 1 7910 my $class = shift;
34 4154   66     10338 $class = ref($class) || $class;
35              
36 4154         5338 my $tag = shift;
37 4154 50       7490 $tag or croak( "No tag provided." );
38              
39 4154 100       7847 if (not @_) { # empty field
40 11         32 return PICA::Field->parse($tag);
41             }
42              
43 4143         7066 my ($occurrence, $tagno) = parse_pp_tag($tag);
44              
45 4143 50       8547 defined $tagno or croak( "\"$tag\" is not a valid tag." );
46              
47 4143         20069 my $self = bless {
48             _tag => $tagno,
49             _occurrence => $occurrence,
50             _subfields => [],
51             }, $class;
52              
53 4143         10735 $self->add(@_);
54              
55 4143         27278 return $self;
56             }
57              
58              
59             sub copy {
60 23     23 1 469 my $self = shift;
61              
62 23         34 my $tagno = $self->{_tag};
63 23         44 my $occurrence = $self->{_occurrence};
64              
65 23         108 my $copy = bless {
66             _tag => $tagno,
67             _occurrence => $occurrence,
68             }, ref($self);
69              
70 23         35 $copy->add( @{$self->{_subfields}} );
  23         62  
71              
72 23         72 return $copy;
73             }
74              
75              
76             sub parse {
77 4088     4088 1 6284 my $class = shift;
78 4088   33     13777 $class = ref($class) || $class;
79              
80 4088         6617 my $data = shift;
81 4088         4825 my $tag_filter_func = shift;
82              
83             # TODO: better manage different parsing modes (normalized, plain, WinIBW...)
84 4088         11408 my $END_OF_FIELD = qr/[\x0A\x0D]+/; # local
85              
86 4088         12462 $data =~ s/^$START_OF_FIELD//;
87 4088         14972 $data =~ s/$END_OF_FIELD$//;
88              
89 4088         9418 my $self = bless {}, $class;
90              
91 4088         21494 my ($tagno, $subfields) = ($data =~ /([^\$\x1F\x83\s]+)\s?(.*)/);
92              
93 4088 50 33     10465 return if $tag_filter_func and !$tag_filter_func->($tagno);
94              
95             # TODO: better manage different parsing modes (normalized, plain, WinIBW...)
96 4088         4569 my $sfreg;
97 4088 50       11681 my $sf = defined $subfields ? substr($subfields, 0, 1) : '';
98 4088 100       9279 if ($sf eq "\x1F") { $sfreg = '\x1F'; }
  781 100       1197  
    100          
    100          
    50          
99 3303         4240 elsif ( $sf eq '$' ) { $sfreg = '\$'; }
100 1         2 elsif( $sf eq "\x83" ) { $sfreg = '\x83'; }
101 1         3 elsif( $sf eq "\x9f" ) { $sfreg = '\x9f'; }
102             elsif( $sf eq '') {
103 2         23 return $self->new($tagno,'');
104             } else {
105 0         0 croak("not allowed subfield indicator (ord: " . ord($sf) . ") specified");
106             }
107 4086         7033 $sfreg = '('.$sfreg.'[0-9a-zA-Z])';
108              
109 4086         33839 my @sfields = split($sfreg, $subfields);
110 4086         5933 shift @sfields;
111              
112 4086         6062 my @subfields = ();
113 4086         4182 my ($value, $code);
114 4086         10101 while (@sfields) {
115 8410         12684 $code = shift @sfields;
116 8410         15132 $code = substr($code, 1);
117 8410         10882 $value = shift @sfields;
118 8410 50       16712 next unless defined $value;
119 8410 100       18019 $value =~ s/\$\$/\$/g if $sf eq '$';
120 8410         16540 $value =~ s/\s+/ /gm;
121 8410         25852 push(@subfields, ($code, $value));
122             }
123              
124 4086         10197 return $self->new($tagno, @subfields);
125             }
126              
127              
128             sub tag {
129 55848     55848 1 56305 my $self = shift;
130 55848         53565 my $tag = shift;
131              
132 55848 100       95218 if (defined $tag) {
133 2         9 my ($occurrence, $tagno) = parse_pp_tag($tag);
134 2 50       8 defined $tagno or croak( "\"$tag\" is not a valid tag." );
135              
136 2         12 $self->{_tag} = $tagno;
137 2         5 $self->{_occurrence} = $occurrence;
138             }
139              
140 55848 100       238011 return $self->{_tag} . ($self->{_occurrence} ? ("/" . $self->{_occurrence}) : "");
141             }
142              
143              
144             sub occurrence {
145 4     4 1 8 my $self = shift;
146 4         8 my $occurrence = shift;
147              
148 4 100       21 if (defined $occurrence) {
149 1 50 33     9 croak unless $occurrence >= 0 and $occurrence <= 99;
150 1         6 $self->{_occurrence} = sprintf("%02d", $occurrence);
151             }
152              
153 4         21 return $self->{_occurrence};
154             }
155              
156             # Shortcut
157             *occ = \&occurrence;
158              
159              
160             sub level {
161 0     0 1 0 my $self = shift;
162 0         0 return substr($self->{_tag},0,1);
163             }
164              
165              
166             sub subfield {
167 1023     1023 1 7205 my $self = shift;
168 1023         1347 my $codes = $_[0];
169 1023 100       2007 if (ref($codes) ne 'Regexp') {
170 1021         1494 $codes = join('',@_);
171 1021 100       1459 if ($codes eq '') {
172 2         7 $codes = qr/./;
173             } else {
174 1019         4478 $codes = qr/[$codes]/;
175             }
176             }
177              
178 1023         1196 my @list;
179 1023         965 my @data = @{$self->{_subfields}};
  1023         3809  
180              
181 1023         2327 for ( my $i=0; $i < @data; $i+=2 ) {
182 2146 100       9131 next unless $data[$i] =~ $codes;
183 1040         1505 my $value = $data[$i+1];
184 1040         1646 $value =~ s/\s+/ /gm;
185 1040 100       1647 if ( wantarray ) {
186 626         1718 push( @list, $value );
187             } else {
188 414         1983 return $value;
189             }
190             }
191              
192 609 100       1148 return $list[0] unless wantarray;
193 601         2395 return @list;
194             }
195              
196             # Shortcut
197             *sf = \&subfield;
198              
199              
200             sub content {
201 2     2 1 1040 my $self = shift;
202 2         6 my $codes = join('',@_);
203 2 50       7 $codes = $codes eq '' ? '.' : "[$codes]";
204 2         14 $codes = qr/$codes/;
205              
206 2         4 my @list;
207 2         1 my @data = @{$self->{_subfields}};
  2         8  
208              
209 2         7 for ( my $i=0; $i < @data; $i+=2 ) {
210 8 50       29 next unless $data[$i] =~ $codes;
211 8         29 push( @list, [ $data[$i], $data[$i+1] ] );
212             }
213              
214 2         11 return @list;
215             }
216              
217              
218             sub add {
219 4169     4169 1 5961 my $self = shift;
220 4169         6392 my $nfields = @_ / 2;
221              
222 4169 100       14112 ($nfields >= 1) or return 0;
223              
224 4167         7412 for my $i ( 1..$nfields ) {
225 8517         12832 my $offset = ($i-1)*2;
226 8517         13990 my $code = $_[$offset];
227 8517         13436 my $value = $_[$offset+1];
228 8517 100       17164 $value = defined $value ? "$value" : "";
229 8517         19408 $value =~ s/\s+/ /gm;
230              
231 8517 50       41623 croak( "Subfield code \"$code\" is not a valid subfield code" )
232             if !($code =~ $SUBFIELD_CODE_REGEXP);
233              
234 8517         10477 push( @{$self->{_subfields}}, $code, $value );
  8517         34451  
235             }
236              
237 4167         7537 return $nfields;
238             }
239              
240              
241             sub update {
242 12     12 1 34 my $self = shift;
243 12         15 my %values;
244             my @order;
245              
246             # collect values into a hash of array references
247 12         30 while( @_ ) {
248 17         22 my $c = shift;
249 17 50       73 croak( "Subfield code \"$c\" is not a valid subfield code" )
250             unless $c =~ $SUBFIELD_CODE_REGEXP;
251 17         22 my $v = shift;
252 17 100       37 if ( exists $values{$c} ) {
253 1 50       4 push @{$values{$c}}, (UNIVERSAL::isa($v,'ARRAY') ? @{$v} : $v);
  1         7  
  0         0  
254             } else {
255 16         19 push @order, $c;
256 16 100       106 $values{$c} = UNIVERSAL::isa($v,'ARRAY') ? $v : [ $v ];
257             }
258             }
259              
260 12         13 my @data;
261 12         15 my $changes = 0;
262              
263 12         13 while ( @{$self->{_subfields}} ) {
  37         84  
264 25         25 my $code = shift @{$self->{_subfields}};
  25         39  
265 25         24 my $value = shift @{$self->{_subfields}};
  25         38  
266              
267 25 100       47 if ( exists $values{$code} ) {
268 15 100       33 if ( defined $values{$code} ) {
269 13         12 my @vals = grep { defined $_ } @{$values{$code}};
  15         116  
  13         24  
270 13         18 push @data, map { $code => "$_" } @vals;
  14         42  
271 13         17 $changes += scalar @vals;
272 13         37 $values{$code} = undef;
273             }
274             # TODO: better count
275             } else {
276             # keep subfield unchanged
277 10         20 push @data, $code => $value;
278             }
279             }
280              
281             ## append new subfields in their order
282 12         18 foreach my $code ( @order ) {
283 16 100       48 next unless defined $values{$code};
284 3         5 my @vals = grep { defined $_ } @{$values{$code}};
  3         91  
  3         7  
285 3         4 $changes += scalar @vals;
286 3         7 push @data, map { $code => "$_" } @vals;
  2         9  
287             }
288              
289             ## synchronize our subfields
290 12         20 $self->{_subfields} = \@data;
291              
292 12         47 return $changes;
293             }
294              
295              
296             sub replace {
297 6     6 1 8 my $self = shift;
298 6         6 my $new;
299              
300 6 50 33     40 if (@_ and UNIVERSAL::isa($self,'PICA::Field')) {
301 6         9 $new = shift;
302             } else {
303 0         0 $new = PICA::Field->new(@_);
304             }
305              
306 6         45 %$self = %$new;
307             }
308              
309              
310             sub empty_subfields {
311 1     1 1 3 my $self = shift;
312              
313 1         3 my @list;
314 1         2 my @data = @{$self->{_subfields}};
  1         4  
315              
316 1         5 while ( defined( my $code = shift @data ) ) {
317 2 50       10 push (@list, $code) if shift @data eq "";
318             }
319              
320 1         6 return @list;
321             }
322              
323              
324             sub empty {
325 10703     10703 1 11620 my $self = shift;
326              
327 10703 100       9766 return 1 unless @{$self->{_subfields}};
  10703         24888  
328              
329 10700         10555 my @data = @{$self->{_subfields}};
  10700         30764  
330              
331 10700         24113 while ( defined( my $code = shift @data ) ) {
332 10703 100       44811 return 0 if shift @data ne "";
333             }
334              
335 2         16 return 1;
336             }
337              
338              
339             sub purged {
340 5     5 1 10 my $self = shift;
341              
342 5         9 my @subfields;
343             my $code;
344 5         8 foreach (@{$self->{_subfields}}) {
  5         16  
345 14 100       27 if (defined $code) {
346 7 100 66     42 push @subfields, ($code, $_) if defined $_ and $_ ne "";
347 7         15 undef $code;
348             } else {
349 7         14 $code = $_;
350             }
351             }
352              
353 5 100       26 return unless @subfields;
354              
355 3         19 my $copy = bless {
356             _tag => $self->{_tag},
357             _occurrence => $self->{_occurrence},
358             _subfields => \@subfields
359             }, ref($self);
360              
361 3         11 return $copy;
362             }
363              
364              
365             sub normalized {
366 15     15 1 903 my $self = shift;
367 15         19 my $subfields = shift;
368              
369 15         48 return $self->string(
370             subfields => $subfields,
371             startfield => $START_OF_FIELD,
372             endfield => $END_OF_FIELD,
373             startsubfield => $SUBFIELD_INDICATOR
374             );
375             }
376              
377              
378             sub sort {
379 2     2 1 11 my ($self, $order) = @_;
380 2 50       3 return unless @{$self->{_subfields}};
  2         8  
381 2 100       8 $order = "" unless defined $order;
382              
383 2         3 my (%pos,$i);
384 2         31 for (split('',$order.'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')) {
385 127 100       359 $pos{$_} = $i++ unless defined $pos{$_};
386             }
387              
388 2         14 my @sf = @{$self->{_subfields}};
  2         10  
389 2         7 my $n = @sf / 2 - 1;
390 2         3 my @sorted = ();
391              
392 11         26 @sorted = sort {
393 2         10 $pos{$sf[2*$a]} <=> $pos{$sf[2*$b]}
394             } (0..$n);
395              
396 2         4 $self->{_subfields} = [ map { $sf[2*$_] => $sf[2*$_+1] } @sorted ];
  10         43  
397             }
398              
399              
400             sub size {
401 2     2 1 4 my $self = shift;
402 2         4 return @{$self->{_subfields}} / 2;
  2         10  
403             }
404              
405              
406             sub string {
407 780     780 1 1131 my $self = shift;
408 780 100       2363 my (%args) = @_ ? @_ : ();
409              
410 780 100       1641 my $subfields = defined($args{subfields}) ? $args{subfields} : '';
411 780 100       1550 my $startfield = defined($args{startfield}) ? $args{startfield} : '';
412 780 100       1652 my $endfield = defined($args{endfield}) ? $args{endfield} : "\n";
413 780 100       1410 my $startsubfield = defined($args{startsubfield}) ? $args{startsubfield} : '$';
414              
415 780         832 my @subs;
416              
417 780         1240 my $subs = $self->{_subfields};
418 780         1199 my $nfields = @$subs / 2;
419              
420 780         1464 for my $i ( 1..$nfields ) {
421 1381         1945 my $offset = ($i-1)*2;
422 1381         1939 my $code = $subs->[$offset];
423 1381         2265 my $value = $subs->[$offset+1];
424 1381 100 66     3459 if (!$subfields || $code =~ /^[$subfields]$/) {
425 1379 100       3507 $value =~ s/\$/\$\$/g if $startsubfield eq '$';
426 1379         5027 push( @subs, $code.$value )
427             }
428             } # for
429              
430 780 100       1935 return "" unless @subs; # no subfields => no field
431              
432 777         1068 my $occ = '';
433 777 100       1939 $occ = "/" . $self->{_occurrence} if defined $self->{_occurrence};
434              
435 777         6146 return $startfield .
436             $self->{_tag} . $occ . ' ' .
437             $startsubfield . join( $startsubfield, @subs ) .
438             $endfield;
439             }
440              
441             # Write the field to a L object
442             my $write_xml = sub {
443             my ($self, $writer) = @_;
444              
445             my ($datafield, $subfield);
446              
447             if (UNIVERSAL::isa( $writer, 'XML::Writer::Namespaces' )) {
448             $datafield = [$PICA::Record::XMLNAMESPACE, 'datafield'];
449             $subfield = [$PICA::Record::XMLNAMESPACE, 'subfield'];
450             } else {
451             $datafield = 'datafield';
452             $subfield = 'subfield';
453             }
454              
455             my %attr = ('tag' => $self->{_tag});
456             $attr{occurrence} = $self->{_occurrence} if defined $self->{_occurrence};
457              
458             $writer->startTag( $datafield, %attr );
459              
460             my $subs = $self->{_subfields};
461             my $nfields = @$subs / 2;
462              
463             if ($nfields) {
464             for my $i ( 1..$nfields ) {
465             my $offset = ($i-1)*2;
466             $writer->startTag( $subfield, code => $subs->[$offset] );
467             $writer->characters( $subs->[$offset+1] );
468             $writer->endTag(); # subfield
469             }
470             }
471              
472             $writer->endTag(); # datafield
473              
474             $writer;
475             };
476              
477              
478             sub xml {
479 9     9 1 2885 my $self = shift;
480              
481 9         13 my %param;
482 9 100       49 if ( UNIVERSAL::isa( $_[0], 'XML::Writer' ) ) {
    100          
483 3         7 (%param) = ( writer => @_ );
484             } elsif ( ref($_[0]) ) {
485 1         4 (%param) = ( OUTPUT => @_ );
486             } else {
487 5         12 (%param) = @_;
488             }
489              
490 9 100       21 if ( defined $param{writer} ) {
491 3         8 $write_xml->( $self, $param{writer} );
492 3         9 return $param{writer};
493             } else {
494 6         51 my ($string, $sref);
495 6 100       19 if (not defined $param{OUTPUT}) {
496 5         7 $sref = \$string;
497 5         11 $param{OUTPUT} = $sref;
498             }
499              
500 6         32 my $writer = PICA::Writer::xmlwriter( %param );
501              
502 6         17 $write_xml->( $self, $writer );
503              
504 6 100       735 return defined $sref ? "$string" : $writer;
505             }
506             }
507              
508              
509             sub html {
510 0     0 1 0 my $self = shift;
511 0         0 my %options = @_;
512              
513             # CSS classes (TODO: customize)
514 0         0 my $field = 'field';
515 0         0 my $tag = 'tag';
516 0         0 my $tagcode = 'tagcode';
517 0         0 my $occurrence = 'occurrence';
518 0         0 my $sfcode = 'sfcode';
519 0         0 my $sfindicator = 'sfindicator';
520              
521 0         0 my $html = "
"
522             . "" . $self->{_tag} . "";
523 0 0       0 if (defined $self->{_occurrence}) {
524 0         0 $html .= "/"
525             . $self->{_occurrence} . "";
526             } else {
527             # TODO: in monospaced mode only
528             # $html .= "   ";
529             }
530 0         0 $html .= " "; # tag
531              
532 0         0 my $subs = $self->{_subfields};
533 0         0 my $nfields = @$subs / 2;
534 0 0       0 if ($nfields) {
535 0         0 for my $i ( 1..$nfields ) {
536 0         0 my $offset = ($i-1)*2;
537 0         0 my $code = $subs->[$offset];
538 0         0 my $text = $subs->[$offset+1];
539 0         0 $html .= "\$"
540             . "$code";
541 0         0 $text =~ s/&/&/g;
542 0         0 $text =~ s/
543 0         0 $html .= $text; # TODO: character encoding (?)
544             }
545             }
546 0         0 return $html . "\n";
547             }
548              
549              
550             sub parse_pp_tag {
551 4378     4378 1 7532 my $tag = shift;
552              
553 4378         12052 my ($tagno, $occurrence) = split ('/', $tag);
554 4378 100 100     43012 undef $tagno unless defined $tagno and $tagno =~ $FIELD_TAG_REGEXP;
555 4378 100 66     27175 undef $occurrence unless defined $occurrence and $occurrence =~ $FIELD_OCCURRENCE_REGEXP;
556              
557 4378         10840 return ($occurrence, $tagno);
558             }
559              
560             1;
561              
562             __END__