/[webpac]/trunk2/lib/WebPAC.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 /trunk2/lib/WebPAC.pm

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

revision 368 by dpavlin, Thu Jun 17 12:27:02 2004 UTC revision 560 by dpavlin, Sat Oct 30 23:04:37 2004 UTC
# Line 7  use Carp; Line 7  use Carp;
7  use Text::Iconv;  use Text::Iconv;
8  use Config::IniFiles;  use Config::IniFiles;
9  use XML::Simple;  use XML::Simple;
10    use Template;
11    use Log::Log4perl qw(get_logger :levels);
12    use Time::HiRes qw(time);
13    
14  use Data::Dumper;  use Data::Dumper;
15    
16    #my $LOOKUP_REGEX = '\[[^\[\]]+\]';
17    #my $LOOKUP_REGEX_SAVE = '\[([^\[\]]+)\]';
18    my $LOOKUP_REGEX = 'lookup{[^\{\}]+}';
19    my $LOOKUP_REGEX_SAVE = 'lookup{([^\{\}]+)}';
20    
21  =head1 NAME  =head1 NAME
22    
23  WebPAC - base class for WebPAC  WebPAC - base class for WebPAC
# Line 22  This module implements methods used by W Line 30  This module implements methods used by W
30    
31  =head2 new  =head2 new
32    
33  This will create new instance of WebPAC using configuration specified by C<config_file>.  Create new instance of WebPAC using configuration specified by C<config_file>.
34    
35   my $webpac = new WebPAC(   my $webpac = new WebPAC(
36          config_file => 'name.conf',          config_file => 'name.conf',
37          [code_page => 'ISO-8859-2',]          code_page => 'ISO-8859-2',
38            low_mem => 1,
39            filter => {
40                    'lower' => sub { lc($_[0]) },
41            },
42   );   );
43    
44  Default C<code_page> is C<ISO-8859-2>.  Default C<code_page> is C<ISO-8859-2>.
45    
46  It will also read configuration files  Default is not to use C<low_mem> options (see L<MEMORY USAGE> below).
47    
48    There is optinal parametar C<filter> which specify different filters which
49    can be applied using C<filter{name}> notation.
50    Same filters can be used in Template Toolkit files.
51    
52    This method will also read configuration files
53  C<global.conf> (used by indexer and Web font-end)  C<global.conf> (used by indexer and Web font-end)
54  and configuration file specified by C<config_file>  and configuration file specified by C<config_file>
55  which describes databases to be indexed.  which describes databases to be indexed.
# Line 52  sub new { Line 70  sub new {
70          my $self = {@_};          my $self = {@_};
71          bless($self, $class);          bless($self, $class);
72    
73            $self->{'start_t'} = time();
74    
75            my $log_file = $self->{'log'} || "log.conf";
76            Log::Log4perl->init($log_file);
77    
78            my $log = $self->_get_logger();
79    
80          # fill in default values          # fill in default values
81          # output codepage          # output codepage
82          $self->{'code_page'} = 'ISO-8859-2' if (! $self->{'code_page'});          $self->{'code_page'} = 'ISO-8859-2' if (! $self->{'code_page'});
# Line 59  sub new { Line 84  sub new {
84          #          #
85          # read global.conf          # read global.conf
86          #          #
87            $log->debug("read 'global.conf'");
88    
89          $self->{global_config_file} = new Config::IniFiles( -file => 'global.conf' ) || croak "can't open 'global.conf'";          my $config = new Config::IniFiles( -file => 'global.conf' ) || $log->logcroak("can't open 'global.conf'");
90    
91          # read global config parametars          # read global config parametars
92          foreach my $var (qw(          foreach my $var (qw(
# Line 70  sub new { Line 96  sub new {
96                          dbi_passwd                          dbi_passwd
97                          show_progress                          show_progress
98                          my_unac_filter                          my_unac_filter
99                            output_template
100                  )) {                  )) {
101                  $self->{global_config}->{$var} = $self->{global_config_file}->val('global', $var);                  $self->{'global_config'}->{$var} = $config->val('global', $var);
102          }          }
103    
104          #          #
105          # read indexer config file          # read indexer config file
106          #          #
107    
108          $self->{indexer_config_file} = new Config::IniFiles( -file => $self->{config_file} ) || croak "can't open '$self->{config_file}'";          $self->{indexer_config_file} = new Config::IniFiles( -file => $self->{config_file} ) || $log->logcroak("can't open '",$self->{config_file},"'");
109    
110            # create UTF-8 convertor for import_xml files
111          $self->{'utf2cp'} = Text::Iconv->new('UTF-8' ,$self->{'code_page'});          $self->{'utf2cp'} = Text::Iconv->new('UTF-8' ,$self->{'code_page'});
112    
113            # create Template toolkit instance
114            $self->{'tt'} = Template->new(
115                    INCLUDE_PATH => ($self->{'global_config_file'}->{'output_template'} || './output_template'),
116                    FILTERS => $self->{'filter'},
117                    EVAL_PERL => 1,
118            );
119    
120            # running with low_mem flag? well, use DBM::Deep then.
121            if ($self->{'low_mem'}) {
122                    $log->info("running with low_mem which impacts performance (<32 Mb memory usage)");
123    
124                    my $db_file = "data.db";
125    
126                    if (-e $db_file) {
127                            unlink $db_file or $log->logdie("can't remove '$db_file' from last run");
128                            $log->debug("removed '$db_file' from last run");
129                    }
130    
131                    require DBM::Deep;
132    
133                    my $db = new DBM::Deep $db_file;
134    
135                    $log->logdie("DBM::Deep error: $!") unless ($db);
136    
137                    if ($db->error()) {
138                            $log->logdie("can't open '$db_file' under low_mem: ",$db->error());
139                    } else {
140                            $log->debug("using file '$db_file' for DBM::Deep");
141                    }
142    
143                    $self->{'db'} = $db;
144            }
145    
146            $log->debug("filters defined: ",Dumper($self->{'filter'}));
147    
148          return $self;          return $self;
149  }  }
150    
# Line 91  Open CDS/ISIS database using OpenIsis mo Line 155  Open CDS/ISIS database using OpenIsis mo
155   $webpac->open_isis(   $webpac->open_isis(
156          filename => '/data/ISIS/ISIS',          filename => '/data/ISIS/ISIS',
157          code_page => '852',          code_page => '852',
158          limit_mfn => '500',          limit_mfn => 500,
159            start_mfn => 6000,
160          lookup => [ ... ],          lookup => [ ... ],
161   );   );
162    
163  By default, ISIS code page is assumed to be C<852>.  By default, ISIS code page is assumed to be C<852>.
164    
165    If optional parametar C<start_mfn> is set, this will be first MFN to read
166    from database (so you can skip beginning of your database if you need to).
167    
168  If optional parametar C<limit_mfn> is set, it will read just 500 records  If optional parametar C<limit_mfn> is set, it will read just 500 records
169  from database in example above.  from database in example above.
170    
# Line 119  sub open_isis { Line 187  sub open_isis {
187          my $self = shift;          my $self = shift;
188          my $arg = {@_};          my $arg = {@_};
189    
190          croak "need filename" if (! $arg->{'filename'});          my $log = $self->_get_logger();
191    
192            $log->logcroak("need filename") if (! $arg->{'filename'});
193          my $code_page = $arg->{'code_page'} || '852';          my $code_page = $arg->{'code_page'} || '852';
194    
195            $log->logdie("can't find database ",$arg->{'filename'}) unless (glob($arg->{'filename'}.'.*'));
196    
197            # store data in object
198            $self->{'isis_filename'} = $arg->{'filename'};
199            $self->{'isis_code_page'} = $code_page;
200    
201          use OpenIsis;          use OpenIsis;
202    
203          #$self->{'isis_code_page'} = $code_page;          #$self->{'isis_code_page'} = $code_page;
# Line 129  sub open_isis { Line 205  sub open_isis {
205          # create Text::Iconv object          # create Text::Iconv object
206          my $cp = Text::Iconv->new($code_page,$self->{'code_page'});          my $cp = Text::Iconv->new($code_page,$self->{'code_page'});
207    
208          print STDERR "reading ISIS database '",$arg->{'filename'},"'\n" if ($self->{'debug'});          $log->info("reading ISIS database '",$arg->{'filename'},"'");
209            $log->debug("isis code page: $code_page");
210    
211          my $isis_db = OpenIsis::open($arg->{'filename'});          my $isis_db = OpenIsis::open($arg->{'filename'});
212    
213          my $maxmfn = OpenIsis::maxRowid( $isis_db ) || 1;          my $maxmfn = OpenIsis::maxRowid( $isis_db ) || 1;
214            my $startmfn = 1;
215    
216            if (my $s = $self->{'start_mfn'}) {
217                    $log->info("skipping to MFN $s");
218                    $startmfn = $s;
219            } else {
220                    $self->{'start_mfn'} = $startmfn;
221            }
222    
223          $maxmfn = $self->{limit_mfn} if ($self->{limit_mfn});          $maxmfn = $startmfn + $self->{limit_mfn} if ($self->{limit_mfn});
224    
225          print STDERR "processing $maxmfn records...\n" if ($self->{'debug'});          $log->info("processing ",($maxmfn-$startmfn)." records...");
226    
227          # read database          # read database
228          for (my $mfn = 1; $mfn <= $maxmfn; $mfn++) {          for (my $mfn = $startmfn; $mfn <= $maxmfn; $mfn++) {
229    
230    
231                    $log->debug("mfn: $mfn\n");
232    
233                    my $rec;
234    
235                  # read record                  # read record
236                  my $row = OpenIsis::read( $isis_db, $mfn );                  my $row = OpenIsis::read( $isis_db, $mfn );
# Line 159  sub open_isis { Line 249  sub open_isis {
249                                                  $val = $l;                                                  $val = $l;
250                                          }                                          }
251    
252                                          push @{$self->{'data'}->{$mfn}->{$k}}, $val;                                          push @{$rec->{$k}}, $val;
253                                  }                                  }
254                            } else {
255                                    push @{$rec->{'000'}}, $mfn;
256                          }                          }
257    
258                  }                  }
259    
260                    $log->confess("record $mfn empty?") unless ($rec);
261    
262                    # store
263                    if ($self->{'low_mem'}) {
264                            $self->{'db'}->put($mfn, $rec);
265                    } else {
266                            $self->{'data'}->{$mfn} = $rec;
267                    }
268    
269                  # create lookup                  # create lookup
                 my $rec = $self->{'data'}->{$mfn};  
270                  $self->create_lookup($rec, @{$arg->{'lookup'}});                  $self->create_lookup($rec, @{$arg->{'lookup'}});
271    
272                    $self->progress_bar($mfn,$maxmfn);
273    
274          }          }
275    
276          $self->{'current_mfn'} = 1;          $self->{'current_mfn'} = -1;
277            $self->{'last_pcnt'} = 0;
278    
279            $log->debug("max mfn: $maxmfn");
280    
281          # store max mfn and return it.          # store max mfn and return it.
282          return $self->{'max_mfn'} = $maxmfn;          return $self->{'max_mfn'} = $maxmfn;
# Line 189  it's implemented, that is). Line 294  it's implemented, that is).
294  sub fetch_rec {  sub fetch_rec {
295          my $self = shift;          my $self = shift;
296    
297          my $mfn = $self->{'current_mfn'}++ || confess "it seems that you didn't load database!";          my $log = $self->_get_logger();
298    
299            $log->logconfess("it seems that you didn't load database!") unless ($self->{'current_mfn'});
300    
301            if ($self->{'current_mfn'} == -1) {
302                    $self->{'current_mfn'} = $self->{'start_mfn'};
303            } else {
304                    $self->{'current_mfn'}++;
305            }
306    
307            my $mfn = $self->{'current_mfn'};
308    
309          if ($mfn > $self->{'max_mfn'}) {          if ($mfn > $self->{'max_mfn'}) {
310                  $self->{'current_mfn'} = $self->{'max_mfn'};                  $self->{'current_mfn'} = $self->{'max_mfn'};
311                    $log->debug("at EOF");
312                  return;                  return;
313          }          }
314    
315          return $self->{'data'}->{$mfn};          $self->progress_bar($mfn,$self->{'max_mfn'});
316    
317            if ($self->{'low_mem'}) {
318                    return $self->{'db'}->get($mfn);
319            } else {
320                    return $self->{'data'}->{$mfn};
321            }
322    }
323    
324    =head2 mfn
325    
326    Returns current record number (MFN).
327    
328     print $webpac->mfn;
329    
330    =cut
331    
332    sub mfn {
333            my $self = shift;
334            return $self->{'current_mfn'};
335    }
336    
337    =head2 progress_bar
338    
339    Draw progress bar on STDERR.
340    
341     $webpac->progress_bar($current, $max);
342    
343    =cut
344    
345    sub progress_bar {
346            my $self = shift;
347    
348            my ($curr,$max) = @_;
349    
350            my $log = $self->_get_logger();
351    
352            $log->logconfess("no current value!") if (! $curr);
353            $log->logconfess("no maximum value!") if (! $max);
354    
355            if ($curr > $max) {
356                    $max = $curr;
357                    $log->debug("overflow to $curr");
358            }
359    
360            $self->{'last_pcnt'} ||= 1;
361    
362            my $p = int($curr * 100 / $max) || 1;
363    
364            # reset on re-run
365            if ($p < $self->{'last_pcnt'}) {
366                    $self->{'last_pcnt'} = $p;
367                    $self->{'last_t'} = time();
368                    $self->{'last_curr'} = undef;
369            }
370    
371            $self->{'last_t'} ||= time();
372    
373            if ($p != $self->{'last_pcnt'}) {
374    
375                    my $last_curr = $self->{'last_curr'} || $curr;
376                    my $t = time();
377                    my $rate = ($curr - $last_curr) / (($t - $self->{'last_t'} || 1));
378                    my $eta = ($max-$curr) / ($rate || 1);
379                    printf STDERR ("%5d [%-38s] %-5d %0.1f/s %s\r",$curr,"=" x ($p/3)."$p%>", $max, $rate, $self->fmt_time($eta));
380                    $self->{'last_pcnt'} = $p;
381                    $self->{'last_t'} = time();
382                    $self->{'last_curr'} = $curr;
383            }
384            print STDERR "\n" if ($p == 100);
385    }
386    
387    =head2 fmt_time
388    
389    Format time (in seconds) for display.
390    
391     print $webpac->fmt_time(time());
392    
393    This method is called by L<progress_bar> to display remaining time.
394    
395    =cut
396    
397    sub fmt_time {
398            my $self = shift;
399    
400            my $t = shift || 0;
401            my $out = "";
402    
403            my ($ss,$mm,$hh) = gmtime($t);
404            $out .= "${hh}h" if ($hh);
405            $out .= sprintf("%02d:%02d", $mm,$ss);
406            $out .= "  " if ($hh == 0);
407            return $out;
408  }  }
409    
410  =head2 open_import_xml  =head2 open_import_xml
# Line 210  Read file from C<import_xml/> directory Line 418  Read file from C<import_xml/> directory
418  sub open_import_xml {  sub open_import_xml {
419          my $self = shift;          my $self = shift;
420    
421            my $log = $self->_get_logger();
422    
423          my $arg = {@_};          my $arg = {@_};
424          confess "need type to load file from import_xml/" if (! $arg->{'type'});          $log->logconfess("need type to load file from import_xml/") if (! $arg->{'type'});
425    
426          $self->{'type'} = $arg->{'type'};          $self->{'type'} = $arg->{'type'};
427    
# Line 220  sub open_import_xml { Line 430  sub open_import_xml {
430    
431          $self->{'tag'} = $type2tag{$type_base};          $self->{'tag'} = $type2tag{$type_base};
432    
433          print STDERR "using type ",$self->{'type'}," tag ",$self->{'tag'},"\n" if ($self->{'debug'});          $log->info("using type '",$self->{'type'},"' tag <",$self->{'tag'},">");
434    
435          my $f = "./import_xml/".$self->{'type'}.".xml";          my $f = "./import_xml/".$self->{'type'}.".xml";
436          confess "import_xml file '$f' doesn't exist!" if (! -e "$f");          $log->logconfess("import_xml file '$f' doesn't exist!") if (! -e "$f");
437    
438            $log->info("reading '$f'");
439    
440          print STDERR "reading '$f'\n" if ($self->{'debug'});          $self->{'import_xml_file'} = $f;
441    
442          $self->{'import_xml'} = XMLin($f,          $self->{'import_xml'} = XMLin($f,
443                  ForceArray => [ $self->{'tag'}, 'config', 'format' ],                  ForceArray => [ $self->{'tag'}, 'config', 'format' ],
                 ForceContent => 1  
444          );          );
445    
446          print Dumper($self->{'import_xml'});          $log->debug("import xml is ",sub { Dumper($self->{'import_xml'}) });
447    
448  }  }
449    
# Line 249  Called internally by C<open_*> methods. Line 460  Called internally by C<open_*> methods.
460  sub create_lookup {  sub create_lookup {
461          my $self = shift;          my $self = shift;
462    
463          my $rec = shift || confess "need record to create lookup";          my $log = $self->_get_logger();
464          confess("need HASH as first argument!") if ($rec !~ /HASH/o);  
465            my $rec = shift || $log->logconfess("need record to create lookup");
466            $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
467    
468          foreach my $i (@_) {          foreach my $i (@_) {
469                  if ($i->{'eval'}) {                  $log->logconfess("need key") unless defined($i->{'key'});
470                          my $eval = $self->fill_in($rec,$i->{'eval'});                  $log->logconfess("need val") unless defined($i->{'val'});
471                          my $key = $self->fill_in($rec,$i->{'key'});  
472                          my @val = $self->fill_in($rec,$i->{'val'});                  if (defined($i->{'eval'})) {
473                          if ($key && @val && eval $eval) {                          # eval first, so we can skip fill_in for key and val
474                            my $eval = $self->fill_in($rec,$i->{'eval'}) || next;
475                            if ($self->_eval($eval)) {
476                                    my $key = $self->fill_in($rec,$i->{'key'}) || next;
477                                    my @val = $self->fill_in($rec,$i->{'val'}) || next;
478                                    $log->debug("stored $key = ",sub { join(" | ",@val) });
479                                  push @{$self->{'lookup'}->{$key}}, @val;                                  push @{$self->{'lookup'}->{$key}}, @val;
480                          }                          }
481                  } else {                  } else {
482                          my $key = $self->fill_in($rec,$i->{'key'});                          my $key = $self->fill_in($rec,$i->{'key'}) || next;
483                          my @val = $self->fill_in($rec,$i->{'val'});                          my @val = $self->fill_in($rec,$i->{'val'}) || next;
484                          if ($key && @val) {                          $log->debug("stored $key = ",sub { join(" | ",@val) });
485                                  push @{$self->{'lookup'}->{$key}}, @val;                          push @{$self->{'lookup'}->{$key}}, @val;
                         }  
486                  }                  }
487          }          }
488  }  }
# Line 296  sub get_data { Line 513  sub get_data {
513    
514          if ($$rec->{$f}) {          if ($$rec->{$f}) {
515                  return '' if (! $$rec->{$f}->[$i]);                  return '' if (! $$rec->{$f}->[$i]);
516                    no strict 'refs';
517                  if ($sf && $$rec->{$f}->[$i]->{$sf}) {                  if ($sf && $$rec->{$f}->[$i]->{$sf}) {
518                          $$found++ if (defined($$found));                          $$found++ if (defined($$found));
519                          return $$rec->{$f}->[$i]->{$sf};                          return $$rec->{$f}->[$i]->{$sf};
# Line 336  Following example will read second value Line 554  Following example will read second value
554  This function B<does not> perform parsing of format to inteligenty skip  This function B<does not> perform parsing of format to inteligenty skip
555  delimiters before fields which aren't used.  delimiters before fields which aren't used.
556    
557    This method will automatically decode UTF-8 string to local code page
558    if needed.
559    
560  =cut  =cut
561    
562  sub fill_in {  sub fill_in {
563          my $self = shift;          my $self = shift;
564    
565          my $rec = shift || confess "need data record";          my $log = $self->_get_logger();
566          my $format = shift || confess "need format to parse";  
567            my $rec = shift || $log->logconfess("need data record");
568            my $format = shift || $log->logconfess("need format to parse");
569          # iteration (for repeatable fields)          # iteration (for repeatable fields)
570          my $i = shift || 0;          my $i = shift || 0;
571    
572            $log->logdie("infitite loop in format $format") if ($i > ($self->{'max_mfn'} || 9999));
573    
574          # FIXME remove for speedup?          # FIXME remove for speedup?
575          confess("need HASH as first argument!") if ($rec !~ /HASH/o);          $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
576    
577            if (utf8::is_utf8($format)) {
578                    $format = $self->_x($format);
579            }
580    
581          my $found = 0;          my $found = 0;
582    
# Line 355  sub fill_in { Line 584  sub fill_in {
584          # remove eval{...} from beginning          # remove eval{...} from beginning
585          $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);          $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);
586    
587            my $filter_name;
588            # remove filter{...} from beginning
589            $filter_name = $1 if ($format =~ s/^filter{([^}]+)}//s);
590    
591          # do actual replacement of placeholders          # do actual replacement of placeholders
592          $format =~ s/v(\d+)(?:\^(\w))*/$self->get_data(\$rec,$1,$2,$i,\$found)/ges;          # repeatable fields
593            $format =~ s/v(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,$i,\$found)/ges;
594            # non-repeatable fields
595            $format =~ s/s(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,0,\$found)/ges;
596    
597          if ($found) {          if ($found) {
598                    $log->debug("format: $format");
599                  if ($eval_code) {                  if ($eval_code) {
600                          my $eval = $self->fill_in($rec,$eval_code,$i);                          my $eval = $self->fill_in($rec,$eval_code,$i);
601                          return if (! eval $eval);                          return if (! $self->_eval($eval));
602                    }
603                    if ($filter_name && $self->{'filter'}->{$filter_name}) {
604                            $log->debug("filter '$filter_name' for $format");
605                            $format = $self->{'filter'}->{$filter_name}->($format);
606                            return unless(defined($format));
607                            $log->debug("filter result: $format");
608                  }                  }
609                  # do we have lookups?                  # do we have lookups?
610                  if ($format =~ /\[[^\[\]]+\]/o) {                  if ($format =~ /$LOOKUP_REGEX/o) {
611                            $log->debug("format '$format' has lookup");
612                          return $self->lookup($format);                          return $self->lookup($format);
613                  } else {                  } else {
614                          return $format;                          return $format;
# Line 387  Lookups can be nested (like C<[d:[a:[v90 Line 631  Lookups can be nested (like C<[d:[a:[v90
631  sub lookup {  sub lookup {
632          my $self = shift;          my $self = shift;
633    
634          my $tmp = shift || confess "need format";          my $log = $self->_get_logger();
635    
636          if ($tmp =~ /\[[^\[\]]+\]/o) {          my $tmp = shift || $log->logconfess("need format");
637    
638            if ($tmp =~ /$LOOKUP_REGEX/o) {
639                  my @in = ( $tmp );                  my @in = ( $tmp );
640    
641                    $log->debug("lookup for: ",$tmp);
642    
643                  my @out;                  my @out;
644                  while (my $f = shift @in) {                  while (my $f = shift @in) {
645                          if ($f =~ /\[([^\[\]]+)\]/) {                          if ($f =~ /$LOOKUP_REGEX_SAVE/o) {
646                                  my $k = $1;                                  my $k = $1;
647                                  if ($self->{'lookup'}->{$k}) {                                  if ($self->{'lookup'}->{$k}) {
648                                          foreach my $nv (@{$self->{'lookup'}->{$k}}) {                                          foreach my $nv (@{$self->{'lookup'}->{$k}}) {
649                                                  my $tmp2 = $f;                                                  my $tmp2 = $f;
650                                                  $tmp2 =~ s/\[$k\]/$nv/g;                                                  $tmp2 =~ s/lookup{$k}/$nv/g;
651                                                  push @in, $tmp2;                                                  push @in, $tmp2;
652                                          }                                          }
653                                  } else {                                  } else {
# Line 408  sub lookup { Line 657  sub lookup {
657                                  push @out, $f;                                  push @out, $f;
658                          }                          }
659                  }                  }
660                    $log->logconfess("return is array and it's not expected!") unless wantarray;
661                  return @out;                  return @out;
662          } else {          } else {
663                  return $tmp;                  return $tmp;
# Line 431  sub parse { Line 681  sub parse {
681    
682          return if (! $format_utf8);          return if (! $format_utf8);
683    
684          confess("need HASH as first argument!") if ($rec !~ /HASH/o);          my $log = $self->_get_logger();
685          confess("need utf2cp Text::Iconv object!") if (! $self->{'utf2cp'});  
686            $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
687            $log->logconfess("need utf2cp Text::Iconv object!") if (! $self->{'utf2cp'});
688    
689          $i = 0 if (! $i);          $i = 0 if (! $i);
690    
691          my $format = $self->{'utf2cp'}->convert($format_utf8) || confess("can't convert '$format_utf8' from UTF-8 to ",$self->{'code_page'});          my $format = $self->_x($format_utf8) || $log->logconfess("can't convert '$format_utf8' from UTF-8 to ",$self->{'code_page'});
692    
693          my @out;          my @out;
694    
695            $log->debug("format: $format");
696    
697          my $eval_code;          my $eval_code;
698          # remove eval{...} from beginning          # remove eval{...} from beginning
699          $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);          $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);
700    
701            my $filter_name;
702            # remove filter{...} from beginning
703            $filter_name = $1 if ($format =~ s/^filter{([^}]+)}//s);
704    
705          my $prefix;          my $prefix;
706          my $all_found=0;          my $all_found=0;
707    
708          while ($format =~ s/^(.*?)v(\d+)(?:\^(\w))*//s) {          while ($format =~ s/^(.*?)(v|s)(\d+)(?:\^(\w))?//s) {
709    
710                  my $del = $1 || '';                  my $del = $1 || '';
711                  $prefix ||= $del if ($all_found == 0);                  $prefix ||= $del if ($all_found == 0);
712    
713                    # repeatable index
714                    my $r = $i;
715                    $r = 0 if (lc("$2") eq 's');
716    
717                  my $found = 0;                  my $found = 0;
718                  my $tmp = $self->get_data(\$rec,$2,$3,$i,\$found);                  my $tmp = $self->get_data(\$rec,$3,$4,$r,\$found);
719    
720                  if ($found) {                  if ($found) {
721                          push @out, $del;                          push @out, $del;
# Line 464  sub parse { Line 726  sub parse {
726    
727          return if (! $all_found);          return if (! $all_found);
728    
729          my $out = join('',@out) . $format;          my $out = join('',@out);
730    
731          # add prefix if not there          if ($out) {
732          $out = $prefix . $out if ($out !~ m/^\Q$prefix\E/);                  # add rest of format (suffix)
733                    $out .= $format;
734    
735                    # add prefix if not there
736                    $out = $prefix . $out if ($out !~ m/^\Q$prefix\E/);
737    
738                    $log->debug("result: $out");
739            }
740    
741          if ($eval_code) {          if ($eval_code) {
742                  my $eval = $self->fill_in($rec,$eval_code,$i);                  my $eval = $self->fill_in($rec,$eval_code,$i) || return;
743                  return if (! eval $eval);                  $log->debug("about to eval{$eval} format: $out");
744                    return if (! $self->_eval($eval));
745            }
746            
747            if ($filter_name && $self->{'filter'}->{$filter_name}) {
748                    $log->debug("about to filter{$filter_name} format: $out");
749                    $out = $self->{'filter'}->{$filter_name}->($out);
750                    return unless(defined($out));
751                    $log->debug("filter result: $out");
752          }          }
753    
754          return $out;          return $out;
# Line 490  sub parse_to_arr { Line 767  sub parse_to_arr {
767    
768          my ($rec, $format_utf8) = @_;          my ($rec, $format_utf8) = @_;
769    
770          confess("need HASH as first argument!") if ($rec !~ /HASH/o);          my $log = $self->_get_logger();
771    
772            $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
773          return if (! $format_utf8);          return if (! $format_utf8);
774    
775          my $i = 0;          my $i = 0;
# Line 500  sub parse_to_arr { Line 779  sub parse_to_arr {
779                  push @arr, $v;                  push @arr, $v;
780          }          }
781    
782            $log->debug("format '$format_utf8' returned ",--$i," elements: ", sub { join(" | ",@arr) }) if (@arr);
783    
784          return @arr;          return @arr;
785  }  }
786    
787  =head2 data_structure  =head2 fill_in_to_arr
788    
789  Create in-memory data structure which represents layout from C<import_xml>.  Similar to C<fill_in>, but returns array of all repeatable fields. Usable
790  It is used later to produce output.  for fields which have lookups, so they shouldn't be parsed but rather
791    C<fill_id>ed.
792    
793   my @ds = $webpac->data_structure($rec);   my @arr = $webpac->fill_in_to_arr($rec,'[v900];;[v250^a]');
794    
795  =cut  =cut
796    
797  # private method _sort_by_order  sub fill_in_to_arr {
 # sort subrouting using order="" attribute  
 sub _sort_by_order {  
798          my $self = shift;          my $self = shift;
799    
800          my $va = $self->{'import_xml'}->{'indexer'}->{$a}->{'order'} ||          my ($rec, $format_utf8) = @_;
                 $self->{'import_xml'}->{'indexer'}->{$a};  
         my $vb = $self->{'import_xml'}->{'indexer'}->{$b}->{'order'} ||  
                 $self->{'import_xml'}->{'indexer'}->{$b};  
801    
802          return $va <=> $vb;          my $log = $self->_get_logger();
803    
804            $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
805            return if (! $format_utf8);
806    
807            my $i = 0;
808            my @arr;
809    
810            while (my @v = $self->fill_in($rec,$format_utf8,$i++)) {
811                    push @arr, @v;
812            }
813    
814            $log->debug("format '$format_utf8' returned ",--$i," elements: ", sub { join(" | ",@arr) }) if (@arr);
815    
816            return @arr;
817  }  }
818    
819    =head2 sort_arr
820    
821    Sort array ignoring case and html in data
822    
823     my @sorted = $webpac->sort_arr(@unsorted);
824    
825    =cut
826    
827    sub sort_arr {
828            my $self = shift;
829    
830            my $log = $self->_get_logger();
831    
832            # FIXME add Schwartzian Transformation?
833    
834            my @sorted = sort {
835                    $a =~ s#<[^>]+/*>##;
836                    $b =~ s#<[^>]+/*>##;
837                    lc($b) cmp lc($a)
838            } @_;
839            $log->debug("sorted values: ",sub { join(", ",@sorted) });
840    
841            return @sorted;
842    }
843    
844    
845    =head2 data_structure
846    
847    Create in-memory data structure which represents layout from C<import_xml>.
848    It is used later to produce output.
849    
850     my @ds = $webpac->data_structure($rec);
851    
852    This method will also set C<$webpac->{'currnet_filename'}> if there is
853    <filename> tag in C<import_xml> and C<$webpac->{'headline'}> if there is
854    <headline> tag.
855    
856    =cut
857    
858  sub data_structure {  sub data_structure {
859          my $self = shift;          my $self = shift;
860    
861            my $log = $self->_get_logger();
862    
863          my $rec = shift;          my $rec = shift;
864          confess("need HASH as first argument!") if ($rec !~ /HASH/o);          $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
865    
866            undef $self->{'currnet_filename'};
867            undef $self->{'headline'};
868    
869          my @sorted_tags;          my @sorted_tags;
870          if ($self->{tags_by_order}) {          if ($self->{tags_by_order}) {
# Line 541  sub data_structure { Line 876  sub data_structure {
876    
877          my @ds;          my @ds;
878    
879            $log->debug("tags: ",sub { join(", ",@sorted_tags) });
880    
881          foreach my $field (@sorted_tags) {          foreach my $field (@sorted_tags) {
882    
883                  my $row;                  my $row;
# Line 548  sub data_structure { Line 885  sub data_structure {
885  #print "field $field [",$self->{'tag'},"] = ",Dumper($self->{'import_xml'}->{'indexer'}->{$field}->{$self->{'tag'}});  #print "field $field [",$self->{'tag'},"] = ",Dumper($self->{'import_xml'}->{'indexer'}->{$field}->{$self->{'tag'}});
886    
887                  foreach my $tag (@{$self->{'import_xml'}->{'indexer'}->{$field}->{$self->{'tag'}}}) {                  foreach my $tag (@{$self->{'import_xml'}->{'indexer'}->{$field}->{$self->{'tag'}}}) {
888                          my @v = $self->parse_to_arr($rec,$tag->{'content'});                          my $format = $tag->{'value'} || $tag->{'content'};
889    
890                          next if (! @v);                          $log->debug("format: $format");
891    
892                          # does tag have type?                          my @v;
893                          if ($tag->{'type'}) {                          if ($format =~ /$LOOKUP_REGEX/o) {
894                                  push @{$row->{$tag->{'type'}}}, @v;                                  @v = $self->fill_in_to_arr($rec,$format);
895                          } else {                          } else {
896                                  push @{$row->{'display'}}, @v;                                  @v = $self->parse_to_arr($rec,$format);
897                                  push @{$row->{'swish'}}, @v;                          }
898                            next if (! @v);
899    
900                            if ($tag->{'sort'}) {
901                                    @v = $self->sort_arr(@v);
902                                    $log->warn("sort within tag is usually not what you want!");
903                            }
904    
905                            # use format?
906                            if ($tag->{'format_name'}) {
907                                    @v = map { $self->apply_format($tag->{'format_name'},$tag->{'format_delimiter'},$_) } @v;
908                            }
909    
910                            if ($field eq 'filename') {
911                                    $self->{'current_filename'} = join('',@v);
912                                    $log->debug("filename: ",$self->{'current_filename'});
913                            } elsif ($field eq 'headline') {
914                                    $self->{'headline'} .= join('',@v);
915                                    $log->debug("headline: ",$self->{'headline'});
916                                    next; # don't return headline in data_structure!
917                            }
918    
919                            # delimiter will join repeatable fields
920                            if ($tag->{'delimiter'}) {
921                                    @v = ( join($tag->{'delimiter'}, @v) );
922                            }
923    
924                            # default types
925                            my @types = qw(display swish);
926                            # override by type attribute
927                            @types = ( $tag->{'type'} ) if ($tag->{'type'});
928    
929                            foreach my $type (@types) {
930                                    # append to previous line?
931                                    $log->debug("type: $type ",sub { join(" ",@v) }, $row->{'append'} || 'no append');
932                                    if ($tag->{'append'}) {
933    
934                                            # I will delimit appended part with
935                                            # delimiter (or ,)
936                                            my $d = $tag->{'delimiter'};
937                                            # default delimiter
938                                            $d ||= " ";
939    
940                                            my $last = pop @{$row->{$type}};
941                                            $d = "" if (! $last);
942                                            $last .= $d . join($d, @v);
943                                            push @{$row->{$type}}, $last;
944    
945                                    } else {
946                                            push @{$row->{$type}}, @v;
947                                    }
948                          }                          }
949    
950    
951                  }                  }
952    
953                  if ($row) {                  if ($row) {
954                          $row->{'tag'} = $field;                          $row->{'tag'} = $field;
955    
956                            # TODO: name_sigular, name_plural
957                            my $name = $self->{'import_xml'}->{'indexer'}->{$field}->{'name'};
958                            $row->{'name'} = $name ? $self->_x($name) : $field;
959    
960                            # post-sort all values in field
961                            if ($self->{'import_xml'}->{'indexer'}->{$field}->{'sort'}) {
962                                    $log->warn("sort at field tag not implemented");
963                            }
964    
965                          push @ds, $row;                          push @ds, $row;
966    
967                            $log->debug("row $field: ",sub { Dumper($row) });
968                  }                  }
969    
970          }          }
971    
972          print "data_structure => ",Dumper(\@ds);          return @ds;
973    
974    }
975    
976    =head2 output
977    
978    Create output from in-memory data structure using Template Toolkit template.
979    
980    my $text = $webpac->output( template => 'text.tt', data => @ds );
981    
982    =cut
983    
984    sub output {
985            my $self = shift;
986    
987            my $args = {@_};
988    
989            my $log = $self->_get_logger();
990    
991            $log->logconfess("need template name") if (! $args->{'template'});
992            $log->logconfess("need data array") if (! $args->{'data'});
993    
994            my $out;
995    
996            $self->{'tt'}->process(
997                    $args->{'template'},
998                    $args,
999                    \$out
1000            ) || confess $self->{'tt'}->error();
1001    
1002            return $out;
1003    }
1004    
1005    =head2 output_file
1006    
1007    Create output from in-memory data structure using Template Toolkit template
1008    to a file.
1009    
1010     $webpac->output_file(
1011            file => 'out.txt',
1012            template => 'text.tt',
1013            data => @ds
1014     );
1015    
1016    =cut
1017    
1018    sub output_file {
1019            my $self = shift;
1020    
1021            my $args = {@_};
1022    
1023            my $log = $self->_get_logger();
1024    
1025            my $file = $args->{'file'} || $log->logconfess("need file name");
1026    
1027            $log->debug("creating file ",$file);
1028    
1029            open(my $fh, ">", $file) || $log->logdie("can't open output file '$file': $!");
1030            print $fh $self->output(
1031                    template => $args->{'template'},
1032                    data => $args->{'data'},
1033            ) || $log->logdie("print: $!");
1034            close($fh) || $log->logdie("close: $!");
1035    }
1036    
1037    =head2 apply_format
1038    
1039    Apply format specified in tag with C<format_name="name"> and
1040    C<format_delimiter=";;">.
1041    
1042     my $text = $webpac->apply_format($format_name,$format_delimiter,$data);
1043    
1044    Formats can contain C<lookup{...}> if you need them.
1045    
1046    =cut
1047    
1048    sub apply_format {
1049            my $self = shift;
1050    
1051            my ($name,$delimiter,$data) = @_;
1052    
1053            my $log = $self->_get_logger();
1054    
1055            if (! $self->{'import_xml'}->{'format'}->{$name}) {
1056                    $log->warn("<format name=\"$name\"> is not defined in ",$self->{'import_xml_file'});
1057                    return $data;
1058            }
1059    
1060            $log->warn("no delimiter for format $name") if (! $delimiter);
1061    
1062            my $format = $self->_x($self->{'import_xml'}->{'format'}->{$name}->{'content'}) || $log->logdie("can't find format '$name'");
1063    
1064            my @data = split(/\Q$delimiter\E/, $data);
1065    
1066            my $out = sprintf($format, @data);
1067            $log->debug("using format $name [$format] on $data to produce: $out");
1068    
1069            if ($out =~ m/$LOOKUP_REGEX/o) {
1070                    return $self->lookup($out);
1071            } else {
1072                    return $out;
1073            }
1074    
1075    }
1076    
1077    
1078    #
1079    #
1080    #
1081    
1082    =head1 INTERNAL METHODS
1083    
1084    Here is a quick list of internal methods, mostly useful to turn debugging
1085    on them (see L<LOGGING> below for explanation).
1086    
1087    =cut
1088    
1089    =head2 _eval
1090    
1091    Internal function to eval code without C<strict 'subs'>.
1092    
1093    =cut
1094    
1095    sub _eval {
1096            my $self = shift;
1097    
1098            my $code = shift || return;
1099    
1100            my $log = $self->_get_logger();
1101    
1102            no strict 'subs';
1103            my $ret = eval $code;
1104            if ($@) {
1105                    $log->error("problem with eval code [$code]: $@");
1106            }
1107    
1108            $log->debug("eval: ",$code," [",$ret,"]");
1109    
1110            return $ret || undef;
1111  }  }
1112    
1113    =head2 _sort_by_order
1114    
1115    Sort xml tags data structure accoding to C<order=""> attribute.
1116    
1117    =cut
1118    
1119    sub _sort_by_order {
1120            my $self = shift;
1121    
1122            my $va = $self->{'import_xml'}->{'indexer'}->{$a}->{'order'} ||
1123                    $self->{'import_xml'}->{'indexer'}->{$a};
1124            my $vb = $self->{'import_xml'}->{'indexer'}->{$b}->{'order'} ||
1125                    $self->{'import_xml'}->{'indexer'}->{$b};
1126    
1127            return $va <=> $vb;
1128    }
1129    
1130    =head2 _get_logger
1131    
1132    Get C<Log::Log4perl> object with a twist: domains are defined for each
1133    method
1134    
1135     my $log = $webpac->_get_logger();
1136    
1137    =cut
1138    
1139    sub _get_logger {
1140            my $self = shift;
1141    
1142            my $name = (caller(1))[3] || caller;
1143            return get_logger($name);
1144    }
1145    
1146    =head2 _x
1147    
1148    Convert string from UTF-8 to code page defined in C<import_xml>.
1149    
1150     my $text = $webpac->_x('utf8 text');
1151    
1152    =cut
1153    
1154    sub _x {
1155            my $self = shift;
1156            my $utf8 = shift || return;
1157    
1158            return $self->{'utf2cp'}->convert($utf8) ||
1159                    $self->_get_logger()->logwarn("can't convert '$utf8'");
1160    }
1161    
1162    #
1163    #
1164    #
1165    
1166    =head1 LOGGING
1167    
1168    Logging in WebPAC is performed by L<Log::Log4perl> with config file
1169    C<log.conf>.
1170    
1171    Methods defined above have different levels of logging, so
1172    it's descriptions will be useful to turn (mostry B<debug> logging) on
1173    or off to see why WabPAC isn't perforing as you expect it (it might even
1174    be a bug!).
1175    
1176    B<This is different from normal Log4perl behaviour>. To repeat, you can
1177    also use method names, and not only classes (which are just few)
1178    to filter logging.
1179    
1180    
1181    =head1 MEMORY USAGE
1182    
1183    C<low_mem> options is double-edged sword. If enabled, WebPAC
1184    will run on memory constraint machines (which doesn't have enough
1185    physical RAM to create memory structure for whole source database).
1186    
1187    If your machine has 512Mb or more of RAM and database is around 10000 records,
1188    memory shouldn't be an issue. If you don't have enough physical RAM, you
1189    might consider using virtual memory (if your operating system is handling it
1190    well, like on FreeBSD or Linux) instead of dropping to L<DBD::Deep> to handle
1191    parsed structure of ISIS database (this is what C<low_mem> option does).
1192    
1193    Hitting swap at end of reading source database is probably o.k. However,
1194    hitting swap before 90% will dramatically decrease performance and you will
1195    be better off with C<low_mem> and using rest of availble memory for
1196    operating system disk cache (Linux is particuallary good about this).
1197    However, every access to database record will require disk access, so
1198    generation phase will be slower 10-100 times.
1199    
1200    Parsed structures are essential - you just have option to trade RAM memory
1201    (which is fast) for disk space (which is slow). Be sure to have planty of
1202    disk space if you are using C<low_mem> and thus L<DBD::Deep>.
1203    
1204    However, when WebPAC is running on desktop machines (or laptops :-), it's
1205    highly undesireable for system to start swapping. Using C<low_mem> option can
1206    reduce WecPAC memory usage to around 64Mb for same database with lookup
1207    fields and sorted indexes which stay in RAM. Performance will suffer, but
1208    memory usage will really be minimal. It might be also more confortable to
1209    run WebPAC reniced on those machines.
1210    
1211    =cut
1212    
1213  1;  1;

Legend:
Removed from v.368  
changed lines
  Added in v.560

  ViewVC Help
Powered by ViewVC 1.1.26