File Coverage

blib/lib/HTML/Macro.pm
Criterion Covered Total %
statement 402 531 75.7
branch 200 316 63.2
condition 66 103 64.0
subroutine 31 37 83.7
pod 1 33 3.0
total 700 1020 68.6


line stmt bran cond sub pod time code
1             # HTML::Macro; Macro.pm
2             # Copyright (c) 2001,2002 Michael Sokolov and Interactive Factory. Some rights
3             # reserved. This program is free software; you can redistribute it and/or
4             # modify it under the same terms as Perl itself.
5              
6             package HTML::Macro;
7              
8 1     1   1154 use strict;
  1         2  
  1         47  
9 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %file_cache %expr_cache);
  1         2  
  1         144  
10              
11             require Exporter;
12             require AutoLoader;
13              
14             @ISA = qw(Exporter AutoLoader);
15             # Items to export into callers namespace by default. Note: do not export
16             # names by default without a very good reason. Use EXPORT_OK instead.
17             # Do not simply export all your public functions/methods/constants.
18             @EXPORT = qw(
19            
20             );
21             $VERSION = '1.29';
22              
23              
24             # Preloaded methods go here.
25              
26 1     1   663 use HTML::Macro::Loop;
  1         3  
  1         64  
27 1     1   6 use Cwd;
  1         2  
  1         8039  
28              
29             # Autoload methods go after =cut, and are processed by the autosplit program.
30              
31             # don't worry about hi-bit characters
32             my %char2htmlentity =
33             (
34             '&' => '&',
35             '>' => '>',
36             '<' => '<',
37             '"' => '"',
38             );
39              
40             sub html_encode
41             {
42 47     47 0 133 $_[0] =~ s/([&><\"])/$char2htmlentity{$1}/g;
43 47         101 return $_[0];
44             }
45              
46             sub collapse_whitespace
47             {
48 21     21 1 69 my ($buf, $blank_lines_only) = @_;
49 21         43 my $out = '';
50 21         30 my $pos = 0;
51 21         111 my $protect_whitespace = '';
52 21         159 while ($buf =~ m{(< \s*
53             (/?textarea|/?pre|/?quote)(_?)
54             (?: (?: \s+\w+ \s* = \s* "[^\"]*") | # quoted attrs
55             (?: \s+\w+ \s* =[^>\"]*) | # attrs w/ no quotes
56             (?: \s+\w+) # attrs with no value
57             ) *
58             >)}sgix)
59             {
60 8         44 my ($match, $tag, $underscore) = ($1, lc $2, $3);
61 8         17 my $nextpos = pos $buf;
62 8 100       21 if ($protect_whitespace)
63             {
64 4         15 $out .= substr ($buf, $pos, $nextpos - $pos);
65             }
66             else
67             {
68 4         15 my $chunk = substr ($buf, $pos, $nextpos - $pos);
69 4 100       12 if (! $blank_lines_only) {
70             # collapse adj white space on a single line
71 2         30 $chunk =~ s/\s+/ /g;
72             }
73             # remove blank lines and trailing whitespace; use UNIX line endings
74 4         110 $chunk =~ s/\s*[\r\n]+/\n/sg;
75 4         15 $out .= $chunk;
76             }
77 8 100 33     64 if ($tag eq "/$protect_whitespace") {
    50          
78 4         9 $protect_whitespace = '';
79             } elsif (! $protect_whitespace && $tag !~ m|^/|) {
80 4         9 $protect_whitespace = $tag;
81             }
82 8         167 $pos = $nextpos;
83             }
84              
85             # process trailing chunk
86 21 100       53 $buf = substr ($buf, $pos) if $pos;
87 21 100       48 if (! $blank_lines_only) {
88             # collapse adj white space on a single line
89 2         14 $buf =~ s/\s+/ /g;
90             }
91             # remove blank lines and trailing whitespace; use UNIX line endings
92 21         369 $buf =~ s/\s*[\r\n]+/\n/sg;
93 21         76 $out .= $buf;
94             }
95              
96             sub process_cf_quotes
97             {
98 54     54 0 77 my ($pbuf) = @_;
99 54         158 $$pbuf =~ s///sg;
100             # These will be valid XML:
101 54         156 $$pbuf =~ s///sg;
102             }
103              
104             sub doloop ($$)
105             {
106 0     0 0 0 my ($self, $loop_id, $loop_body, $element) = @_;
107              
108 0 0       0 if ($self->{'@attr'}->{'debug'}) {
109 0         0 print STDERR "HTML::Macro: processing loop $loop_id\n";
110             }
111 0         0 my $p = $self;
112 0         0 my $loop;
113 0         0 while ($p) {
114 0         0 $loop = $$p{$loop_id};
115 0 0       0 last if $loop;
116             # look for loops in outer scopes
117 0         0 $p = $p->{'@parent'};
118 0 0       0 last if !$p;
119 0 0       0 if ($p->isa('HTML::Macro::Loop'))
120             {
121 0         0 $p = $p->{'@parent'};
122 0 0       0 die if ! $p;
123             }
124             }
125 0 0       0 if (! $loop ) {
126 0         0 $self->warning ("no match for loop id=$loop_id");
127 0         0 return '';
128             }
129 0 0 0     0 if (!ref $loop || ! $loop->isa('HTML::Macro::Loop'))
130             {
131 0         0 $self->error ("doloop: $loop (substitution for loop id \"$loop_id\") is not a HTML::Macro::Loop!");
132             }
133 0         0 my $separator;
134 0 0 0     0 if ($element =~ /\bseparator="([^\"]*)"/ ||
135             $element =~ /\bseparator=(\S+)/)
136             {
137 0         0 $separator = $1;
138             }
139 0         0 my $separator_final;
140 0 0 0     0 if ($element =~ /\bseparator_final="([^\"]*)"/ ||
141             $element =~ /\bseparator_final=(\S+)/)
142             {
143 0         0 $separator_final = $1;
144             }
145 0         0 my $collapse = ($element =~ /\scollapse\b/);
146 0         0 $loop->{'@dynamic'} = $self; # allow dynamic scoping of macros !
147 0         0 $loop_body = $loop->doloop ($loop_body, $separator, $separator_final, $collapse);
148             #$loop_body = $self->dosub ($loop_body);
149 0         0 return $loop_body;
150             }
151              
152             sub doeval ($$)
153             {
154 6     6 0 24 my ($self, $attr, $attrval, $body) = @_;
155 6 50       34 if ($self->{'@attr'}->{'debug'}) {
156 0         0 print STDERR "HTML::Macro: processing eval: { $attr $attrval }\n";
157             }
158 6         10 my $htm;
159 6 100       17 if ($body) {
160 3         18 $htm = new HTML::Macro;
161 3         56 $htm->{'@parent'} = $self;
162 3         38 $htm->{'@body'} = $body;
163 3         15 my @incpath = @ {$self->{'@incpath'}};
  3         56  
164 3         89 $htm->{'@incpath'} = \@incpath; # make a copy of incpath
165 3         21 $htm->{'@attr'} = $self->{'@attr'};
166 3         36 $htm->{'@cwd'} = $self->{'@cwd'};
167             } else {
168 3         6 $htm = $self;
169             }
170 6         24 my $package = $self->{'@caller_package'};
171 6         11 my $result;
172 6 100       37 if ($attr eq 'expr') {
    50          
173 5         2056 $result = eval " & {package $package; sub { $attrval } } (\$htm)";
174             } elsif ($attr eq 'func') {
175 1         9 $package = $::{$package . '::'};
176 1         10 my $func = $$package{$attrval};
177 1         6 eval {
178 1         2 $result = & {$func} ($htm);
  1         6  
179             };
180             }
181 6 50       494 if ($@) {
182 0         0 $self->error ("error evaluating $attr '$attrval': $@");
183             }
184 6   100     92 return $result || ''; # inhibit undefined warnings
185             }
186              
187             sub case_fold_match
188             {
189 87     87 0 159 my ($hash, $key) = @_;
190 87 50       443 my $val =
    50          
    0          
    50          
    100          
    100          
191             exists($$hash{$key}) ? (defined($$hash{$key}) ? $$hash{$key} : '')
192             : ( exists ($$hash{lc $key}) ? (defined($$hash{lc $key}) ? $$hash{lc $key} : '')
193             : (exists $$hash{uc $key} ? (defined($$hash{uc $key}) ? $$hash{uc $key} : '')
194             : undef) );
195 87         202 return $val;
196             }
197              
198             sub match_token ($$)
199             {
200 84     84 0 147 my ($self, $var) = @_;
201              
202 84 50       236 if ($self->{'@attr'}->{'debug'}) {
203 0         0 print STDERR "HTML::Macro: matching token $var\n";
204             }
205             # these are the two styles we've used
206 84         88 my $val;
207 84         317 my $dynamic = $self->{'@dynamic'};
208 84         289 while ($self)
209             {
210             # ovalues is also used to store request variables so they override
211             # data fetched (later in the processing of a request) from the database
212 84 100       373 $val = &case_fold_match ($self->{'@ovalues'}, $var) if $self->{'@ovalues'};
213 84 100       430 $val = &case_fold_match ($self, $var) if ! defined ($val);
214 84 100       275 return $val if (defined ($val));
215              
216             # include outer loops in scope
217 6         20 $self = $self->parent();
218             }
219             ## If no lexically-scoped variable matched, check dynamically scoped variables
220             # This has the effect of allowing unrelated (orthogonal) loops to be nested
221 6 50       15 if ($dynamic) {
222 0         0 return &match_token ($dynamic, $var);
223             }
224 6         98 return undef;
225             }
226              
227             sub dosub ($$)
228             {
229 304     304 0 713 my ($self, $html) = @_;
230             # replace any "word" surrounded by single or double hashmarks: "##".
231             # Warning: two tokens of this sort placed right next to each other
232             # are indistinguishable from a single token: #PAGE##NUM# could be one
233             # token or two: #PAGE# followed by #NUM#. This code breaks this ambiguity
234             # by being greedy. Probably should change it to be parsimonious and
235             # disallow hashmarks as part of tokens...
236              
237             # NOTE: "word" may also be preceded by a single '@' now; this exposes
238             # internal values (like @include_body) for substitution
239              
240 304         362 my $lastpos = 0;
241 304 100       1173 if ($html =~ /((\#{1,2})(\@?\w+)\2)/sg )
242             {
243 67         145 my ( $matchpos, $matchlen ) = (pos ($html), length ($1));
244 67         662 my $result = substr ($html, 0, $matchpos - $matchlen);
245 67         81 while (1)
246             {
247 76         179 my $quoteit = substr($2,1);
248 76         129 my $var = $3;
249             #warn "xxx $quoteit, $var: ($1,$2); (pos,len) = $matchpos, $matchlen";
250 76         217 my $val = $self->match_token ($var);
251 76 100       355 $result .= defined ($val) ?
    100          
252             ($quoteit ? &html_encode($val) : $val) : ($2 . $var . $2);
253 76         101 $lastpos = $matchpos;
254 76 100       209 if ($html !~ /\G.*?((\#{1,2})(\@?\w+)\2)/sg)
255             {
256 67         113 $result .= substr ($html, $lastpos);
257 67         208 return $result;
258             }
259 9         19 ( $matchpos, $matchlen ) = (pos ($html), length ($1));
260 9         18 $result .= substr ($html, $lastpos,
261             $matchpos - $matchlen - $lastpos);
262             }
263             }
264 237         970 return $html;
265             }
266              
267             sub findfile
268             # follow the include path, looking for the file and return an open file handle
269             {
270 32     32 0 150 my ($self, $fname) = @_;
271 32 50       352 if (substr($fname,0,1) eq '/') {
272 0         0 my @stat = stat $fname;
273 0 0       0 return ($fname, $stat[9]) if @stat;
274             } else {
275 32         47 my @incpath = @ {$self->{'@incpath'}};
  32         235  
276 32 50       313 push (@incpath, $self->{'@cwd'} . '/') unless ($self->{'@no_local_incpa\
277             th'});
278 32         100 while (@incpath)
279             {
280 36         73 my $dir = pop @incpath;
281 36         2717 my @stat = stat $dir . $fname;
282 36 100       286 return ($dir . $fname, $stat[9]) if @stat;
283             }
284             }
285 0         0 $self->error ("Cannot find file $fname, incpath=" .
286 0         0 join (',',@ {$self->{'@incpath'}})
287             . ", cwd=" . $self->{'@cwd'});
288 0         0 return ();
289             }
290              
291             sub openfile
292             # open the file, change directories to the file's directory, remembering where
293             # we came from, and add the file's directory to incpath
294             {
295 32     32 0 74 my ($self, $path) = @_;
296 32         47 my @incpath = @ {$self->{'@incpath'}};
  32         90  
297              
298 32         271 my $cwd = $self->{'@cwd'};
299              
300 32 50       1750 open (FILE, $path) || $self->error ("Cannot open '$path': $!");
301              
302 32 50       131 if ($self->{'@attr'}->{'debug'}) {
303 0         0 print STDERR "HTML::Macro: opening $path, incpath=@incpath, cwd=$cwd";
304             }
305 32         71 $self->{'@file'} = $path;
306              
307             # we will change directories so relative includes work
308             # remember where we are so we can get back here
309              
310 32         43 push @ {$self->{'@cdpath'}}, $cwd;
  32         97  
311              
312 32         45 my ($dir, $fname);
313 32 50       365 if ($path =~ m|(.*)/([^/])+$|) {
314 32         285 ($dir, $fname) = ($1, $2);
315             } else {
316 0         0 ($dir, $fname) = ('', $path);
317             }
318              
319             # add our current directory to incpath so includes from other directories
320             # will still look here - if $dir is not an absolute path. Recognizes
321             # drive letters even if this is !Windows. oh well
322              
323 32 50       313 $dir = "$cwd/$dir" if ($dir !~ m|^([A-Za-z]:)?/|);
324 32         68 $dir =~ s|//+|/|g; # remove double slashes
325              
326 32         44 push @ {$self->{'@incpath'}}, $dir . '/';
  32         159  
327              
328             # chdir to where file is
329             # chdir $dir || $self->error ("openfile can't chdir $dir (opening $path): $!");
330              
331             #print STDERR "openfile: \@cwd=", $dir, "\n";
332 32         146 $self->{'@cwd'} = $dir;
333              
334 32         122 return *FILE{IO};
335             }
336              
337             sub dodefine
338             {
339 13     13 0 34 my ($self, $name, $val, $global) = @_;
340              
341             # double-evaluation for define:
342 13         40 $val = $self->process_buf ($val);
343              
344 13 100       41 if ($global) {
345 1         42 $self->set_global ($name, $self->dosub($val));
346             } else {
347 12         27 $self->set ($name, $self->dosub($val));
348             }
349             }
350              
351             sub doinclude ($$$)
352             {
353 14     14 0 55 my ($self, $include, $body) = @_;
354 14         23 my $lastpos = 0;
355 14         39 my $file = $self->{'@file'};
356 14         39 $include = $self->dosub ($include);
357 14 50       147 if ($include !~ m||sgi)
358             {
359 0         0 $self->error ("bad include ($include)");
360             }
361 14         44 my ($filename, $asis) = ($1, $2);
362 14         18 my $out;
363 14 50       65 if ($asis)
    100          
364             {
365             #open (ASIS, $filename) || $self->error ("can't open $filename: $!");
366            
367 0         0 my $buf = $self->readfile ($filename);
368              
369 0         0 my $lastdir = pop @ {$self->{'@cdpath'}};
  0         0  
370 0 0       0 if ($lastdir)
371             {
372             # chdir $lastdir ;
373 0         0 $self->{'@cwd'} = $lastdir;
374             }
375             else {
376 0         0 delete $self->{'@cwd'};
377             }
378             # we pushed the included file's directory into incpath when
379             # opening it (see openfile); now pop it - we would usu. do this in
380             # process
381 0         0 pop @ {$self->{'@incpath'}};
  0         0  
382              
383 0         0 $out = $buf;
384             }
385             elsif ($body)
386             {
387 5         12 my $inc_body = $self->{'@include_body'};
388 5         10 $self->{'@include_body'} = $body;
389 5         17 $out = $self->process ($filename);
390 5         12 $self->{'@include_body'} = $inc_body;
391             }
392             else
393             {
394 9         47 $out = $self->process ($filename);
395             }
396 14         24 $self->{'@file'} = $file;
397 14         104 return $out;
398             }
399              
400             sub attr_backwards_compat
401             {
402 89     89 0 161 my ($self) = @_;
403 89         147 my $attr = $self->{'@attr'};
404 89         281 foreach my $key ('debug', 'collapse_whitespace', 'collapse_blank_lines',
405             'precompile')
406             {
407 356 100       1641 $$attr{$key} = $$self{'@' . $key} if defined $$self{'@' . $key};
408             }
409             }
410              
411             sub eval_if_attrs
412             {
413 66     66 0 136 my ($self, $attrs, $match, $tag, $nextpos, $package) = @_;
414 66         87 my $true;
415 66 100       558 if ($attrs =~ /^\s* expr \s* = \s* "([^\"]*)" \s*$/six)
    100          
416             {
417 57   100     198 my $expr = $1 || '';
418 57         119 $expr = $self->dosub ($expr);
419 57         4449 $true = eval "{ package $package; $expr }";
420 57 50       408 if ($@) {
421 0         0 $self->error ("error evaluating $match (after substitutions: $expr): $@",
422             $nextpos);
423             }
424             }
425             elsif ($attrs =~ /^\s* (n?)def \s* = \s* "([^\"]*)" \s*$/six)
426             {
427 8         185 my $ndef = $1;
428 8   50     28 my $token = $2 || '';
429 8         20 $true = $self->match_token ($token);
430 8 50       24 $true = ! $true if $ndef;
431             }
432             else
433             {
434 1         7 $self->error ("error parsing '$tag' attributes: $attrs",
435             $nextpos);
436             }
437 65         257 return $true;
438             }
439              
440             sub process_buf ($$)
441             {
442 57     57 0 325 my ($self, $buf) = @_;
443 57 100       153 return '' if ! $buf;
444 54         504 my $out = '';
445 54         94 my @tag_stack = ();
446 54         69 my $pos = 0;
447 54         61 my $quoting = 0;
448 54         51 my $looping = 0;
449 54         95 my $true = 1;
450 54         76 my $emitting = 1;
451 54         61 my $active = 1;
452              
453 54         112 &attr_backwards_compat;
454              
455             # remove CFM-style quotes:
456 54         235 &process_cf_quotes (\$buf);
457              
458 54 100       287 my $underscore = $self->{'@attr'}->{'precompile'} ? '_' : '';
459 54 50       147 print STDERR "Entering process_buf: $buf\n" if ($self->{'@attr'}->{'debug'});
460              
461 54 100       187 $self->get_caller_info if ! $self->{'@caller_package'};
462 54         87 my $package = $self->{'@caller_package'};
463              
464 54         1281 while ($buf =~ m{(< \s*
465             (/?loop|/?if|/?include|/?else|/?quote|/?eval|elsif|/?define)$underscore(/?)
466             ( (?: \s+\w+ \s* = \s* "[^\"]*") | # quoted attrs
467             (?: \s+\w+ \s* =[^>\"]) | # attrs w/ no quotes
468             (?: \s+\w+) # attrs with no value
469             ) * \s*
470             (/?)>)}sgix)
471             {
472 279         2011 my ($match, $tag, $slash, $attrs, $slash2) = ($1, lc $2, $3, $4, $5);
473 279         1032 my $nextpos = (pos $buf) - (length ($&));
474 279 100       996 $slash = $slash2 if ! $slash; # allow normal XML style
475 279 50 66     1158 if (! $slash && $tag eq 'elsif')
476             {
477 0         0 $slash = 1;
478 0         0 $self->warning ("missing trailing slash for singleton tag $tag", $nextpos);
479             }
480 279 100       570 $tag .= '/' if $slash;
481 279   100     829 $emitting = $true && ! $looping;
482 279   100     1404 $active = $true && !$quoting && !$looping;
483 279 100       865 if ($active)
    100          
    100          
484             {
485 159         607 $out .= $self->dosub
486             (substr ($buf, $pos, $nextpos - $pos));
487             # skip over the matched tag; handling any state changes below
488 159         556 $pos = $nextpos + length($&);
489             }
490             elsif ($quoting)
491             {
492             # ignore everything except quote tags
493 8 100       40 if ($tag eq '/quote')
    50          
494             {
495 5         13 my $matching_tag = pop @tag_stack;
496 5 50       17 $self->error ("no match for tag 'quote'", $nextpos)
497             if (! $matching_tag);
498 5         14 my ($start_tag, $attr) = @$matching_tag;
499 5 50       18 $self->error ("start tag $start_tag ends with end tag 'quote'",
500             $nextpos)
501             if ($start_tag ne 'quote');
502 5 50 33     36 if ($emitting && !$attr)
503             {
504             # here we'ved popped out of a bunch of possibly nested
505             # quotes: !$attr means this is the outermost one and
506             # $emitting means we're neither in a false condition nor
507             # are we in an accumulating loop (which will be processed
508             # later in a recursion).
509            
510             # the next line says to emit the tag if we are
511             # in a "preserved" quote:
512 5 100       17 my $endpos = ($quoting == 2) ? ($nextpos + length($match))
513             : $nextpos;
514 5         63 $out .= substr ($buf, $pos, $endpos - $pos);
515 5         10 $pos = $nextpos + length($match);
516             }
517 5         14 $quoting = $attr;
518             }
519             elsif ($tag eq 'quote')
520             {
521 0         0 push @tag_stack, [ 'quote', $quoting, $nextpos ];
522             }
523 8         90 next;
524             }
525             elsif (!$looping)
526             # if looping, just match tags until we find the right matching
527             # end loop; don't process anything except quotes, since we might
528             # quote a loop tag!
529             # Rather, leave that for a recursion.
530             {
531             # die if $true; # debugging test
532             # if we're in a false conditional, don't emit anything and skip over
533             # the matched tag
534 94         144 $pos = $nextpos + length($match);
535             }
536 271 100 66     2567 if ($tag eq 'loop' || $tag eq 'eval' || $tag eq 'include' || $tag eq 'define')
      100        
      100        
537             # loop and eval are similar in their lexical force - both are block-level
538             # tags that force embedded scopes. Therefore their contents are processed
539             # in a nested evaluation, and not here.
540             # The effect on eval is that an eval nested in a loop
541             {
542 15         31 my ($attr, $attrval);
543 15 50       62 if ($tag eq 'loop') {
    100          
    100          
    50          
544 0 0 0     0 $match =~ /id="([^\"]*)"/ || $match =~ /id=(\S+)/ ||
545             $self->error ("loop tag '$match' has no id", $nextpos);
546 0         0 $attr = $1;
547 0         0 $attrval = $match;
548             } elsif ($tag eq 'eval') {
549 5 50       60 $match =~ /(expr|func)="([^\"]*)"/ ||
550             $self->error ("eval tag '$match' has no expr or func", $nextpos);
551 5         20 ($attr, $attrval) = ($1, $2);
552             } elsif ($tag eq 'include') {
553 5         23 $attr = $match;
554 5         10 $attrval = undef;
555             } elsif ($tag eq 'define') {
556 5 50       33 $match =~ /(name)="([^\"]*)"/ ||
557             $self->error ("define tag '$match' has no name", $nextpos);
558 5         17 $attr = $match;
559 5         9 $attrval = $2;
560             }
561 15         142 push @tag_stack, [$tag, $attr, $nextpos, $attrval];
562 15         27 ++$looping;
563 15         169 next;
564             }
565 256 100 66     2701 if ($tag eq '/loop' || $tag eq '/eval' || $tag eq '/include' || $tag eq '/define')
      100        
      100        
566             {
567 15         34 my $matching_tag = pop @tag_stack;
568 15 50       34 $self->error ("no match for tag '$tag'", $nextpos)
569             if ! $matching_tag;
570 15         113 my ($start_tag, $attr, $tag_pos, $attrval) = @$matching_tag;
571 15 50       41 $self->error ("start tag '$start_tag' (at char $tag_pos) ends with end tag '$tag'",
572             $nextpos)
573             if ($start_tag ne substr ($tag, 1));
574              
575 15         18 -- $looping;
576 15 100 66     449 if ($true && !$looping && !$quoting)
      66        
577             {
578 14         268 my $body = substr ($buf, $pos, $nextpos-$pos);
579 14 50       60 if ($tag eq '/loop') {
    100          
    100          
    50          
580 0         0 $attr = $self->dosub ($attr);
581 0         0 $out .= $self->doloop ($attr, $body, $attrval);
582             } elsif ($tag eq '/eval') {
583             # tag=eval
584 4         13 $attrval = $self->dosub ($attrval);
585 4         33 $out .= $self->doeval ($attr, $attrval, $body);
586             } elsif ($tag eq '/include') {
587 5         30 my $incbody = #eval {
588             $self->process_buf ($body);
589             #};
590 5 50       15 &error ("error processing included file $attr: $@")
591             if ($@);
592 5         23 $out .= $self->doinclude ($attr, $incbody);
593             } elsif ($tag eq '/define') {
594 5         13 $self->dodefine($attrval, $body);
595             }
596 14         62 $pos = $nextpos + length($match);
597             }
598 15         160 next;
599             }
600 241 100       747 if ($tag eq 'quote')
601             {
602 5         25 push @tag_stack, ['quote', $quoting, $nextpos];
603 5 100       22 if ($match =~ /preserve="([^\"]*)"/)
604             {
605 2   100     18 my $expr = $1 || '';
606 2         12 $expr = $self->dosub ($expr);
607 2         157 my $result = eval "{ package $package; $expr }";
608 2 100       9 if ($result)
609             {
610 1         10 $quoting = 2;
611             # why ?
612 1 50       6 $pos = $nextpos if !$looping;
613             }
614             else
615             {
616 1 50       13 if ($match =~ /expr="([^\"]*)"/)
617             {
618 1   50     6 $expr = $1 || '';
619 1         7 $expr = $self->dosub ($expr);
620 1         76 $result = eval "{ package $package; $expr }";
621 1 50       5 if ($result)
622             {
623 1         3 $quoting = 1;
624             }
625             } else {
626 0         0 $quoting = 1;
627             }
628             }
629 2 50       8 if ($@) {
630 0         0 $self->error ("error evaluating $match (after substitutions: $expr): $@",
631             $nextpos);
632             }
633             }
634             else {
635 3         8 $quoting = 1;
636             }
637 5         63 next;
638             }
639 236 50       571 if ($tag eq '/quote')
640             {
641 0         0 my $matching_tag = pop @tag_stack;
642 0 0       0 $self->error ("no match for tag '$tag'", $nextpos)
643             if ! $matching_tag;
644 0         0 my ($start_tag, $attr, $tag_pos) = @$matching_tag;
645 0 0       0 $self->error ("start tag '$start_tag' ends with end tag '$tag'",
646             $nextpos)
647             if ($start_tag ne substr ($tag, 1));
648 0         0 next;
649             }
650 236 100       465 next if $looping; # ignore the rest of these tags while looping
651              
652 234 100 100     1048 if (substr($tag, 0, 1) eq '/')
    100          
    100          
    100          
653             # process end tags; match w/start tags and handle state changes
654             {
655 90         125 my $matching_tag = pop @tag_stack;
656 90 50       176 $self->error ("no match for tag '$tag'", $nextpos)
657             if ! $matching_tag;
658 90         163 my ($start_tag, $attr, $tag_pos) = @$matching_tag;
659 90 100 100     520 if ($tag eq '/if' && $start_tag eq 'elsif') {
660 1         4 $matching_tag = pop @tag_stack;
661 1 50       8 $self->error ("no match for tag '/if'", $nextpos)
662             if ! $matching_tag;
663 1         6 ($start_tag, $attr, $tag_pos) = @$matching_tag;
664             }
665 90 50       214 $self->error ("start tag '$start_tag' ends with end tag '$tag'",
666             $nextpos)
667             if ($start_tag ne substr ($tag, 1));
668              
669 90 100       2496 if ($start_tag eq 'if')
670             {
671 65         162 $true = $attr;
672             }
673             }
674             elsif ($tag eq 'if')
675             {
676 66         232 push @tag_stack, ['if', $true, $nextpos] ;
677 66 100       143 if ($active) {
678 61         155 $true = $self->eval_if_attrs
679             ($attrs, $match, $tag, $nextpos, $package);
680             }
681             }
682             elsif ($tag eq 'elsif/') {
683 13         26 my $top = $tag_stack[$#tag_stack];
684 13         25 my $last_tag = $$top[0];
685 13 100       32 if ($last_tag eq 'if') {
    50          
686 9         39 $top = ['elsif', $$top[1], $true];
687 9         279 push @tag_stack, $top;
688             } elsif ($last_tag eq 'elsif') {
689             # if *any* of the foregoing if/elsif clauses have been true
690 4   100     15 $$top[2] ||= $true;
691             } else {
692 0         0 $self->error (" not in ", $nextpos);
693             }
694 13 100 66     192 if (!$looping && $$top[1] && $$top[2]) {
    100 100        
      66        
      66        
695             # if an earlier if/elsif was true, and we are not overshadowed
696             # by an enclosing scope, this one is false.
697 3         8 $true = 0;
698             }
699             elsif (!$looping && $$top[1] && ! $$top[2]) {
700             # if all previous if/elsifs were false, this one might still be true
701 5         13 $true = $self->eval_if_attrs ($attrs, $match, $tag, $nextpos, $package);
702             }
703             }
704             elsif ($tag eq 'else/' || $tag eq 'else')
705             {
706 45         87 my $top = $tag_stack[$#tag_stack];
707 45         72 my $last_tag = $$top[0];
708              
709             # if we are embedded in a false condition, it overrides us:
710             # don't change false based on this else. Also, don't evaluate
711             # anything while looping: postpone for recursion.
712              
713 45 100       116 if ($last_tag eq 'elsif') {
    50          
714            
715 8   100     27 my $if_elsif_any_true = $$top[2] || $true;
716 8         13 pop @tag_stack;
717 8         10 my $top = $tag_stack[$#tag_stack];
718             # check falsitude of enclosing scope
719 8 100 66     43 $true = (! $looping && ! $if_elsif_any_true) if $$top[1];
720             } elsif ($last_tag eq 'if') {
721 37 50 33     208 $true = ! $true if (! $looping && $$top[1]);
722             } else {
723 0         0 $self->error (" not in ", $nextpos);
724             }
725              
726 45 100       219 push @tag_stack, ['else', $true] if $tag eq 'else';
727             }
728              
729             # skip these tags if false since they don't effect the truth value:
730 233 100       1304 next if !$active;
731              
732 139 100       1677 if ($tag eq 'include/')
    100          
    100          
733             {
734             # singleton (empty) include
735 9         54 $out .= $self->doinclude ($match);
736             }
737             elsif ($tag eq 'define/')
738             {
739 8 50       71 $match =~ /name="([^\"]*)"/ ||
740             $self->error ("no name attr for define tag in '$match'",
741             $nextpos);
742 8         24 my ($name) = $1;
743 8 50       55 $match =~ /value="([^\"]*)"/ ||
744             $self->error ("no value attr for empty define tag in '$match'",
745             $nextpos);
746 8         18 my ($val) = $1;
747 8         27 my ($global) = ($match =~ / global(?:="global")?/);
748 8         41 $self->dodefine($name, $val, $global);
749             }
750             elsif ($tag eq 'eval/') {
751 2 100       17 if ($match =~ /expr="([^\"]*)"/) {
    50          
752 1         5 my $expr = $self->dosub ($1);
753 1         5 $self->doeval ('expr', $expr);
754             } elsif ($match =~ /func="(\w+)"/) {
755 1         25 $self->doeval ('func', $1);
756             } else {
757 0         0 $self->error ("eval tag must have valid expr or func attribute", $nextpos);
758             }
759             }
760             }
761             # process trailer
762 53         148 while (@tag_stack)
763             {
764 0         0 my $tag = pop @tag_stack;
765 0         0 $self->error ("EOF while still looking for close tag for " . $$tag[0]
766             . '(' . $$tag[1] .')', $$tag[2]);
767             }
768 53         235 $out .= $self->dosub (substr ($buf, $pos));
769             # remove extra whitespace
770              
771 53 100       287 if ($self->{'@attr'}->{'collapse_whitespace'})
    100          
772             {
773             # collapse adjacent white space
774 1         11 $out = &collapse_whitespace ($out, undef);
775             }
776             elsif ($self->{'@attr'}->{'collapse_blank_lines'})
777             {
778             # remove blank lines
779 12         391 $out = &collapse_whitespace ($out, 1);
780             }
781 53 50       214 print STDERR "Exiting process_buf: $out\n" if ($self->{'@attr'}->{'debug'});
782 53         183 return $out;
783             }
784              
785             sub readfile
786             {
787 32     32 0 150 my ($self, $fname) = @_;
788              
789 32 50       115 $self->{'@cwd'} = cwd if ! $self->{'@cwd'};
790 32         208 my $cwd = $self->{'@cwd'};
791 32         138 my $key = $cwd . '/' . $fname;
792              
793 32         293 my ($path, $mtime) = $self->findfile ($fname);
794 32 50       86 if (!$path) {
795 0         0 $self->error ("$fname not found: incpath=(" . join (',',@{$$self{'@incpath'}}) . ")");
  0         0  
796 0         0 return;
797             }
798 32 0 33     124 if ($self->{'@attr'}->{'cache_files'} && exists $file_cache{$key}
      33        
799             && $file_cache{$key . '@mtime'} >= $mtime)
800             {
801             #print STDERR "readfile CACHED (file=", $$self{'@file'}, ") $key\n";
802              
803             # the name of the file
804 0         0 $$self{'@file'} = $file_cache{$key . '@file'};
805              
806             # the absolute path of the file's directory
807 0         0 push @{$$self{'@incpath'}}, $file_cache{$key . '@incpath_new'};
  0         0  
808              
809             # the absolute path of the enclosing file's directory;
810             # where we chdir when we're done processing this file
811 0         0 push @{$$self{'@cdpath'}}, $file_cache{$key . '@cdpath_new'};
  0         0  
812              
813             # Isn't this also the absolute path of the file's directory?
814 0         0 $$self{'@cwd'} = $file_cache{$key . '@cwd'};
815              
816             # chdir $$self{'@cwd'};
817              
818             # return the contents of the file
819 0         0 return $file_cache{$key};
820             }
821              
822             #print STDERR "readfile $key\n";
823 32         143 my $fh = $self->openfile ($path);
824              
825             #open (HTML, $fname) || $self->error ("can't open $fname: $!");
826 32         81 my $separator = $/;
827 32         129 undef $/;
828 32         1193 my $body = <$fh>;
829 32         123 $/ = $separator;
830 32         521 close $fh;
831              
832             # remove extra whitespace
833 32 100       442 if ($self->{'@attr'}->{'collapse_whitespace'})
    100          
834             {
835             # collapse adjacent white space
836 1         7 $body = &collapse_whitespace ($body, undef);
837             }
838             elsif ($self->{'@attr'}->{'collapse_blank_lines'})
839             {
840             # remove blank lines
841 7         20 $body = &collapse_whitespace ($body, 1);
842             }
843              
844 32 50       103 if ($self->{'@attr'}->{'cache_files'})
845             {
846 0         0 $file_cache{$key} = $body;
847 0         0 $file_cache{$key . '@file'} = $$self{'@file'};
848 0         0 my $list = $$self{'@incpath'};
849 0         0 $file_cache{$key . '@incpath_new'} = $$list[$#$list];
850 0         0 $list = $$self{'@cdpath'};
851 0         0 $file_cache{$key . '@cdpath_new'} = $$list[$#$list];
852 0         0 $file_cache{$key . '@cwd'} = $$self{'@cwd'};
853 0         0 $file_cache{$key . '@mtime'} = $mtime;
854             }
855 32         128 return $body;
856             #print STDERR "cwd=", $$self{'@cwd'}, "\n";
857              
858             #warn "nothing read from $fname" if ! $$self{'@body'};
859             }
860              
861             sub process ($$)
862             {
863 35     35 0 613 my ($self, $fname) = @_;
864              
865 35         111 &attr_backwards_compat;
866              
867 35 100       237 $$self{'@body'} = &readfile ($self, $fname) if ($fname);
868              
869 35         232 my $result = $self->process_buf ($$self{'@body'});
870            
871 34         50 my $lastdir = pop @ {$self->{'@cdpath'}};
  34         664  
872 34 100       73 if ($lastdir)
873             {
874             #print STDERR "popping up to $lastdir\n";
875             # chdir $lastdir ;
876 31         56 $self->{'@cwd'} = $lastdir;
877             }
878             else {
879 3         18 delete $self->{'@cwd'};
880             }
881 34         34 pop @ {$self->{'@incpath'}};
  34         78  
882              
883 34         192 return $result;
884             }
885              
886             sub print ($$)
887             {
888             # warn "gosub $_[0] \n";
889 0     0 0 0 my ($self, $fname) = @_;
890              
891 0         0 print "Cache-Control: no-cache\n";
892 0         0 print "Pragma: no-cache\n";
893 0         0 print "Content-Type: text/html\n\n";
894 0         0 print &process;
895             }
896              
897             sub error
898             {
899 1     1 0 3 my ($self, $msg, $pos) = @_;
900 1 50       4 $self->get_caller_info if ! $self->{'@caller_package'};
901 1         4 $msg = "HTML::Macro: $msg\n";
902 1 50       148 $msg .= "parsing " . $self->{'@file'} if ($self->{'@file'});
903             #$msg .= " near char $pos" if $pos;
904 1 50       3 if ($pos) {
905 1         2 my $line = 1;
906 1         2 my $linepos = 0;
907 1         3 my $body = $$self{'@body'};
908 1   66     27 while ($body =~ /\n/sg && pos $body <= $pos) {
909 2         4 ++$line;
910 2         38 $linepos = pos $body;
911             }
912 1         2 my $charpos = ($pos - $linepos);
913 1         4 $msg .= " on line $line, char $charpos\n\n";
914 1         4 $msg .= substr($body, $linepos, ((pos $body) - $linepos));
915             }
916 1         15 die "$msg\ncalled from " . $self->{'@caller_file'} . ", line " . $self->{'@caller_line'} . "\n";
917             }
918              
919             sub warning
920             {
921 0     0 0 0 my ($self, $msg, $pos) = @_;
922 0 0       0 $self->get_caller_info if ! $self->{'@caller_package'};
923 0         0 $msg = "HTML::Macro: $msg";
924 0 0       0 $msg .= " parsing " . $self->{'@file'} if ($self->{'@file'});
925 0 0       0 if ($pos) {
926 0         0 my $line = 1;
927 0         0 my $linepos = 0;
928 0         0 my $body = $$self{'@body'};
929 0   0     0 while ($body =~ /\n/sg && pos $body <= $pos) {
930 0         0 ++$line;
931 0         0 $linepos = pos $body;
932             }
933 0         0 my $charpos = ($pos - $linepos);
934 0         0 $msg .= " on line $line, char $charpos\n\n";
935 0         0 $msg .= substr($body, $linepos, ((pos $body) - $linepos));
936             }
937 0         0 warn "$msg\ncalled from " . $self->{'@caller_file'} . ", line " . $self->{'@caller_line'} . "\n";
938             }
939              
940             sub set ($$)
941             {
942 35     35 0 882 my $self = shift;
943 35         125 while ($#_ > 0) {
944 35         172 $$self {$_[0]} = $_[1];
945 35         41 shift;
946 35         168 shift;
947             }
948 35 50       350 warn "odd number of arguments to set" if @_;
949             }
950              
951             sub parent ($$)
952             {
953 7     7 0 15 my $self = shift;
954 7         13 $self = $self->{'@parent'};
955 7 50       35 return undef if !$self;
956             # parent may be either an HTML::Macro or an HTML::Macro::Loop
957 0 0       0 if ($self->isa('HTML::Macro::Loop'))
958             {
959 0         0 $self = $self->{'@parent'};
960 0 0       0 if ( ! $self ) {
961 0         0 warn "found an orphaned HTML::Macro::Loop" ;
962 0         0 return undef;
963             }
964             }
965 0         0 return $self;
966             }
967              
968             sub top ($$)
969             {
970 2     2 0 3 my $self = shift;
971 2         3 my $parent;
972 2         9 while (my $parent = $self->{'@parent'}) {
973 1         3 $self = $parent;
974             }
975 2         16 return $self;
976             }
977              
978             sub set_global ($$)
979             {
980 2     2 0 81 my $self = shift;
981 2         19 $self->top()->set (@_);
982             }
983              
984             sub set_ovalue ($$)
985             {
986 2     2 0 73 my $self = shift;
987 2         8 while ($#_ > 0) {
988 2         20 $self->{'@ovalues'} {$_[0]} = $_[1];
989 2         3 shift;
990 2         5 shift;
991             }
992 2 50       7 warn "odd number of arguments to set" if @_;
993             }
994              
995             sub push_incpath ($ )
996             {
997 1     1 0 120 my ($self) = shift;
998 1 50       24 $self->{'@cwd'} = cwd if ! $self->{'@cwd'};
999 1         16 while (my $dir = shift)
1000             {
1001 1 50       25 $dir .= '/' if $dir !~ m|/$|;
1002 1 50       21 if ($dir !~ m|^(?:[A-Za-z]:)?/|)
1003             {
1004             # turn into an absolute path if not already
1005             # allow DOS drive letters at the start
1006 1         8 $dir = $self->{'@cwd'} . '/' . $dir;
1007             }
1008 1         7 push @ {$self->{'@incpath'}}, $dir;
  1         20  
1009             }
1010             }
1011              
1012             sub set_hash ($ )
1013             {
1014 0     0 0 0 my ($self, $hash) = @_;
1015 0         0 while (my ($var, $val) = each %$hash)
1016             {
1017 0 0       0 $$self {$var} = defined($val) ? $val : '';
1018             }
1019             }
1020              
1021             sub get ($ )
1022             # finds values in enclosing scopes and uses macro case-collapsing rules; ie
1023             # matches $var, $uc var, or lc $var
1024             {
1025 0     0 0 0 my ($self, $var) = @_;
1026 0         0 return $self->match_token ($var);
1027             }
1028              
1029             sub declare ($@)
1030             # use this to indicate which vars are expected on this page.
1031             # Just initializes the hash to have zero for all of its args
1032             # *if the variable is not already set*
1033             {
1034 1     1 0 122 my ($self, @vars) = @_;
1035 1         11 for my $var (@vars) {
1036 3 50       52 $self->{$var} = '' if ! defined ($self->{$var});
1037             }
1038             }
1039              
1040             sub get_caller_info ($ )
1041             {
1042 13     13 0 22 my ($self) = @_;
1043 13         21 my ($caller_file, $caller_line);
1044 13         21 my $stack_count = 0;
1045 13         21 my $pkg;
1046 13         19 do {
1047 39         708 ($pkg, $caller_file, $caller_line) = caller ($stack_count++);
1048             }
1049             # ignore HTML::Macro and HTML::Macro::Loop
1050             while ($pkg =~ /HTML::Macro/);
1051              
1052 13         57 $self->{'@caller_package'} = $pkg;
1053 13         38 $self->{'@caller_file'} = $caller_file;
1054 13         54 $self->{'@caller_line'} = $caller_line;
1055             }
1056              
1057             sub new ($$$ )
1058             {
1059 13     13 0 804 my ($class, $fname, $attr) = @_;
1060 13         71 my $self = { };
1061 13         46 $self->{'@incpath'} = [ ];
1062 13         71717 $self->{'@cwd'} = cwd;
1063              
1064 13 50       395 if ($attr) {
1065 0 0       0 if (ref $attr ne 'HASH') {
1066 0         0 $self->error ('third argument (attr) to new must be hash ref');
1067             }
1068 0         0 $self->{'@attr'} = $attr;
1069             } else {
1070 13         158 $self->{'@attr'} = {};
1071             }
1072              
1073 13         208 bless $self, $class;
1074              
1075 13 50       122 $$self{'@body'} = &readfile($self, $fname) if ($fname);
1076              
1077 13         437 return $self;
1078             }
1079              
1080             sub new_loop ()
1081             {
1082 0     0 0 0 my ($self, $name, @loop_vars) = @_;
1083 0         0 my $new_loop = HTML::Macro::Loop->new($self);
1084 0 0       0 if ($name) {
1085 0         0 $self->set ($name, $new_loop);
1086 0 0       0 if (@loop_vars) {
1087 0         0 $new_loop->declare (@loop_vars);
1088             }
1089             }
1090 0         0 return $new_loop;
1091             }
1092              
1093             sub keys ()
1094             {
1095 1     1 0 79 my ($self) = @_;
1096 1         143 my @keys = grep /^[^@]/, keys %$self;
1097 1 50       8 push @keys, keys % {$self->{'@ovalues'}} if $self->{'@ovalues'};
  1         5  
1098 1 50       5 push @keys, $self->parent()->keys() if $self->parent();
1099 1         8 return @keys;
1100             }
1101              
1102             1;
1103             __END__