/[webpac2]/trunk/lib/WebPAC/Normalize.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.pm

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

revision 14 by dpavlin, Sun Jul 17 00:04:25 2005 UTC revision 554 by dpavlin, Sat Jul 1 10:19:29 2006 UTC
# Line 1  Line 1 
1  package WebPAC::Normalize;  package WebPAC::Normalize;
2    use Exporter 'import';
3    @EXPORT = qw/
4            _set_rec _set_lookup
5            _get_ds _clean_ds
6            _debug
7    
8            tag search display
9            marc marc_indicators marc_repeatable_subfield
10    
11            rec1 rec2 rec
12            regex prefix suffix surround
13            first lookup join_with
14    /;
15    
16  use warnings;  use warnings;
17  use strict;  use strict;
18  use Data::Dumper;  
19  use Storable;  #use base qw/WebPAC::Common/;
20    use Data::Dump qw/dump/;
21    use Encode qw/from_to/;
22    
23    # debugging warn(s)
24    my $debug = 0;
25    
26    
27  =head1 NAME  =head1 NAME
28    
29  WebPAC::Normalize - normalisation of source file  WebPAC::Normalize - describe normalisaton rules using sets
30    
31  =head1 VERSION  =head1 VERSION
32    
33  Version 0.01  Version 0.07
34    
35  =cut  =cut
36    
37  our $VERSION = '0.01';  our $VERSION = '0.07';
38    
39  =head1 SYNOPSIS  =head1 SYNOPSIS
40    
41  This package contains code that could be helpful in implementing different  This module uses C<conf/normalize/*.pl> files to perform normalisation
42  normalisation front-ends.  from input records using perl functions which are specialized for set
43    processing.
44    
45    Sets are implemented as arrays, and normalisation file is valid perl, which
46    means that you check it's validity before running WebPAC using
47    C<perl -c normalize.pl>.
48    
49    Normalisation can generate multiple output normalized data. For now, supported output
50    types (on the left side of definition) are: C<tag>, C<display>, C<search> and
51    C<marc>.
52    
53  =head1 FUNCTIONS  =head1 FUNCTIONS
54    
55  =head2 new  Functions which start with C<_> are private and used by WebPAC internally.
56    All other functions are available for use within normalisation rules.
57    
58  Create new normalisation object  =head2 data_structure
59    
60    my $n = new WebPAC::Normalize::Something(  Return data structure
61          cache_data_structure => './cache/ds/',  
62          lookup_regex => $lookup->regex,    my $ds = WebPAC::Normalize::data_structure(
63            lookup => $lookup->lookup_hash,
64            row => $row,
65            rules => $normalize_pl_config,
66            marc_encoding => 'utf-8',
67    );    );
68    
69  Optional parameter C<cache_data_structure> defines path to directory  Options C<lookup>, C<row>, C<rules> and C<log> are mandatory while all
70  in which cache file for C<data_structure> call will be created.  other are optional.
71    
72    This function will B<die> if normalizastion can't be evaled.
73    
74  Recommended parametar C<lookup_regex> is used to enable parsing of lookups  Since this function isn't exported you have to call it with
75  in structures.  C<WebPAC::Normalize::data_structure>.
76    
77  =cut  =cut
78    
79  sub new {  sub data_structure {
80          my $class = shift;          my $arg = {@_};
81          my $self = {@_};  
82          bless($self, $class);          die "need row argument" unless ($arg->{row});
83            die "need normalisation argument" unless ($arg->{rules});
84    
85          $self->setup_cache_dir( $self->{'cache_data_structure'} );          no strict 'subs';
86            _set_lookup( $arg->{lookup} );
87            _set_rec( $arg->{row} );
88            _clean_ds( %{ $arg } );
89            eval "$arg->{rules}";
90            die "error evaling $arg->{rules}: $@\n" if ($@);
91    
92          $self ? return $self : return undef;          return _get_ds();
93  }  }
94    
95  =head2 setup_cache_dir  =head2 _set_rec
   
 Check if specified cache directory exist, and if not, disable caching.  
96    
97   $setup_cache_dir('./cache/ds/');  Set current record hash
98    
99  If you pass false or zero value to this function, it will disable    _set_rec( $rec );
 cacheing.  
100    
101  =cut  =cut
102    
103  sub setup_cache_dir {  my $rec;
         my $self = shift;  
104    
105          my $dir = shift;  sub _set_rec {
106            $rec = shift or die "no record hash";
107    }
108    
109          my $log = $self->_get_logger();  =head2 _get_ds
110    
111          if ($dir) {  Return hash formatted as data structure
                 my $msg;  
                 if (! -e $dir) {  
                         $msg = "doesn't exist";  
                 } elsif (! -d $dir) {  
                         $msg = "is not directory";  
                 } elsif (! -w $dir) {  
                         $msg = "not writable";  
                 }  
112    
113                  if ($msg) {    my $ds = _get_ds();
114                          undef $self->{'cache_data_structure'};  
115                          $log->warn("cache_data_structure $dir $msg, disabling...");  =cut
116                  } else {  
117                          $log->debug("using cache dir $dir");  my ($out,$marc_record, $marc_encoding, $marc_repeatable_subfield, $marc_indicators);
118                  }  
119          } else {  sub _get_ds {
120                  $log->debug("disabling cache");          return $out;
                 undef $self->{'cache_data_structure'};  
         }  
121  }  }
122    
123    =head2 _clean_ds
124    
125  =head2 data_structure  Clean data structure hash for next record
126    
127  Create in-memory data structure which represents normalized layout from    _clean_ds();
 C<conf/normalize/*.xml>.  
128    
129  This structures are used to produce output.  =cut
130    
131    sub _clean_ds {
132            my $a = {@_};
133            ($out,$marc_record, $marc_encoding, $marc_repeatable_subfield, $marc_indicators) = ();
134            $marc_encoding = $a->{marc_encoding};
135    }
136    
137   my @ds = $webpac->data_structure($rec);  =head2 _set_lookup
138    
139  B<Note: historical oddity follows>  Set current lookup hash
140    
141  This method will also set C<< $webpac->{'currnet_filename'} >> if there is    _set_lookup( $lookup );
 C<< <filename> >> tag and C<< $webpac->{'headline'} >> if there is  
 C<< <headline> >> tag.  
142    
143  =cut  =cut
144    
145  sub data_structure {  my $lookup;
         my $self = shift;  
146    
147          my $log = $self->_get_logger();  sub _set_lookup {
148            $lookup = shift;
149    }
150    
151          my $rec = shift;  =head2 _get_marc_fields
         $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);  
152    
153          my $cache_file;  Get all fields defined by calls to C<marc>
154    
155          if (my $cache_path = $self->{'cache_data_structure'}) {          $marc->add_fields( WebPAC::Normalize:_get_marc_fields() );
                 my $id = $rec->{'000'};  
                 $id = $rec->{'000'}->[0] if ($id =~ m/^ARRAY/o);  
                 unless (defined($id)) {  
                         $log->warn("Can't use cache_data_structure on records without unique identifier in field 000");  
                         undef $self->{'cache_data_structure'};  
                 } else {  
                         $cache_file = "$cache_path/$id";  
                         if (-r $cache_file) {  
                                 my $ds_ref = retrieve($cache_file);  
                                 if ($ds_ref) {  
                                         $log->debug("cache hit: $cache_file");  
                                         my $ok = 1;  
                                         foreach my $f (qw(current_filename headline)) {  
                                                 if ($ds_ref->{$f}) {  
                                                         $self->{$f} = $ds_ref->{$f};  
                                                 } else {  
                                                         $ok = 0;  
                                                 }  
                                         };  
                                         if ($ok && $ds_ref->{'ds'}) {  
                                                 return @{ $ds_ref->{'ds'} };  
                                         } else {  
                                                 $log->warn("cache_data_structure $cache_path corrupt. Use rm $cache_path/* to re-create it on next run!");  
                                                 undef $self->{'cache_data_structure'};  
                                         }  
                                 }  
                         }  
                 }  
         }  
156    
157          undef $self->{'currnet_filename'};  We are using I<magic> which detect repeatable fields only from
158          undef $self->{'headline'};  sequence of field/subfield data generated by normalization.
159    
160          my @sorted_tags;  Repeatable field is created when there is second occurence of same subfield or
161          if ($self->{tags_by_order}) {  if any of indicators are different.
                 @sorted_tags = @{$self->{tags_by_order}};  
         } else {  
                 @sorted_tags = sort { $self->_sort_by_order } keys %{$self->{'import_xml'}->{'indexer'}};  
                 $self->{tags_by_order} = \@sorted_tags;  
         }  
162    
163          my @ds;  This is sane for most cases. Something like:
164    
165          $log->debug("tags: ",sub { join(", ",@sorted_tags) });    900a-1 900b-1 900c-1
166      900a-2 900b-2
167      900a-3
168    
169          foreach my $field (@sorted_tags) {  will be created from any combination of:
170    
171                  my $row;    900a-1 900a-2 900a-3 900b-1 900b-2 900c-1
172    
173  #print "field $field [",$self->{'tag'},"] = ",Dumper($self->{'import_xml'}->{'indexer'}->{$field}->{$self->{'tag'}});  and following rules:
174    
175                  foreach my $tag (@{$self->{'import_xml'}->{'indexer'}->{$field}->{$self->{'tag'}}}) {    marc('900','a', rec('200','a') );
176                          my $format = $tag->{'value'} || $tag->{'content'};    marc('900','b', rec('200','b') );
177      marc('900','c', rec('200','c') );
178    
179                          $log->debug("format: $format");  which might not be what you have in mind. If you need repeatable subfield,
180    define it using C<marc_repeatable_subfield> like this:
181    
182                          my @v;  ....
                         if ($self->{'lookup_regex'} && $format =~ $self->{'lookup_regex'}) {  
                                 @v = $self->fill_in_to_arr($rec,$format);  
                         } else {  
                                 @v = $self->parse_to_arr($rec,$format);  
                         }  
                         next if (! @v);  
183    
184                          if ($tag->{'sort'}) {  =cut
                                 @v = $self->sort_arr(@v);  
                         }  
185    
186                          # use format?  sub _get_marc_fields {
                         if ($tag->{'format_name'}) {  
                                 @v = map { $self->apply_format($tag->{'format_name'},$tag->{'format_delimiter'},$_) } @v;  
                         }  
187    
188                          if ($field eq 'filename') {          return if (! $marc_record || ref($marc_record) ne 'ARRAY' || $#{ $marc_record } < 0);
                                 $self->{'current_filename'} = join('',@v);  
                                 $log->debug("filename: ",$self->{'current_filename'});  
                         } elsif ($field eq 'headline') {  
                                 $self->{'headline'} .= join('',@v);  
                                 $log->debug("headline: ",$self->{'headline'});  
                                 next; # don't return headline in data_structure!  
                         }  
189    
190                          # delimiter will join repeatable fields          # first, sort all existing fields
191                          if ($tag->{'delimiter'}) {          # XXX might not be needed, but modern perl might randomize elements in hash
192                                  @v = ( join($tag->{'delimiter'}, @v) );          my @sorted_marc_record = sort {
193                          }                  $a->[0] . $a->[3] cmp $b->[0] . $b->[3]
194            } @{ $marc_record };
195    
196                          # default types          # output marc fields
197                          my @types = qw(display swish);          my @m;
198                          # override by type attribute  
199                          @types = ( $tag->{'type'} ) if ($tag->{'type'});          # count unique field-subfields (used for offset when walking to next subfield)
200            my $u;
201                          foreach my $type (@types) {          map { $u->{ $_->[0] . $_->[3]  }++ } @sorted_marc_record;
                                 # append to previous line?  
                                 $log->debug("type: $type ",sub { join(" ",@v) }, $row->{'append'} || 'no append');  
                                 if ($tag->{'append'}) {  
   
                                         # I will delimit appended part with  
                                         # delimiter (or ,)  
                                         my $d = $tag->{'delimiter'};  
                                         # default delimiter  
                                         $d ||= " ";  
   
                                         my $last = pop @{$row->{$type}};  
                                         $d = "" if (! $last);  
                                         $last .= $d . join($d, @v);  
                                         push @{$row->{$type}}, $last;  
   
                                 } else {  
                                         push @{$row->{$type}}, @v;  
                                 }  
                         }  
202    
203            if ($debug) {
204                    warn "## marc_repeatable_subfield ", dump( $marc_repeatable_subfield ), $/;
205                    warn "## marc_record ", dump( $marc_record ), $/;
206                    warn "## sorted_marc_record ", dump( \@sorted_marc_record ), $/;
207                    warn "## subfield count ", dump( $u ), $/;
208            }
209    
210            my $len = $#sorted_marc_record;
211            my $visited;
212            my $i = 0;
213            my $field;
214    
215            foreach ( 0 .. $len ) {
216    
217                    # find next element which isn't visited
218                    while ($visited->{$i}) {
219                            $i = ($i + 1) % ($len + 1);
220                  }                  }
221    
222                  if ($row) {                  # mark it visited
223                          $row->{'tag'} = $field;                  $visited->{$i}++;
224    
225                          # TODO: name_sigular, name_plural                  my $row = $sorted_marc_record[$i];
                         my $name = $self->{'import_xml'}->{'indexer'}->{$field}->{'name'};  
                         $row->{'name'} = $name ? $self->_x($name) : $field;  
   
                         # post-sort all values in field  
                         if ($self->{'import_xml'}->{'indexer'}->{$field}->{'sort'}) {  
                                 $log->warn("sort at field tag not implemented");  
                         }  
226    
227                          push @ds, $row;                  # field and subfield which is key for
228                    # marc_repeatable_subfield and u
229                    my $fsf = $row->[0] . $row->[3];
230    
231                    if ($debug > 1) {
232    
233                            print "### field so far [", $#$field, "] : ", dump( $field ), " ", $field ? 'T' : 'F', $/;
234                            print "### this [$i]: ", dump( $row ),$/;
235                            print "### sf: ", $row->[3], " vs ", $field->[3],
236                                    $marc_repeatable_subfield->{ $row->[0] . $row->[3] } ? ' (repeatable)' : '', $/,
237                                    if ($#$field >= 0);
238    
                         $log->debug("row $field: ",sub { Dumper($row) });  
239                  }                  }
240    
241          }                  # if field exists
242                    if ( $#$field >= 0 ) {
243                            if (
244                                    $row->[0] ne $field->[0] ||             # field
245                                    $row->[1] ne $field->[1] ||             # i1
246                                    $row->[2] ne $field->[2]                # i2
247                            ) {
248                                    push @m, $field;
249                                    warn "## saved/1 ", dump( $field ),$/ if ($debug);
250                                    $field = $row;
251    
252                            } elsif (
253                                    ( $row->[3] lt $field->[-2] )           # subfield which is not next (e.g. a after c)
254                                    ||
255                                    ( $row->[3] eq $field->[-2] &&          # same subfield, but not repeatable
256                                            ! $marc_repeatable_subfield->{ $fsf }
257                                    )
258                            ) {
259                                    push @m, $field;
260                                    warn "## saved/2 ", dump( $field ),$/ if ($debug);
261                                    $field = $row;
262    
263          if ($cache_file) {                          } else {
264                  store {                                  # append new subfields to existing field
265                          ds => \@ds,                                  push @$field, ( $row->[3], $row->[4] );
266                          current_filename => $self->{'current_filename'},                          }
267                          headline => $self->{'headline'},                  } else {
268                  }, $cache_file;                          # insert first field
269                  $log->debug("created storable cache file $cache_file");                          $field = $row;
270                    }
271    
272                    if (! $marc_repeatable_subfield->{ $fsf }) {
273                            # make step to next subfield
274                            $i = ($i + $u->{ $fsf } ) % ($len + 1);
275                    }
276          }          }
277    
278          return @ds;          if ($#$field >= 0) {
279                    push @m, $field;
280                    warn "## saved/3 ", dump( $field ),$/ if ($debug);
281            }
282    
283            return @m;
284  }  }
285    
286  =head2 apply_format  =head2 _debug
   
 Apply format specified in tag with C<format_name="name"> and  
 C<format_delimiter=";;">.  
287    
288   my $text = $webpac->apply_format($format_name,$format_delimiter,$data);  Change level of debug warnings
289    
290  Formats can contain C<lookup{...}> if you need them.    _debug( 2 );
291    
292  =cut  =cut
293    
294  sub apply_format {  sub _debug {
295          my $self = shift;          my $l = shift;
296            return $debug unless defined($l);
297          my ($name,$delimiter,$data) = @_;          $debug = $l;
298    }
299    
300          my $log = $self->_get_logger();  =head1 Functions to create C<data_structure>
301    
302          if (! $self->{'import_xml'}->{'format'}->{$name}) {  Those functions generally have to first in your normalization file.
                 $log->warn("<format name=\"$name\"> is not defined in ",$self->{'import_xml_file'});  
                 return $data;  
         }  
303    
304          $log->warn("no delimiter for format $name") if (! $delimiter);  =head2 tag
305    
306          my $format = $self->_x($self->{'import_xml'}->{'format'}->{$name}->{'content'}) || $log->logdie("can't find format '$name'");  Define new tag for I<search> and I<display>.
307    
308          my @data = split(/\Q$delimiter\E/, $data);    tag('Title', rec('200','a') );
309    
         my $out = sprintf($format, @data);  
         $log->debug("using format $name [$format] on $data to produce: $out");  
310    
311          if ($self->{'lookup_regex'} && $out =~ $self->{'lookup_regex'}) {  =cut
                 return $self->lookup($out);  
         } else {  
                 return $out;  
         }  
312    
313    sub tag {
314            my $name = shift or die "tag needs name as first argument";
315            my @o = grep { defined($_) && $_ ne '' } @_;
316            return unless (@o);
317            $out->{$name}->{tag} = $name;
318            $out->{$name}->{search} = \@o;
319            $out->{$name}->{display} = \@o;
320  }  }
321    
322  =head2 parse  =head2 display
323    
324  Perform smart parsing of string, skipping delimiters for fields which aren't  Define tag just for I<display>
 defined. It can also eval code in format starting with C<eval{...}> and  
 return output or nothing depending on eval code.  
325    
326   my $text = $webpac->parse($rec,'eval{"v901^a" eq "Deskriptor"}descriptor: v250^a', $i);    @v = display('Title', rec('200','a') );
327    
328  =cut  =cut
329    
330  sub parse {  sub display {
331          my $self = shift;          my $name = shift or die "display needs name as first argument";
332            my @o = grep { defined($_) && $_ ne '' } @_;
333            return unless (@o);
334            $out->{$name}->{tag} = $name;
335            $out->{$name}->{display} = \@o;
336    }
337    
338          my ($rec, $format_utf8, $i) = @_;  =head2 search
339    
340          return if (! $format_utf8);  Prepare values just for I<search>
341    
342          my $log = $self->_get_logger();    @v = search('Title', rec('200','a') );
343    
344          $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);  =cut
345    
346          $i = 0 if (! $i);  sub search {
347            my $name = shift or die "search needs name as first argument";
348            my @o = grep { defined($_) && $_ ne '' } @_;
349            return unless (@o);
350            $out->{$name}->{tag} = $name;
351            $out->{$name}->{search} = \@o;
352    }
353    
354          my $format = $self->_x($format_utf8) || $log->logconfess("can't convert '$format_utf8' from UTF-8 to ",$self->{'code_page'});  =head2 marc
355    
356          my @out;  Save value for MARC field
357    
358          $log->debug("format: $format");    marc('900','a', rec('200','a') );
359    
360          my $eval_code;  =cut
         # remove eval{...} from beginning  
         $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);  
361    
362          my $filter_name;  sub marc {
363          # remove filter{...} from beginning          my $f = shift or die "marc needs field";
364          $filter_name = $1 if ($format =~ s/^filter{([^}]+)}//s);          die "marc field must be numer" unless ($f =~ /^\d+$/);
365    
366          my $prefix;          my $sf = shift or die "marc needs subfield";
         my $all_found=0;  
367    
368          while ($format =~ s/^(.*?)(v|s)(\d+)(?:\^(\w))?//s) {          foreach (@_) {
369                    my $v = $_;             # make var read-write for Encode
370                    next unless (defined($v) && $v !~ /^\s*$/);
371                    from_to($v, 'iso-8859-2', $marc_encoding) if ($marc_encoding);
372                    my ($i1,$i2) = defined($marc_indicators->{$f}) ? @{ $marc_indicators->{$f} } : (' ',' ');
373                    push @{ $marc_record }, [ $f, $i1, $i2, $sf => $v ];
374            }
375    }
376    
377                  my $del = $1 || '';  =head2 marc_repeatable_subfield
                 $prefix ||= $del if ($all_found == 0);  
378    
379                  # repeatable index  Save values for MARC repetable subfield
                 my $r = $i;  
                 $r = 0 if (lc("$2") eq 's');  
380    
381                  my $found = 0;    marc_repeatable_subfield('910', 'z', rec('909') );
                 my $tmp = $self->get_data(\$rec,$3,$4,$r,\$found);  
382    
383                  if ($found) {  =cut
                         push @out, $del;  
                         push @out, $tmp;  
                         $all_found += $found;  
                 }  
         }  
384    
385          return if (! $all_found);  sub marc_repeatable_subfield {
386            my ($f,$sf) = @_;
387            die "marc_repeatable_subfield need field and subfield!\n" unless ($f && $sf);
388            $marc_repeatable_subfield->{ $f . $sf }++;
389            marc(@_);
390    }
391    
392          my $out = join('',@out);  =head2 marc_indicators
393    
394          if ($out) {  Set both indicators for MARC field
                 # add rest of format (suffix)  
                 $out .= $format;  
395    
396                  # add prefix if not there    marc_indicators('900', ' ', 1);
                 $out = $prefix . $out if ($out !~ m/^\Q$prefix\E/);  
397    
398                  $log->debug("result: $out");  Any indicator value other than C<0-9> will be treated as undefined.
         }  
399    
400          if ($eval_code) {  =cut
                 my $eval = $self->fill_in($rec,$eval_code,$i) || return;  
                 $log->debug("about to eval{$eval} format: $out");  
                 return if (! $self->_eval($eval));  
         }  
           
         if ($filter_name && $self->{'filter'}->{$filter_name}) {  
                 $log->debug("about to filter{$filter_name} format: $out");  
                 $out = $self->{'filter'}->{$filter_name}->($out);  
                 return unless(defined($out));  
                 $log->debug("filter result: $out");  
         }  
401    
402          return $out;  sub marc_indicators {
403  }          my $f = shift || die "marc_indicators need field!\n";
404            my ($i1,$i2) = @_;
405            die "marc_indicators($f, ...) need i1!\n" unless(defined($i1));
406            die "marc_indicators($f, $i1, ...) need i2!\n" unless(defined($i2));
407    
408  =head2 parse_to_arr          $i1 = ' ' if ($i1 !~ /^\d$/);
409            $i2 = ' ' if ($i2 !~ /^\d$/);
410            @{ $marc_indicators->{$f} } = ($i1,$i2);
411    }
412    
 Similar to C<parse>, but returns array of all repeatable fields  
413    
414   my @arr = $webpac->parse_to_arr($rec,'v250^a');  =head1 Functions to extract data from input
415    
416  =cut  This function should be used inside functions to create C<data_structure> described
417    above.
418    
419  sub parse_to_arr {  =head2 rec1
         my $self = shift;  
420    
421          my ($rec, $format_utf8) = @_;  Return all values in some field
422    
423          my $log = $self->_get_logger();    @v = rec1('200')
424    
425          $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);  TODO: order of values is probably same as in source data, need to investigate that
         return if (! $format_utf8);  
426    
427          my $i = 0;  =cut
         my @arr;  
428    
429          while (my $v = $self->parse($rec,$format_utf8,$i++)) {  sub rec1 {
430                  push @arr, $v;          my $f = shift;
431            return unless (defined($rec) && defined($rec->{$f}));
432            if (ref($rec->{$f}) eq 'ARRAY') {
433                    return map {
434                            if (ref($_) eq 'HASH') {
435                                    values %{$_};
436                            } else {
437                                    $_;
438                            }
439                    } @{ $rec->{$f} };
440            } elsif( defined($rec->{$f}) ) {
441                    return $rec->{$f};
442          }          }
   
         $log->debug("format '$format_utf8' returned ",--$i," elements: ", sub { join(" | ",@arr) }) if (@arr);  
   
         return @arr;  
443  }  }
444    
445  =head2 fill_in_to_arr  =head2 rec2
446    
447  Similar to C<fill_in>, but returns array of all repeatable fields. Usable  Return all values in specific field and subfield
 for fields which have lookups, so they shouldn't be parsed but rather  
 C<fill_id>ed.  
448    
449   my @arr = $webpac->fill_in_to_arr($rec,'[v900];;[v250^a]');    @v = rec2('200','a')
450    
451  =cut  =cut
452    
453  sub fill_in_to_arr {  sub rec2 {
454          my $self = shift;          my $f = shift;
455            return unless (defined($rec && $rec->{$f}));
456            my $sf = shift;
457            return map { $_->{$sf} } grep { ref($_) eq 'HASH' && $_->{$sf} } @{ $rec->{$f} };
458    }
459    
460          my ($rec, $format_utf8) = @_;  =head2 rec
461    
462          my $log = $self->_get_logger();  syntaxtic sugar for
463    
464          $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);    @v = rec('200')
465          return if (! $format_utf8);    @v = rec('200','a')
466    
467          my $i = 0;  =cut
         my @arr;  
468    
469          while (my @v = $self->fill_in($rec,$format_utf8,$i++)) {  sub rec {
470                  push @arr, @v;          if ($#_ == 0) {
471                    return rec1(@_);
472            } elsif ($#_ == 1) {
473                    return rec2(@_);
474          }          }
   
         $log->debug("format '$format_utf8' returned ",--$i," elements: ", sub { join(" | ",@arr) }) if (@arr);  
   
         return @arr;  
475  }  }
476    
477  =head2 sort_arr  =head2 regex
478    
479  Sort array ignoring case and html in data  Apply regex to some or all values
480    
481   my @sorted = $webpac->sort_arr(@unsorted);    @v = regex( 's/foo/bar/g', @v );
482    
483  =cut  =cut
484    
485  sub sort_arr {  sub regex {
486          my $self = shift;          my $r = shift;
487            my @out;
488            #warn "r: $r\n", dump(\@_);
489            foreach my $t (@_) {
490                    next unless ($t);
491                    eval "\$t =~ $r";
492                    push @out, $t if ($t && $t ne '');
493            }
494            return @out;
495    }
496    
497          my $log = $self->_get_logger();  =head2 prefix
498    
499          # FIXME add Schwartzian Transformation?  Prefix all values with a string
500    
501          my @sorted = sort {    @v = prefix( 'my_', @v );
                 $a =~ s#<[^>]+/*>##;  
                 $b =~ s#<[^>]+/*>##;  
                 lc($b) cmp lc($a)  
         } @_;  
         $log->debug("sorted values: ",sub { join(", ",@sorted) });  
502    
503          return @sorted;  =cut
504    
505    sub prefix {
506            my $p = shift or die "prefix needs string as first argument";
507            return map { $p . $_ } grep { defined($_) } @_;
508  }  }
509    
510    =head2 suffix
511    
512  =head2 _sort_by_order  suffix all values with a string
513    
514  Sort xml tags data structure accoding to C<order=""> attribute.    @v = suffix( '_my', @v );
515    
516  =cut  =cut
517    
518  sub _sort_by_order {  sub suffix {
519          my $self = shift;          my $s = shift or die "suffix needs string as first argument";
520            return map { $_ . $s } grep { defined($_) } @_;
521    }
522    
523          my $va = $self->{'import_xml'}->{'indexer'}->{$a}->{'order'} ||  =head2 surround
                 $self->{'import_xml'}->{'indexer'}->{$a};  
         my $vb = $self->{'import_xml'}->{'indexer'}->{$b}->{'order'} ||  
                 $self->{'import_xml'}->{'indexer'}->{$b};  
524    
525          return $va <=> $vb;  surround all values with a two strings
526  }  
527      @v = surround( 'prefix_', '_suffix', @v );
528    
529  =head2 _x  =cut
530    
531  Convert strings from C<conf/normalize> encoding into application specific  sub surround {
532  (optinally specified using C<code_page> to C<new> constructor.          my $p = shift or die "surround need prefix as first argument";
533            my $s = shift or die "surround needs suffix as second argument";
534            return map { $p . $_ . $s } grep { defined($_) } @_;
535    }
536    
537   my $text = $n->_x('normalize text string');  =head2 first
538    
539  This is a stub so that other modules doesn't have to implement it.  Return first element
540    
541      $v = first( @v );
542    
543  =cut  =cut
544    
545  sub _x {  sub first {
546          my $self = shift;          my $r = shift;
547          return shift;          return $r;
548  }  }
549    
550    =head2 lookup
551    
552  =head1 AUTHOR  Consult lookup hashes for some value
553    
554  Dobrica Pavlinusic, C<< <dpavlin@rot13.org> >>    @v = lookup( $v );
555      @v = lookup( @v );
556    
557  =head1 COPYRIGHT & LICENSE  =cut
558    
559  Copyright 2005 Dobrica Pavlinusic, All Rights Reserved.  sub lookup {
560            my $k = shift or return;
561            return unless (defined($lookup->{$k}));
562            if (ref($lookup->{$k}) eq 'ARRAY') {
563                    return @{ $lookup->{$k} };
564            } else {
565                    return $lookup->{$k};
566            }
567    }
568    
569    =head2 join_with
570    
571  This program is free software; you can redistribute it and/or modify it  Joins walues with some delimiter
572  under the same terms as Perl itself.  
573      $v = join_with(", ", @v);
574    
575  =cut  =cut
576    
577  1; # End of WebPAC::DB  sub join_with {
578            my $d = shift;
579            return join($d, grep { defined($_) && $_ ne '' } @_);
580    }
581    
582    # END
583    1;

Legend:
Removed from v.14  
changed lines
  Added in v.554

  ViewVC Help
Powered by ViewVC 1.1.26