fbc241b2c0afe0a1d0c0674d59c3c40669fe242b
[webpac2] / lib / WebPAC / Common.pm
1 package WebPAC::Common;
2
3 use warnings;
4 use strict;
5
6 use Log::Log4perl qw(get_logger :levels);
7
8 =head1 NAME
9
10 WebPAC::Common - internal methods called from other WebPAC modules
11
12 =head1 VERSION
13
14 Version 0.01
15
16 =cut
17
18 our $VERSION = '0.01';
19
20 #my $LOOKUP_REGEX = '\[[^\[\]]+\]';
21 #my $LOOKUP_REGEX_SAVE = '\[([^\[\]]+)\]';
22 my $LOOKUP_REGEX = 'lookup{[^\{\}]+}';
23 my $LOOKUP_REGEX_SAVE = 'lookup{([^\{\}]+)}';
24
25 =head1 SYNOPSYS
26
27 This module defines common functions, and is used as base for other, more
28 specific modules.
29
30  my $webpac = new WebPAC::Common(
31         filter => {
32                 'filter_name_1' => sub {
33                         # filter code
34                         return length($_);
35                 }, ...
36         },
37   }
38
39 =head1 FUNCTIONS
40
41 =head2 fill_in
42
43 Workhourse of all: takes record from in-memory structure of database and
44 strings with placeholders and returns string or array of with substituted
45 values from record.
46
47  my $text = $webpac->fill_in($rec,'v250^a');
48
49 Optional argument is ordinal number for repeatable fields. By default,
50 it's assume to be first repeatable field (fields are perl array, so first
51 element is 0).
52 Following example will read second value from repeatable field.
53
54  my $text = $webpac->fill_in($rec,'Title: v250^a',1);
55
56 This function B<does not> perform parsing of format to inteligenty skip
57 delimiters before fields which aren't used.
58
59 This method will automatically decode UTF-8 string to local code page
60 if needed.
61
62 =cut
63
64 sub fill_in {
65         my $self = shift;
66
67         my $log = $self->_get_logger();
68
69         my $rec = shift || $log->logconfess("need data record");
70         my $format = shift || $log->logconfess("need format to parse");
71         # iteration (for repeatable fields)
72         my $i = shift || 0;
73
74         $log->logdie("infitite loop in format $format") if ($i > ($self->{'max_mfn'} || 9999));
75
76         # FIXME remove for speedup?
77         $log->logconfess("need HASH as first argument!") if ($rec !~ /HASH/o);
78
79         if (utf8::is_utf8($format)) {
80                 $format = $self->_x($format);
81         }
82
83         my $found = 0;
84
85         my $eval_code;
86         # remove eval{...} from beginning
87         $eval_code = $1 if ($format =~ s/^eval{([^}]+)}//s);
88
89         my $filter_name;
90         # remove filter{...} from beginning
91         $filter_name = $1 if ($format =~ s/^filter{([^}]+)}//s);
92
93         # do actual replacement of placeholders
94         # repeatable fields
95         $format =~ s/v(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,$i,\$found)/ges;
96         # non-repeatable fields
97         $format =~ s/s(\d+)(?:\^(\w))?/$self->get_data(\$rec,$1,$2,0,\$found)/ges;
98
99         if ($found) {
100                 $log->debug("format: $format");
101                 if ($eval_code) {
102                         my $eval = $self->fill_in($rec,$eval_code,$i);
103                         return if (! $self->_eval($eval));
104                 }
105                 if ($filter_name && $self->{'filter'}->{$filter_name}) {
106                         $log->debug("filter '$filter_name' for $format");
107                         $format = $self->{'filter'}->{$filter_name}->($format);
108                         return unless(defined($format));
109                         $log->debug("filter result: $format");
110                 }
111                 # do we have lookups?
112                 if ($format =~ /$LOOKUP_REGEX/o) {
113                         $log->debug("format '$format' has lookup");
114                         return $self->lookup($format);
115                 } else {
116                         return $format;
117                 }
118         } else {
119                 return;
120         }
121 }
122
123
124 =head2 get_data
125
126 Returns value from record.
127
128  my $text = $self->get_data(\$rec,$f,$sf,$i,\$found);
129
130 Arguments are:
131 record reference C<$rec>,
132 field C<$f>,
133 optional subfiled C<$sf>,
134 index for repeatable values C<$i>.
135
136 Optinal variable C<$found> will be incremeted if there
137 is field.
138
139 Returns value or empty string.
140
141 =cut
142
143 sub get_data {
144         my $self = shift;
145
146         my ($rec,$f,$sf,$i,$found) = @_;
147
148         if ($$rec->{$f}) {
149                 return '' if (! $$rec->{$f}->[$i]);
150                 no strict 'refs';
151                 if ($sf && $$rec->{$f}->[$i]->{$sf}) {
152                         $$found++ if (defined($$found));
153                         return $$rec->{$f}->[$i]->{$sf};
154                 } elsif ($$rec->{$f}->[$i]) {
155                         $$found++ if (defined($$found));
156                         # it still might have subfield, just
157                         # not specified, so we'll dump all
158                         if ($$rec->{$f}->[$i] =~ /HASH/o) {
159                                 my $out;
160                                 foreach my $k (keys %{$$rec->{$f}->[$i]}) {
161                                         $out .= $$rec->{$f}->[$i]->{$k}." ";
162                                 }
163                                 return $out;
164                         } else {
165                                 return $$rec->{$f}->[$i];
166                         }
167                 }
168         } else {
169                 return '';
170         }
171 }
172
173
174 =head1 INTERNAL METHODS
175
176 Here is a quick list of internal methods, mostly useful to turn debugging
177 on them (see L<LOGGING> below for explanation).
178
179 =cut
180
181 =head2 _eval
182
183 Internal function to eval code without C<strict 'subs'>.
184
185 =cut
186
187 sub _eval {
188         my $self = shift;
189
190         my $code = shift || return;
191
192         my $log = $self->_get_logger();
193
194         no strict 'subs';
195         my $ret = eval $code;
196         if ($@) {
197                 $log->error("problem with eval code [$code]: $@");
198         }
199
200         $log->debug("eval: ",$code," [",$ret,"]");
201
202         return $ret || undef;
203 }
204
205 =head2 _sort_by_order
206
207 Sort xml tags data structure accoding to C<order=""> attribute.
208
209 =cut
210
211 sub _sort_by_order {
212         my $self = shift;
213
214         my $va = $self->{'import_xml'}->{'indexer'}->{$a}->{'order'} ||
215                 $self->{'import_xml'}->{'indexer'}->{$a};
216         my $vb = $self->{'import_xml'}->{'indexer'}->{$b}->{'order'} ||
217                 $self->{'import_xml'}->{'indexer'}->{$b};
218
219         return $va <=> $vb;
220 }
221
222 =head2 _x
223
224 Convert string from UTF-8 to code page defined in C<import_xml>.
225
226  my $text = $webpac->_x('utf8 text');
227
228 Default application code page is C<ISO-8859-2>. You will probably want to
229 change that when creating new instance of object based on this one.
230
231 =cut
232
233 sub _x {
234         my $self = shift;
235         my $utf8 = shift || return;
236
237         # create UTF-8 convertor for import_xml files
238         $self->{'utf2cp'} ||= Text::Iconv->new('UTF-8' ,$self->{'code_page'} || 'ISO-8859-2');
239
240         return $self->{'utf2cp'}->convert($utf8) ||
241                 $self->_get_logger()->logwarn("can't convert '$utf8'");
242 }
243
244 =head2 _init_logger
245
246 This function will init C<Log::Log4perl> using provided configuration file.
247
248   $webpac->_init_logger('/path/to/log.conf');
249
250 If no path to configuration file is given, dummy empty configuration
251 will be create.
252
253 =cut
254
255 sub _init_logger {
256         my $self = shift;
257         my $file = shift;
258         if ($file) {
259                 Log::Log4perl->init($file);
260         } else {
261                 my $conf = q( );
262                 Log::Log4perl->init( \$conf );
263         }
264 }
265
266
267 =head2 _get_logger
268
269 Get C<Log::Log4perl> object with a twist: domains are defined for each
270 method
271
272  my $log = $webpac->_get_logger();
273
274 =cut
275
276 sub _get_logger {
277         my $self = shift;
278
279         $self->{'_logger_ok'} ||= $self->_init_logger;
280
281         my $name = (caller(1))[3] || caller;
282         return get_logger($name);
283 }
284
285
286 =head1 LOGGING
287
288 Logging in WebPAC is performed by L<Log::Log4perl> with config file
289 C<log.conf>.
290
291 Methods defined above have different levels of logging, so
292 it's descriptions will be useful to turn (mostry B<debug> logging) on
293 or off to see why WabPAC isn't perforing as you expect it (it might even
294 be a bug!).
295
296 B<This is different from normal Log4perl behaviour>. To repeat, you can
297 also use method names, and not only classes (which are just few)
298 to filter logging.
299
300