/[webpac2]/trunk/lib/WebPAC/Normalize/MARC.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/lib/WebPAC/Normalize/MARC.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1024 by dpavlin, Sat Nov 10 11:37:47 2007 UTC revision 1047 by dpavlin, Mon Nov 19 15:56:05 2007 UTC
# Line 1  Line 1 
1  package WebPAC::Normalize::MARC;  package WebPAC::Normalize::MARC;
2  use Exporter 'import';  use Exporter 'import';
3  @EXPORT = qw/  our @EXPORT = qw/
4            marc marc_indicators marc_repeatable_subfield
5            marc_compose marc_leader marc_fixed
6            marc_duplicate marc_remove marc_count
7            marc_original_order
8          marc_template          marc_template
9  /;  /;
10    
11  use strict;  use strict;
12  use warnings;  use warnings;
13    
14    use Storable qw/dclone/;
15  use Data::Dump qw/dump/;  use Data::Dump qw/dump/;
16  use Carp qw/confess/;  use Carp qw/confess/;
17    
18  use WebPAC::Normalize;  use WebPAC::Normalize;
19    
20    our $debug = 0;
21    
22    my ($marc_record, $marc_encoding, $marc_repeatable_subfield, $marc_indicators, $marc_leader);
23    my ($marc_record_offset, $marc_fetch_offset) = (0, 0);
24    
25    our $rec;
26    
27  =head1 NAME  =head1 NAME
28    
29  WebPAC::Normalize::MARC - create MARC/ISO2709 records  WebPAC::Normalize::MARC - create MARC/ISO2709 records
# Line 42  WebPAC::Normalize::MARC - create MARC/IS Line 54  WebPAC::Normalize::MARC - create MARC/IS
54                  ],                  ],
55          );          );
56    
57    Returns number of records produced.
58    
59  =cut  =cut
60    
61    my $created_with_marc_template;
62    
63  sub marc_template {  sub marc_template {
64          my $args = {@_};          my $args = {@_};
65          warn "## marc_template(",dump($args),")";          warn "## marc_template(",dump($args),")",$/ if $debug;
66    
67          foreach ( qw/subfields_rename isis_template marc_template/ ) {          foreach ( qw/subfields_rename isis_template marc_template/ ) {
68  #               warn "ref($_) = ",ref($args->{$_});  #               warn "ref($_) = ",ref($args->{$_})  if $debug;
69                  die "$_ not ARRAY" if defined($args->{$_}) && ref($args->{$_}) ne 'ARRAY';                  die "$_ not ARRAY" if defined($args->{$_}) && ref($args->{$_}) ne 'ARRAY';
70          }          }
71    
# Line 61  sub marc_template { Line 77  sub marc_template {
77          die "record field ", $args->{from}, " isn't array ",dump( $rec ) unless (ref($r) eq 'ARRAY');          die "record field ", $args->{from}, " isn't array ",dump( $rec ) unless (ref($r) eq 'ARRAY');
78    
79          my @subfields_rename = @{ $args->{subfields_rename} };          my @subfields_rename = @{ $args->{subfields_rename} };
80  #       warn "### subfields_rename [$#subfields_rename] = ",dump( @subfields_rename );  #       warn "### subfields_rename [$#subfields_rename] = ",dump( @subfields_rename )  if $debug;
81    
82          confess "need mapping in pairs for subfields_rename"          confess "need mapping in pairs for subfields_rename"
83                  if $#subfields_rename % 2 != 1;                  if $#subfields_rename % 2 != 1;
84                    
85          my ( $subfields_rename, $from_subfields, $to_subfields );          my ( $subfields_rename, $from_subfields );
86            our $to_subfields = {};
87          while ( my ( $from, $to ) = splice(@subfields_rename, 0, 2) ) {          while ( my ( $from, $to ) = splice(@subfields_rename, 0, 2) ) {
88                  my ( $f, $t ) = (                  my ( $f, $t ) = (
89                          $from_subfields->{ $from }++,                          $from_subfields->{ $from }++,
# Line 74  sub marc_template { Line 91  sub marc_template {
91                  );                  );
92                  $subfields_rename->{ $from }->[ $f ] = [ $to => $t ];                  $subfields_rename->{ $from }->[ $f ] = [ $to => $t ];
93          }          }
94          warn "### subfields_rename = ",dump( $subfields_rename ),$/;          warn "### subfields_rename = ",dump( $subfields_rename ),$/ if $debug;
95          warn "### from_subfields = ", dump( $from_subfields ),$/;          warn "### from_subfields = ", dump( $from_subfields ),$/ if $debug;
96          warn "### to_subfields = ", dump( $to_subfields ),$/;          warn "### to_subfields = ", dump( $to_subfields ),$/ if $debug;
97    
98            our $_template;
99    
100          my $fields_re = join('|', keys %$to_subfields );          $_template->{isis}->{fields_re} = join('|', keys %$from_subfields );
101            $_template->{marc}->{fields_re} = join('|', keys %$to_subfields );
102    
103          my @marc_out;          my @marc_out;
           
         our $_template;  
104    
105          sub _parse_template {          sub _parse_template {
106                  my ( $name, $templates ) = @_;                  my ( $name, $templates ) = @_;
107    
108                    my $fields_re = $_template->{$name}->{fields_re} || die "can't find $name in ",dump( $_template->{$name}->{fields_re} );
109    
110                  foreach my $template ( @{ $templates } ) {                  foreach my $template ( @{ $templates } ) {
111                          our $count = {};                          our $count = {};
112                          our @marc_order = ();                          our @order = ();
113                          sub my_count {                          sub my_count {
114                                  my $sf = shift;                                  my $sf = shift;
115                                  my $nr = $count->{$sf}++;                                  my $nr = $count->{$sf}++;
116                                  push @marc_order, [ $sf, $nr ];                                  push @order, [ $sf, $nr ];
117                                  return $sf . $nr;                                  return $sf . $nr;
118                          }                          }
119                          my $pos_template = $template;                          my $pos_template = $template;
120                          $pos_template =~ s/($fields_re)/my_count($1)/ge;                          $pos_template =~ s/($fields_re)/my_count($1)/ge;
121                          my $count_key = dump( $count );                          my $count_key = dump( $count );
122                          warn "### template: |$template| -> |$pos_template| count = $count_key marc_order = ",dump( @marc_order ),$/;                          warn "### template: |$template| -> |$pos_template| count = $count_key order = ",dump( @order ),$/ if $debug;
123                          $_template->{$name}->{pos}->{ $count_key } = $pos_template;                          $_template->{$name}->{pos}->{ $count_key } = $pos_template;
124                          $_template->{$name}->{order}->{ $pos_template } = [ @marc_order ];                          $_template->{$name}->{order}->{ $pos_template } = [ @order ];
125                  }                  }
126                  warn "### from ",dump( $templates ), " created ", dump( $_template );                  warn "### from ",dump( $templates ), " using $fields_re created ", dump( $_template ),$/ if $debug;
127          }          }
128    
129          _parse_template( 'marc', $args->{marc_template} );          _parse_template( 'marc', $args->{marc_template} );
130          _parse_template( 'isis', $args->{isis_template} );          _parse_template( 'isis', $args->{isis_template} );
131          warn "### _template = ",dump( $_template );          warn "### _template = ",dump( $_template ),$/ if $debug;
132    
133          my $m;          my $m;
134    
135          foreach my $r ( @{ $rec->{ $args->{from} } } ) {          our $from_rec = $rec->{ $args->{from} };
136    
137            foreach my $r ( @$from_rec ) {
138    
139                    my $to = $args->{to};
140                    my ($i1,$i2) = _get_marc_indicators( $to );
141                    $m = [ $to, $i1, $i2 ];
142    
143                  my $i1 = $r->{i1} || ' ';                  $created_with_marc_template->{ $to }++;
                 my $i2 = $r->{i2} || ' ';  
                 $m = [ $args->{to}, $i1, $i2 ];  
144    
145                  warn "### r = ",dump( $r );                  warn "### r = ",dump( $r ),$/ if $debug;
146    
147                  my ( $new_r, $from_count, $to_count );                  my ( $from_mapping, $from_count, $to_count );
148                  foreach my $sf ( keys %{$r} ) {                  our $to_mapping;
149                    foreach my $from_sf ( keys %{$r} ) {
150                          # skip everything which isn't one char subfield (e.g. 'subfields')                          # skip everything which isn't one char subfield (e.g. 'subfields')
151                          next unless $sf =~ m/^\w$/;                          next unless $from_sf =~ m/^\w$/;
152                          my $nr = $from_count->{$sf}++;                          my $from_nr = $from_count->{$from_sf}++;
153                          my $rename_to = $subfields_rename->{ $sf } ||                          my $rename_to = $subfields_rename->{ $from_sf } ||
154                                  die "can't find subfield rename for $sf/$nr in ", dump( $subfields_rename );                                  die "can't find subfield rename for $from_sf/$from_nr in ", dump( $subfields_rename );
155                          warn "### rename $sf/$nr to ", dump( $rename_to->[$nr] ), $/;                          my ( $to_sf, $to_nr ) = @{ $rename_to->[$from_nr] };
156                          my ( $to_sf, $to_nr ) = @{ $rename_to->[$nr] };                          $to_mapping->{ $to_sf }->[ $to_nr ] = [ $from_sf => $from_nr ];
                         $new_r->{ $to_sf }->[ $to_nr ] = [ $sf => $nr ];  
157    
158                          $to_count->{ $to_sf }++;                          my $to_nr2 = $to_count->{ $to_sf }++;
159                            $from_mapping->{ $from_sf }->[ $from_nr ] = [ $to_sf => $to_nr2 ];
160    
161                            warn "### from $from_sf/$from_nr -> $to_sf/$to_nr\tto $from_sf/$from_nr -> $to_sf/$to_nr2\n" if $debug;
162                  }                  }
163    
164                  warn "### new_r = ",dump( $new_r );                  warn "### from_mapping = ",dump( $from_mapping ), "\n### to_mapping = ",dump( $to_mapping ),$/ if $debug;
165    
166                  my $from_count_key = dump( $to_count );                  my $count_key = {
167                            from => dump( $from_count ),
168                            to   => dump( $to_count),
169                    };
170    
171                    warn "### count_key = ",dump( $count_key ),$/ if $debug;
172    
173                    my $processed_templates = 0;
174    
175                    # this defines order of traversal
176                    foreach ( qw/isis:from marc:to/ ) {
177                            my ($name,$count_name) = split(/:/);
178    
179                            my $ckey = $count_key->{$count_name} || die "can't find count_key $count_name in ",dump( $count_key );
180    
181                            my $template = $_template->{$name}->{pos}->{ $ckey } || next;
182                            $processed_templates++;
183    
184                            warn "### traverse $name $count_name selected template: |$template|\n" if $debug;
185    
186                            our $fill_in = {};
187    
188                            my @templates = split(/\|/, $template );
189                            @templates = ( $template ) unless @templates;
190    
191                            warn "### templates = ",dump( @templates ),$/ if $debug;
192    
193                            foreach my $sf ( @templates ) {
194                                    sub fill_in {
195                                            my ( $name, $r, $pre, $sf, $nr, $post ) = @_;
196                                            warn "#### fill_in( $name, r, '$pre', $sf, $nr, '$post' )\n" if $debug;
197                                            my ( $from_sf, $from_nr );
198                                            if ( $name eq 'marc' ) {
199                                                    die "no $sf/$nr in to_mapping: ",dump( $to_mapping ), "\n>>>> from record ",dump( $r ), "\n>>>> full record = ",dump( $from_rec ) unless defined $to_mapping->{$sf}->[$nr];
200                                                    ( $from_sf, $from_nr ) = @{ $to_mapping->{$sf}->[$nr] };
201                                            } else {
202                                                    ( $from_sf, $from_nr ) = ( $sf, $nr );
203                                            }
204                                            my $v = $r->{ $from_sf }; # || die "no $from_sf/$from_nr";
205                                            if ( ref( $v ) eq 'ARRAY' ) {
206                                                    $v = $pre . $v->[$from_nr] . $post;
207                                            } elsif ( $from_nr == 0 ) {
208                                                    $v = $pre . $v . $post;
209                                            } else {
210                                                    die "requested subfield $from_sf/$from_nr but it's ",dump( $v );
211                                            }
212                                            warn "#### fill_in( $sf, $nr ) = $from_sf/$from_nr >>>> ",dump( $v ),$/ if $debug;
213                                            $fill_in->{$sf}->[$nr] = $v;
214                                            return $v;
215                                    }
216                                    my $fields_re = $_template->{$name}->{fields_re} || die "can't find $name in ",dump( $_template->{$name}->{fields_re} );
217                                    warn "#### $sf <<<< $fields_re\n" if $debug;
218                                    $sf =~ s/^(.*?)($fields_re)(\d+)(.*?)$/fill_in($name,$r,$1,$2,$3,$4)/ge;
219                                    warn "#### >>>> $sf with fill_in = ",dump( $fill_in ),$/ if $debug;
220                            }
221    
222                  warn "### from_count = ",dump( $from_count ), $/;                          warn "## template: |$template|\n## _template->$name = ",dump( $_template->{$name} ),$/ if $debug;
                 warn "### to_count   = ",dump( $to_count ), $/;  
223    
224                  my $template = $_template->{marc}->{pos}->{ $from_count_key } ||                          foreach my $sf ( @{ $_template->{$name}->{order}->{$template} } ) {
225                          die "I don't have template for:\n$from_count_key\n## available templates\n", dump( $_template );                                  my ( $sf, $nr ) = @$sf;
226                                    my $v = $fill_in->{$sf}->[$nr] || die "can't find fill_in $sf/$nr";
227                                    if ( $name eq 'isis') {
228                                            ( $sf, $nr ) = @{ $from_mapping->{$sf}->[$nr] };
229                                    }
230                                    warn "++ $sf/$nr |$v|\n" if $debug;
231                                    push @$m, ( $sf, $v );
232                            }
233    
234                  warn "### selected template: |$template|\n";                          warn "#### >>>> created MARC record: ", dump( $m ),$/ if $debug;
235    
236                  our $fill_in = {};                          push @marc_out, $m;
237    
238                  my @templates = split(/\|/, $template );                          last;
239                  @templates = ( $template );                  }
240            
241                    die "I don't have template for fields ",dump( $count_key ), "\n## available templates\n", dump( $_template ) unless $processed_templates;
242                    warn ">>> $processed_templates templates applied to data\n",$/ if $debug;
243            }
244    
245                  foreach my $sf ( @templates ) {  
246                          sub fill_in {          my $recs = 0;
247                                  my ( $r, $sf, $nr ) = @_;  
248                                  my ( $from_sf, $from_nr ) = @{ $new_r->{ $sf }->[ $nr ] };          foreach my $marc ( @marc_out ) {
249                                  my $v = $r->{ $from_sf }; # || die "no $from_sf/$from_nr";                  warn "+++ ",dump( $marc ),$/ if $debug;
250                                  warn "#### fill_in( $sf, $nr ) = $from_sf/$from_nr >>>> ",dump( $v ), $/;                  _marc_push( $marc );
251                                  if ( ref( $v ) eq 'ARRAY' ) {                  $recs++;
252                                          $fill_in->{$sf}->[$nr] = $v->[$from_nr];          }
253                                          return $v->[$from_nr];  
254                                  } elsif ( $from_nr == 0 ) {          warn "### marc_template produced $recs MARC records: ",dump( @marc_out ),$/ if $debug;
255                                          $fill_in->{$sf}->[$nr] = $v;  
256                                          return $v;          return $recs;
257    }
258    
259    =head2 marc_leader
260    
261    Setup fields within MARC leader or get leader
262    
263      marc_leader('05','c');
264      my $leader = marc_leader();
265    
266    =cut
267    
268    sub marc_leader {
269            my ($offset,$value) = @_;
270    
271            if ($offset) {
272                    $marc_leader->[ $marc_record_offset ]->{ $offset } = $value;
273            } else {
274                    
275                    if (defined($marc_leader)) {
276                            die "marc_leader not array = ", dump( $marc_leader ) unless (ref($marc_leader) eq 'ARRAY');
277                            return $marc_leader->[ $marc_record_offset ];
278                    } else {
279                            return;
280                    }
281            }
282    }
283    
284    =head2 marc_fixed
285    
286    Create control/indentifier fields with values in fixed positions
287    
288      marc_fixed('008', 00, '070402');
289      marc_fixed('008', 39, '|');
290    
291    Positions not specified will be filled with spaces (C<0x20>).
292    
293    There will be no effort to extend last specified value to full length of
294    field in standard.
295    
296    =cut
297    
298    sub marc_fixed {
299            my ($f, $pos, $val) = @_;
300            die "need marc(field, position, value)" unless defined($f) && defined($pos);
301    
302            confess "need val" unless defined $val;
303    
304            my $update = 0;
305    
306            map {
307                    if ($_->[0] eq $f) {
308                            my $old = $_->[1];
309                            if (length($old) <= $pos) {
310                                    $_->[1] .= ' ' x ( $pos - length($old) ) . $val;
311                                    warn "## marc_fixed($f,$pos,'$val') append '$old' -> '$_->[1]'\n" if ($debug > 1);
312                            } else {
313                                    $_->[1] = substr($old, 0, $pos) . $val . substr($old, $pos + length($val));
314                                    warn "## marc_fixed($f,$pos,'$val') update '$old' -> '$_->[1]'\n" if ($debug > 1);
315                            }
316                            $update++;
317                    }
318            } @{ $marc_record->[ $marc_record_offset ] };
319    
320            if (! $update) {
321                    my $v = ' ' x $pos . $val;
322                    push @{ $marc_record->[ $marc_record_offset ] }, [ $f, $v ];
323                    warn "## marc_fixed($f,$pos,'val') created '$v'\n" if ($debug > 1);
324            }
325    }
326    
327    =head2 marc
328    
329    Save value for MARC field
330    
331      marc('900','a', rec('200','a') );
332      marc('001', rec('000') );
333    
334    =cut
335    
336    sub marc {
337            my $f = shift or die "marc needs field";
338            die "marc field must be numer" unless ($f =~ /^\d+$/);
339    
340            my $sf;
341            if ($f >= 10) {
342                    $sf = shift or die "marc needs subfield";
343            }
344    
345            foreach (@_) {
346                    my $v = $_;             # make var read-write for Encode
347                    next unless (defined($v) && $v !~ /^\s*$/);
348                    my ($i1,$i2) = _get_marc_indicators( $f );
349                    if (defined $sf) {
350                            push @{ $marc_record->[ $marc_record_offset ] }, [ $f, $i1, $i2, $sf => $v ];
351                    } else {
352                            push @{ $marc_record->[ $marc_record_offset ] }, [ $f, $v ];
353                    }
354            }
355    }
356    
357    =head2 marc_repeatable_subfield
358    
359    Save values for MARC repetable subfield
360    
361      marc_repeatable_subfield('910', 'z', rec('909') );
362    
363    =cut
364    
365    sub marc_repeatable_subfield {
366            my ($f,$sf) = @_;
367            die "marc_repeatable_subfield need field and subfield!\n" unless ($f && $sf);
368            $marc_repeatable_subfield->{ $f . $sf }++;
369            marc(@_);
370    }
371    
372    =head2 marc_indicators
373    
374    Set both indicators for MARC field
375    
376      marc_indicators('900', ' ', 1);
377    
378    Any indicator value other than C<0-9> will be treated as undefined.
379    
380    =cut
381    
382    sub marc_indicators {
383            my $f = shift || die "marc_indicators need field!\n";
384            my ($i1,$i2) = @_;
385            die "marc_indicators($f, ...) need i1!\n" unless(defined($i1));
386            die "marc_indicators($f, $i1, ...) need i2!\n" unless(defined($i2));
387    
388            $i1 = ' ' if ($i1 !~ /^\d$/);
389            $i2 = ' ' if ($i2 !~ /^\d$/);
390            @{ $marc_indicators->{$f} } = ($i1,$i2);
391    }
392    
393    sub _get_marc_indicators {
394            my $f = shift || confess "need field!\n";
395            return defined($marc_indicators->{$f}) ? @{ $marc_indicators->{$f} } : (' ',' ');
396    }
397    
398    =head2 marc_compose
399    
400    Save values for each MARC subfield explicitly
401    
402      marc_compose('900',
403            'a', rec('200','a')
404            'b', rec('201','a')
405            'a', rec('200','b')
406            'c', rec('200','c')
407      );
408    
409    If you specify C<+> for subfield, value will be appended
410    to previous defined subfield.
411    
412    =cut
413    
414    sub marc_compose {
415            my $f = shift or die "marc_compose needs field";
416            die "marc_compose field must be numer" unless ($f =~ /^\d+$/);
417    
418            my ($i1,$i2) = _get_marc_indicators( $f );
419            my $m = [ $f, $i1, $i2 ];
420    
421            warn "### marc_compose input subfields = ", dump(@_),$/ if ($debug > 2);
422    
423            if ($#_ % 2 != 1) {
424                    die "ERROR: marc_compose",dump($f,@_)," not valid (must be even).\nDo you need to add first() or join() around some argument?\n";
425            }
426    
427            while (@_) {
428                    my $sf = shift;
429                    my $v = shift;
430    
431                    next unless (defined($v) && $v !~ /^\s*$/);
432                    warn "## ++ marc_compose($f,$sf,$v) ", dump( $m ),$/ if ($debug > 1);
433                    if ($sf ne '+') {
434                            push @$m, ( $sf, $v );
435                    } else {
436                            $m->[ $#$m ] .= $v;
437                    }
438            }
439    
440            warn "## marc_compose current marc = ", dump( $m ),$/ if ($debug > 1);
441    
442            push @{ $marc_record->[ $marc_record_offset ] }, $m if ($#{$m} > 2);
443    }
444    
445    =head2 marc_duplicate
446    
447    Generate copy of current MARC record and continue working on copy
448    
449      marc_duplicate();
450    
451    Copies can be accessed using C<< _get_marc_fields( fetch_next => 1 ) >> or
452    C<< _get_marc_fields( offset => 42 ) >>.
453    
454    =cut
455    
456    sub marc_duplicate {
457             my $m = $marc_record->[ -1 ];
458             die "can't duplicate record which isn't defined" unless ($m);
459             push @{ $marc_record }, dclone( $m );
460             push @{ $marc_leader }, dclone( marc_leader() );
461             warn "## marc_duplicate = ", dump(@$marc_leader, @$marc_record), $/ if ($debug > 1);
462             $marc_record_offset = $#{ $marc_record };
463             warn "## marc_record_offset = $marc_record_offset", $/ if ($debug > 1);
464    
465    }
466    
467    =head2 marc_remove
468    
469    Remove some field or subfield from MARC record.
470    
471      marc_remove('200');
472      marc_remove('200','a');
473    
474    This will erase field C<200> or C<200^a> from current MARC record.
475    
476      marc_remove('*');
477    
478    Will remove all fields in current MARC record.
479    
480    This is useful after calling C<marc_duplicate> or on it's own (but, you
481    should probably just remove that subfield definition if you are not
482    using C<marc_duplicate>).
483    
484    FIXME: support fields < 10.
485    
486    =cut
487    
488    sub marc_remove {
489            my ($f, $sf) = @_;
490    
491            die "marc_remove needs record number" unless defined($f);
492    
493            my $marc = $marc_record->[ $marc_record_offset ];
494    
495            warn "### marc_remove before = ", dump( $marc ), $/ if ($debug > 2);
496    
497            if ($f eq '*') {
498    
499                    delete( $marc_record->[ $marc_record_offset ] );
500                    warn "## full marc_record = ", dump( @{ $marc_record }), $/ if ($debug > 1);
501    
502            } else {
503    
504                    my $i = 0;
505                    foreach ( 0 .. $#{ $marc } ) {
506                            last unless (defined $marc->[$i]);
507                            warn "#### working on ",dump( @{ $marc->[$i] }), $/ if ($debug > 3);
508                            if ($marc->[$i]->[0] eq $f) {
509                                    if (! defined $sf) {
510                                            # remove whole field
511                                            splice @$marc, $i, 1;
512                                            warn "#### slice \@\$marc, $i, 1 = ",dump( @{ $marc }), $/ if ($debug > 3);
513                                            $i--;
514                                  } else {                                  } else {
515                                          die "requested subfield $from_sf/$from_nr but it's ",dump( $v );                                          foreach my $j ( 0 .. (( $#{ $marc->[$i] } - 3 ) / 2) ) {
516                                                    my $o = ($j * 2) + 3;
517                                                    if ($marc->[$i]->[$o] eq $sf) {
518                                                            # remove subfield
519                                                            splice @{$marc->[$i]}, $o, 2;
520                                                            warn "#### slice \@{\$marc->[$i]}, $o, 2 = ", dump( @{ $marc }), $/ if ($debug > 3);
521                                                            # is record now empty?
522                                                            if ($#{ $marc->[$i] } == 2) {
523                                                                    splice @$marc, $i, 1;
524                                                                    warn "#### slice \@\$marc, $i, 1 = ", dump( @{ $marc }), $/ if ($debug > 3);
525                                                                    $i--;
526                                                            };
527                                                    }
528                                            }
529                                  }                                  }
530                          }                          }
531                          warn "#### $sf <<<< $fields_re\n";                          $i++;
                         $sf =~ s/($fields_re)(\d+)/fill_in($r,$1,$2)/ge;  
                         warn "#### >>>> $sf with fill_in = ",dump( $fill_in ),$/;  
532                  }                  }
533    
534                  warn "## template: |$template|\n## _template->marc = ",dump( $_template->{marc} );                  warn "### marc_remove($f", $sf ? ",$sf" : "", ") after = ", dump( $marc ), $/ if ($debug > 2);
535    
536                  foreach my $sf ( @{ $_template->{marc}->{order}->{$template} } ) {                  $marc_record->[ $marc_record_offset ] = $marc;
537                          my ( $sf, $nr ) = @$sf;          }
538                          my $v = $fill_in->{$sf}->[$nr] || die "can't find fill_in $sf/$nr";  
539                          warn "++ $sf/$nr |$v|\n";          warn "## full marc_record = ", dump( @{ $marc_record }), $/ if ($debug > 1);
540                          push @$m, ( $sf, $v );  }
541    
542    =head2 marc_original_order
543    
544    Copy all subfields preserving original order to marc field.
545    
546      marc_original_order( marc_field_number, original_input_field_number );
547    
548    Please note that field numbers are consistent with other commands (marc
549    field number first), but somewhat counter-intuitive (destination and then
550    source).
551    
552    You might want to use this command if you are just renaming subfields or
553    using pre-processing modify_record in C<config.yml> and don't need any
554    post-processing or want to preserve order of original subfields.
555    
556    
557    =cut
558    
559    sub marc_original_order {
560    
561            my ($to, $from) = @_;
562            die "marc_original_order needs from and to fields\n" unless ($from && $to);
563    
564            return unless defined($rec->{$from});
565    
566            my $r = $rec->{$from};
567            die "record field $from isn't array ",dump( $rec ) unless (ref($r) eq 'ARRAY');
568    
569            my ($i1,$i2) = _get_marc_indicators( $to );
570            warn "## marc_original_order($to,$from) source = ", dump( $r ),$/ if ($debug > 1);
571    
572            foreach my $d (@$r) {
573    
574                    if (! defined($d->{subfields}) && ref($d->{subfields}) ne 'ARRAY') {
575                            warn "# marc_original_order($to,$from): field $from doesn't have subfields specification\n";
576                            next;
577                  }                  }
578            
579                    my @sfs = @{ $d->{subfields} };
580    
581                  warn "#### >>>> created marc: ", dump( $m );                  die "field $from doesn't have even number of subfields specifications\n" unless($#sfs % 2 == 1);
582    
583                    warn "#--> d: ",dump($d), "\n#--> sfs: ",dump(@sfs),$/ if ($debug > 2);
584    
585                    my $m = [ $to, $i1, $i2 ];
586    
587                    while (my $sf = shift @sfs) {
588    
589                            warn "#--> sf: ",dump($sf), $/ if ($debug > 2);
590                            my $offset = shift @sfs;
591                            die "corrupted sufields specification for field $from\n" unless defined($offset);
592    
593                            my $v;
594                            if (ref($d->{$sf}) eq 'ARRAY') {
595                                    $v = $d->{$sf}->[$offset] if (defined($d->{$sf}->[$offset]));
596                            } elsif ($offset == 0) {
597                                    $v = $d->{$sf};
598                            } else {
599                                    die "field $from subfield '$sf' need occurence $offset which doesn't exist", dump($d->{$sf});
600                            }
601                            push @$m, ( $sf, $v ) if (defined($v));
602                    }
603    
604                  push @marc_out, $m;                  if ($#{$m} > 2) {
605                            push @{ $marc_record->[ $marc_record_offset ] }, $m;
606                    }
607          }          }
608    
609          warn "### marc_template produced: ",dump( @marc_out );          warn "## marc_record = ", dump( $marc_record ),$/ if ($debug > 1);
610    }
611    
612    
613          foreach my $marc ( @marc_out ) {  =head2 marc_count
614                  warn "+++ ",dump( $marc );  
615                  WebPAC::Normalize::_marc_push( $marc );  Return number of MARC records created using L</marc_duplicate>.
616    
617      print "created ", marc_count(), " records";
618    
619    =cut
620    
621    sub marc_count {
622            return $#{ $marc_record };
623    }
624    
625    =head1 PRIVATE FUNCTIONS
626    
627    =head2 _marc_push
628    
629     _marc_push( $marc );
630    
631    =cut
632    
633    sub _marc_push {
634            my $marc = shift || die "no marc?";
635            push @{ $marc_record->[ $marc_record_offset ] }, $marc;
636    }
637    
638    =head2 _clean
639    
640    Clean internal structures
641    
642    =cut
643    
644    sub _clean {
645            ($marc_record, $marc_encoding, $marc_repeatable_subfield, $marc_indicators, $marc_leader, $created_with_marc_template) = ();
646            ($marc_record_offset, $marc_fetch_offset) = (0,0);
647    }
648    
649    
650    =head2 _get_marc_fields
651    
652    Get all fields defined by calls to C<marc>
653    
654            $marc->add_fields( WebPAC::Normalize:_get_marc_fields() );
655    
656    We are using I<magic> which detect repeatable fields only from
657    sequence of field/subfield data generated by normalization.
658    
659    This magic is disabled for all records created with C<marc_template>.
660    
661    Repeatable field is created when there is second occurence of same subfield or
662    if any of indicators are different.
663    
664    This is sane for most cases. Something like:
665    
666      900a-1 900b-1 900c-1
667      900a-2 900b-2
668      900a-3
669    
670    will be created from any combination of:
671    
672      900a-1 900a-2 900a-3 900b-1 900b-2 900c-1
673    
674    and following rules:
675    
676      marc('900','a', rec('200','a') );
677      marc('900','b', rec('200','b') );
678      marc('900','c', rec('200','c') );
679    
680    which might not be what you have in mind. If you need repeatable subfield,
681    define it using C<marc_repeatable_subfield> like this:
682    
683      marc_repeatable_subfield('900','a');
684      marc('900','a', rec('200','a') );
685      marc('900','b', rec('200','b') );
686      marc('900','c', rec('200','c') );
687    
688    will create:
689    
690      900a-1 900a-2 900a-3 900b-1 900c-1
691      900b-2
692    
693    There is also support for returning next or specific using:
694    
695      while (my $mf = WebPAC::Normalize:_get_marc_fields( fetch_next => 1 ) ) {
696            # do something with $mf
697      }
698    
699    will always return fields from next MARC record or
700    
701      my $mf = WebPAC::Normalize::_get_marc_fields( offset => 42 );
702    
703    will return 42th copy record (if it exists).
704    
705    =cut
706    
707    my $fetch_pos;
708    
709    sub _get_marc_fields {
710    
711            my $arg = {@_};
712            warn "### _get_marc_fields arg: ", dump($arg), $/ if ($debug > 2);
713            $fetch_pos = $marc_fetch_offset;
714            if ($arg->{offset}) {
715                    $fetch_pos = $arg->{offset};
716            } elsif($arg->{fetch_next}) {
717                    $marc_fetch_offset++;
718          }          }
719    
720            return if (! $marc_record || ref($marc_record) ne 'ARRAY');
721    
722            warn "### full marc_record = ", dump( @{ $marc_record }), $/ if ($debug > 2);
723    
724            my $marc_rec = $marc_record->[ $fetch_pos ];
725    
726            warn "## _get_marc_fields (at offset: $fetch_pos) -- marc_record = ", dump( @$marc_rec ), $/ if ($debug > 1);
727    
728            return if (! $marc_rec || ref($marc_rec) ne 'ARRAY' || $#{ $marc_rec } < 0);
729    
730            # first, sort all existing fields
731            # XXX might not be needed, but modern perl might randomize elements in hash
732    #       my @sorted_marc_record = sort {
733    #               $a->[0] . ( $a->[3] || '' ) cmp $b->[0] . ( $b->[3] || '')
734    #       } @{ $marc_rec };
735    
736            my @sorted_marc_record = @{ $marc_rec };        ### FIXME disable sorting
737            
738            # output marc fields
739            my @m;
740    
741            # count unique field-subfields (used for offset when walking to next subfield)
742            my $u;
743            map { $u->{ $_->[0] . ( $_->[3] || '')  }++ } @sorted_marc_record;
744    
745            if ($debug) {
746                    warn "## marc_repeatable_subfield = ", dump( $marc_repeatable_subfield ), $/ if ( $marc_repeatable_subfield );
747                    warn "## marc_record[$fetch_pos] = ", dump( $marc_rec ), $/;
748                    warn "## sorted_marc_record = ", dump( \@sorted_marc_record ), $/;
749                    warn "## subfield count = ", dump( $u ), $/;
750            }
751    
752            my $len = $#sorted_marc_record;
753            my $visited;
754            my $i = 0;
755            my $field;
756    
757            warn "## created_with_marc_template = ",dump( $created_with_marc_template ) if $debug;
758    
759            foreach ( 0 .. $len ) {
760    
761                    # find next element which isn't visited
762                    while ($visited->{$i}) {
763                            $i = ($i + 1) % ($len + 1);
764                    }
765    
766                    # mark it visited
767                    $visited->{$i}++;
768    
769                    my $row = dclone( $sorted_marc_record[$i] );
770    
771                    if ( $created_with_marc_template->{ $row->[0] } ) {
772                            push @m, $row;
773                            warn "## copied marc_template created ", dump( $row ),$/ if $debug;
774                            next;
775                    }
776    
777                    # field and subfield which is key for
778                    # marc_repeatable_subfield and u
779                    my $fsf = $row->[0] . ( $row->[3] || '' );
780    
781                    if ($debug > 1) {
782    
783                            print "### field so far [", $#$field, "] : ", dump( $field ), " ", $field ? 'T' : 'F', $/;
784                            print "### this [$i]: ", dump( $row ),$/;
785                            print "### sf: ", $row->[3], " vs ", $field->[3],
786                                    $marc_repeatable_subfield->{ $row->[0] . $row->[3] } ? ' (repeatable)' : '', $/,
787                                    if ($#$field >= 0);
788    
789                    }
790    
791                    # if field exists
792                    if ( $#$field >= 0 ) {
793                            if (
794                                    $row->[0] ne $field->[0] ||             # field
795                                    $row->[1] ne $field->[1] ||             # i1
796                                    $row->[2] ne $field->[2]                # i2
797                            ) {
798                                    push @m, $field;
799                                    warn "## saved/1 ", dump( $field ),$/ if ($debug);
800                                    $field = $row;
801    
802                            } elsif (
803                                    ( $row->[3] lt $field->[-2] )           # subfield which is not next (e.g. a after c)
804                                    ||
805                                    ( $row->[3] eq $field->[-2] &&          # same subfield, but not repeatable
806                                            ! $marc_repeatable_subfield->{ $fsf }
807                                    )
808                            ) {
809                                    push @m, $field;
810                                    warn "## saved/2 ", dump( $field ),$/ if ($debug);
811                                    $field = $row;
812    
813                            } else {
814                                    # append new subfields to existing field
815                                    push @$field, ( $row->[3], $row->[4] );
816                            }
817                    } else {
818                            # insert first field
819                            $field = $row;
820                    }
821    
822                    if (! $marc_repeatable_subfield->{ $fsf }) {
823                            # make step to next subfield
824                            $i = ($i + $u->{ $fsf } ) % ($len + 1);
825                    }
826            }
827    
828            if ($#$field >= 0) {
829                    push @m, $field;
830                    warn "## saved/3 ", dump( $field ),$/ if ($debug);
831            }
832    
833            return \@m;
834    }
835    
836    =head2 _get_marc_leader
837    
838    Return leader from currently fetched record by L</_get_marc_fields>
839    
840      print WebPAC::Normalize::MARC::_get_marc_leader();
841    
842    =cut
843    
844    sub _get_marc_leader {
845            die "no fetch_pos, did you called _get_marc_fields first?" unless ( defined( $fetch_pos ) );
846            return $marc_leader->[ $fetch_pos ];
847    }
848    
849    =head2 _created_marc_records
850    
851      my $nr_records = _created_marc_records;
852    
853    =cut
854    
855    sub _created_marc_records {
856            return $#{ $marc_record } + 1 if $marc_record;
857  }  }
858    
859  1;  1;

Legend:
Removed from v.1024  
changed lines
  Added in v.1047

  ViewVC Help
Powered by ViewVC 1.1.26