File Coverage

blib/lib/HTML/Transmorgify/Metatags.pm
Criterion Covered Total %
statement 319 414 77.0
branch 113 230 49.1
condition 19 46 41.3
subroutine 41 53 77.3
pod 0 21 0.0
total 492 764 64.4


line stmt bran cond sub pod time code
1              
2             package HTML::Transmorgify::Metatags;
3              
4 4     4   3428 use strict;
  4         6  
  4         145  
5 4     4   22 use warnings;
  4         5  
  4         158  
6 4     4   25 use HTML::Transmorgify qw(dangling eat_cr %variables $rbuf compile continue_compile capture_compile run $debug $modules queue_intercept rbuf boolean bomb $input_file $input_line $original_file allocate_result_type);
  4         6  
  4         875  
7 4     4   24 use Scalar::Util qw(reftype blessed);
  4         8  
  4         202  
8 4     4   12345 use HTML::Entities;
  4         48362  
  4         486  
9 4     4   12333 use URI::Escape;
  4         6077  
  4         1071  
10 4     4   30 use List::Util;
  4         6  
  4         212  
11 4     4   31 use File::Slurp;
  4         12  
  4         317  
12 4     4   3611 use Object::Dependency;
  4         33299  
  4         154  
13 4     4   43 use Carp qw(confess);
  4         7  
  4         28971  
14             require Exporter;
15              
16             our @ISA = qw(HTML::Transmorgify Exporter);
17             our @metatags_result_array;
18             our @include_dirs = ('.');
19             our @EXPORT = ();
20             our @EXPORT_OK = qw(%transformations @include_dirs %allowed_functions);
21              
22             my %tags;
23             my %shared_tags;
24             my $tag_package = { tag_package => __PACKAGE__ };
25              
26 0     0 0 0 sub return_false { 0 };
27              
28             sub add_tags
29             {
30 4     4 0 12 my ($module, $tobj) = @_;
31 4         58 $tobj->intercept_exclusive($tobj, __PACKAGE__, 100, %tags);
32 4         40 $tobj->intercept_shared($tobj, __PACKAGE__, 100, %shared_tags);
33 4         35 $tobj->intercept_pre_compile(\&setup_diversions);
34             }
35              
36             $tags{"/define"} = \&dangling;
37             $tags{define} = \&define_tag;
38              
39             sub define_tag
40             {
41 33     33 0 72 my ($attr, $closed) = @_;
42 33         120 my $static_name = $attr->static('name', 0);
43 33         100 my $static_value = $attr->static('value', 1);
44 33         107 my $raw_value = $attr->raw('value', 1);
45              
46 33         129 my $raw_late = $attr->boolean('eval', undef, 0, raw => 1);
47 33         68 my $early_binding = ! defined($raw_late); # XXX think about this -- other cases?
48              
49 33 50       82 print STDERR "### DEFINE $static_name\n" if $debug;
50              
51 33 50       838 if (defined $static_name) {
52 33 50       169 bomb("illegal variable name for define") if $static_name =~ /^[\s_]/;
53 33 100       88 if (defined $static_value) {
54             # a common case, I expect
55 4     4   26 push(@$HTML::Transmorgify::rbuf, sub { get_varset_func($static_name)->($static_value) });
  4         17  
56 4         18 eat_cr;
57 4 50       13 print STDERR "### DEFINE $static_name = '$static_value' DONE\n" if $debug;
58 4         14 return undef;
59             }
60             }
61              
62              
63             #
64             # Definitions can be in-line like
65             # or they can be bracketed: value.
66             # If they're in-line, then there will be a $raw_value.
67             #
68 29         30 my $buf;
69 29 50       57 if (defined $raw_value) {
70 0         0 $buf = compile($modules, \$raw_value);
71             } else {
72 29         37 my $levels = 1;
73 29         171 $buf = capture_compile("define", $attr, $tag_package,
74             "/define" => \&return_false,
75             );
76             }
77 29         211 do_trim($attr, $buf);
78              
79 29         164 eat_cr;
80              
81             rbuf (sub {
82 29 50   29   67 print STDERR "# define macro $attr\n" if $debug;
83 29   33     78 my $name = $static_name || $attr->get('name', 0);
84 29 50       77 bomb("illegal variable name for define") if $name =~ /^ /;
85 29         403 my $set = get_varset_func($name);
86              
87 29 100 100     231 if ((@$buf == 1 && ! ref($buf->[0])) || ! $attr->boolean('eval', undef, 0)) {
      100        
88             #
89             # early binding -- let's set the variable to a string since the
90             # the value is a simple string or we've been explicitly told
91             # not to evaluate the string.
92             #
93 27         105 my $vals = $attr->vals;
94 27 50       63 print STDERR "# attr $attr\n" if $debug;
95 27 50       329 $attr->hide_position(0) if $attr->last_position(0);
96 27         165 my $r;
97             {
98 27 50       33 printf STDERR "# local'izing variables{%s}\n", join(', ', keys %$vals) if $debug;
  27         61  
99 27         135 local(@HTML::Transmorgify::variables{keys %$vals}) = values %$vals;
100 27         79 local(@metatags_result_array) = ( '' );
101 27         95 run($buf, \@metatags_result_array);
102 27         87 $r = \@metatags_result_array;
103             }
104 27 50       398 if (@$r > 1) {
105             #
106             # Results can come in different "buckets". Bucket 0, is the
107             # main text results. Other buckets are used for things like,
108             # HTTP headers, collecting javascript, etc. Without knowing
109             # what they're used for it's hard to know what to do with
110             # the extra results. In any case, we're going to $set->() the
111             # new value to a callback. XXX figure out semantics for
112             # a macro expansion that adds to the HTTP header, etc.
113             #
114             $set->(sub {
115 0         0 $HTML::Transmorgify::result->[$_] .= $r->[$_] for grep { defined $r->[$_] } 0..$#$r;
  0         0  
116 0         0 });
117 0 0       0 print STDERR "# now setting variable $name = '$r->[0] and other buckets'\n" if $debug;
118             } else {
119 27         78 $set->($r->[0]);
120 27 50       545 print STDERR "# now setting variable $name = '$r->[0]'\n" if $debug;
121             }
122             } else {
123             # late binding -- don't evaluate until the variable is expanded.
124             # we prefer early binding because more can happen at compile time and
125             # that will help performance if we can cache the results.
126             $set->(sub {
127 3 50       11 print STDERR "# evaluating variable '$name'...\n" if $debug;
128 3         11 my $vals = $attr->vals;
129 3 50       10 $attr->hide_position(0) if $attr->last_position(0);
130 3 50       37 printf STDERR "# local'izing variables{%s}\n", join(', ', keys %$vals) if $debug;
131 3         18 local(@HTML::Transmorgify::variables{keys %$vals}) = values %$vals;
132 3         13 run($buf);
133 2         15 });
134             }
135 29         385 });
136 29 50       109 print STDERR "### DONE DEFINE $static_name\n" if $debug;
137 29         89 return undef;
138             };
139              
140             $tags{include} = \&include_tag;
141              
142             sub include_tag
143             {
144 0     0 0 0 my ($attr, $closed) = @_;
145 0 0       0 warn unless $closed;
146 0         0 eat_cr;
147 0         0 my $v = $attr->vals;
148 0         0 my @k = grep { $_ ne 'file' } keys %$v;
  0         0  
149              
150             rbuf (sub {
151 0 0   0   0 print STDERR "# including file $attr\n" if $debug;
152             # $attr->run;
153 0         0 my $file = $attr->get('file', 0);
154 0         0 my $ifile = find_file($file);
155 0         0 local($HTML::Transmorgify::input_file) = $ifile;
156 0 0       0 bomb("cannot find include file $file", attr => $attr) unless $ifile;
157 0         0 local($HTML::Transmorgify::input_line) = 1;
158 0         0 my $contents = read_file($ifile);
159 0         0 local(@HTML::Transmorgify::variables{@k}) = map { $attr->get($_) } @k;
  0         0  
160 0         0 my $buf = compile($HTML::Transmorgify::modules, \$contents);
161 0         0 run($buf);
162 0         0 });
163 0         0 return undef;
164             };
165              
166             #
167             # We're doing a little hack to grab control of the compile
168             # sequence so that we can wrap the diversions code around it
169             #
170              
171             our @process_text;
172              
173             sub setup_diversions
174             {
175 27     27 0 66 push(@process_text, $HTML::Transmorgify::process_text_ref);
176 27         105 $HTML::Transmorgify::process_text_ref = \"";
177             }
178              
179             $tags{enableDiversionsCodeRightNow} = \&enable_diversions;
180             $tags{capture} = \&capture_tag;
181             $tags{playback} = \&playback_tag;
182              
183             # because diversions can be nested, we have to
184             # resolve them in dependency order. We'll use
185             # an Object;:Dependency graph to store their names
186             # so that they be resolved in dependency order.
187              
188             # At runtime, a playback tag cannot pull in the
189             # information it needs because it may not exist yet.
190             # To solve that, the action of the playback tag is
191             # to note the output possition within the current
192             # block so that we can pull in the text in post-
193             # processing.
194              
195             our $diversion_graph;
196             our %diversion_text;
197             our %diversion_used;
198             our %diversion_playback;
199             our $diversion_current;
200              
201             sub enable_diversions
202             {
203 27     27 0 49 my ($attr, $closed) = @_;
204 27 50       102 die unless $closed;
205 27         44 my $real_text = shift(@process_text);
206              
207              
208              
209             rbuf (sub {
210 27     27   49 local(%diversion_text);
211 27         38 local(%diversion_used);
212 27         36 local(%diversion_playback);
213 27         192 local($diversion_graph) = Object::Dependency->new();
214              
215 27         940 $diversion_graph->add('*main body*');
216              
217 27         2139 my $buf = compile($HTML::Transmorgify::modules, $real_text);
218             {
219 27         46 local($HTML::Transmorgify::result_array[0]) = '';
  27         79  
220 27 50       69 print STDERR "Start(diversion=*main body*)\n" if $debug;
221 27         55 local($diversion_current) = '*main body*';
222 27         107 run($buf);
223 27         95 $diversion_text{'*main body*'} = $HTML::Transmorgify::result_array[0];
224 27 50       90 print STDERR "End(diversion=*main body*)\n" if $debug;
225             }
226              
227              
228             #
229             # Pull the playbacks into the strings.
230             #
231 27         159 while(my ($ready) = $diversion_graph->independent(count => 1)) {
232 31         1109 $diversion_graph->remove_dependency($ready);
233 31         884 my $textref = \$diversion_text{$ready};
234 31 50       90 $$textref = '' unless defined $$textref;
235 31 100       501 next unless $diversion_playback{$ready};
236 3         4 my @newstring;
237 3         4 my $lastpos = 0;
238 3         4 for my $playback (@{$diversion_playback{$ready}}) {
  3         13  
239 5         8 my $pos = $playback->{position};
240 5         12 push(@newstring, substr($$textref, $lastpos, $pos-$lastpos));
241 5         12 push(@newstring, $diversion_text{$playback->{diversion}});
242 5         10 $lastpos = $pos;
243             }
244 3         9 push(@newstring, substr($$textref, $lastpos, length($$textref)-$lastpos));
245 3         18 $$textref = join('', @newstring);
246             }
247              
248 27 50       602 die unless $diversion_graph->alldone();
249              
250 27         558 $HTML::Transmorgify::result_array[0] .= $diversion_text{'*main body*'};
251 27         220 });
252 27         85 return undef;
253             }
254              
255             sub capture_tag
256             {
257 4     4 0 8 my ($attr, $closed) = @_;
258 4 50       10 die if $closed;
259 4         14 my $static_name = $attr->static('name', 0);
260            
261 4         9 my $diversion = \my $str;
262 4         19 my $buf = capture_compile("capture", $attr, $tag_package, '/capture' => \&return_false);
263 4         16 do_trim($attr, $buf);
264 4         15 eat_cr;
265              
266             rbuf (sub {
267 4 50   4   12 my $name = $static_name or $attr->get('name', 0);
268 4 50       16 die " tag requires a capture name" unless defined $name;
269 4         12 allocate_result_type($name);
270 4         8 local($HTML::Transmorgify::result_array[0]) = '';
271 4 50       11 print STDERR "Start(diversion=$name)\n" if $debug;
272 4         13 local($diversion_current) = $name;
273 4         18 run($buf);
274 4         11 $diversion_text{$name} .= $HTML::Transmorgify::result_array[0];
275 4 50       17 print STDERR "End(diversion=$name)\n" if $debug;
276 4         49 });
277 4         12 return undef;
278             }
279              
280             sub playback_tag
281             {
282 5     5 0 8 my ($attr, $closed) = @_;
283 5 50       11 warn "$attr is not closed, but it should be" unless $closed;
284 5         19 my $static_name = $attr->static('name', 0);
285              
286             rbuf (sub {
287 5 50   5   23 my $name = $static_name or $attr->get('name', 0);
288 5 50       10 confess unless defined $diversion_current;
289 5   100     26 $diversion_playback{$diversion_current} ||= [];
290 5         7 push(@{$diversion_playback{$diversion_current}}, {
  5         21  
291             diversion => $name,
292             position => length($HTML::Transmorgify::result_array[0]),
293             });
294 5         20 $diversion_graph->add($diversion_current, $name);
295 5         32 });
296              
297 5         13 return undef;
298             }
299              
300             #
301             # Add height & width to tags if they don't
302             # already have them.
303             #
304             $shared_tags{img} = \&img_tag;
305             sub img_tag
306             {
307 0     0 0 0 my ($attr) = @_;
308              
309             $attr->static_action('src', sub {
310 0 0   0   0 print STDERR "# processing img $attr\n" if $debug;
311 0         0 my $vals = $attr->vals;
312 0 0 0     0 if (! $vals->{height} && ! $vals->{width} && $vals->{src}) {
      0        
313 0         0 my $file = find_file($attr->get('src'));
314 0 0       0 $attr->set(attr_imgsize($file)) if $file;
315             }
316 0         0 });
317              
318 0         0 return 1;
319             }
320              
321             #
322             # Capture and put it in the first result buffer
323             # for inclusion in a combined will be
324             # recognized even if it's inside a quoted string.
325             #
326             $tags{script} = \&script_tag;
327             sub script_tag
328             {
329 0     0 0 0 my ($attr, $closed) = @_;
330 0         0 my $tag = "$attr";
331 0         0 my $before = pos($$HTML::Transmorgify::textref);
332 0 0 0     0 if (! $closed && $$HTML::Transmorgify::textref =~ m{
333             (.*?)
334             ]
335             }gcxs) {
336 0         0 my $text = substr($$HTML::Transmorgify::textref, $before, pos($$HTML::Transmorgify::textref)-$before);
337 0     0   0 rbuf (sub { $HTML::Transmorgify::result->[1] .= $tag . $text });
  0         0  
338             } else {
339 0     0   0 rbuf (sub { $HTML::Transmorgify::result->[1] .= $tag });
  0         0  
340 0         0 return;
341             }
342             };
343              
344             $tags{"/script"} = \&dangling;
345              
346             our %transformations = (
347             html => \&encode_entities,
348             uri => sub { uri_escape($_[0], "^A-Za-z0-9\-\._~/") },
349             url => sub { uri_escape($_[0], "^A-Za-z0-9\-\._~/") },
350             comment => sub { return '' },
351             none => sub { $_[0] },
352             );
353              
354             $tags{"/transform"} = \&dangling;
355             $tags{transform} = \&transform_tag;
356              
357             #
358             # XXX TODO items:
359             # 1) cache transformation output
360             # 2) notice when transforming constant inputs and do it at compile time.
361             #
362              
363             sub transform_tag
364             {
365 0     0 0 0 my ($attr, $closed) = @_;
366 0 0       0 die if $closed;
367 0         0 eat_cr;
368              
369 0         0 my $encode = $attr->get('encode', 0);
370              
371 0 0       0 die unless $transformations{$encode};
372              
373 0         0 my $levels = 1;
374 0         0 my $savebuf = capture_compile("transform", $attr, $tag_package,
375             "/transform" => \&return_false,
376             );
377              
378             rbuf (sub {
379 0 0   0   0 print STDERR "# processing transform $attr\n" if $debug;
380 0         0 my $contents;
381             {
382 0         0 local($HTML::Transmorgify::result->[0]) = '';
  0         0  
383 0         0 run($savebuf);
384 0         0 $contents = \$HTML::Transmorgify::result->[0];
385             }
386 0         0 $HTML::Transmorgify::result->[0] .= $transformations{$encode}->($$contents);
387 0         0 });
388             };
389              
390             #
391             # If we set a value, like: value
392             # then we have to to auto vivify an entire data structure. We
393             # assume that small (1-9999) integers are array indexes.
394             #
395             # We return a reference to the emtpy cell so that a value can
396             # be assigned.
397             #
398              
399             sub create_container
400             {
401 17     17 0 153 my ($ref, @refines) = @_;
402 17 50       42 print STDERR "Adding new containers: @refines\n" if $debug;
403 17         47 while (@refines > 1) {
404 19         24 my $new;
405 19         34 my $ele = shift @refines;
406 19 100       87 if ($refines[0] =~ /^\d{1,4}$/) {
407 17         34 $new = [];
408             } else {
409 2         5 $new = {};
410             }
411 19 100       113 if (blessed $ref) {
    100          
    50          
412             # should be a HTML::Transmorgify::ObjectGlue
413 1         49 $ref->set($ele, $new);
414             } elsif (ref($ref) eq 'ARRAY') {
415 1         6 $ref->[$ele] = $new;
416             } elsif (ref($ref) eq 'HASH') {
417 17         47 $ref->{$ele} = $new;
418             } else {
419 0         0 die;
420             }
421 19         61 $ref = $new;
422             }
423 17         67 return $ref;
424             }
425              
426             #
427             # This returns a function that sets a variable (of the $name
428             # parameter) to a value.
429             #
430              
431             sub get_varset_func
432             {
433 33     33 0 71 my ($name) = @_;
434 33 100       243 if ($name =~ /(.+)\.([^\.]+)/) {
435 9         36 my ($cname, $ename) = ($1, $2);
436 9         23 my $container = lookup($cname, $ename, $cname);
437 9 50       54 if (blessed $container) {
    100          
    50          
438             # should be a HTML::Transmorgify::ObjectGlue
439 0     0   0 return sub { $container->set($ename, $_[0]) };
  0         0  
440             } elsif (ref($container) eq 'HASH') {
441 2     2   11 return sub { $container->{$ename} = $_[0] };
  2         16  
442             } elsif (ref($container) eq 'ARRAY') {
443 7     7   126 return sub { $container->[$ename] = $_[0] };
  7         27  
444             } else {
445 0         0 die "no container $cname to hold $ename";
446             }
447             } else {
448 24     24   106 return sub { $HTML::Transmorgify::variables{$name} = $_[0] };
  24         83  
449             }
450             }
451              
452             #
453             # This looks up a variable, optionally creating the container
454             # object to to hold the variable. Variables can have multiple
455             # components to their names: eg: "a.b.c".
456             #
457              
458             sub lookup
459             {
460 135     135 0 373 my ($name, $create, $error_context) = @_;
461              
462 4 50   4   67 if ($debug) { no warnings; print STDERR "lookup($name, $create, $error_context)\n"; }
  4         10  
  4         8993  
  135         373  
  0         0  
463              
464 135 50 33     690 unless (defined($name) && length($name)) {
465 0 0       0 print STDERR "# tried to look up undef/empty!\n" if $debug;
466 0         0 return '';
467             }
468              
469 135 50       304 die if $name =~ /^ /;
470              
471 135         489 my ($primary, @refines) = split(/[.]/, $name);
472              
473 135 100       501 unless (exists $HTML::Transmorgify::variables{$primary}) {
474 16 50       34 if (defined $create) {
475 16         52 return create_container(\%HTML::Transmorgify::variables, $primary, @refines, $create);
476             } else {
477 0 0       0 print STDERR "# tried to look up $primary ($name) and didn't find it\n" if $debug;
478 0         0 return '';
479             }
480             }
481              
482 119         177 my $r = $HTML::Transmorgify::variables{$primary};
483              
484 119 50       341 printf STDERR "# lookup %s, got '%s'\n", $name, dstring($r) if $debug;
485              
486 119         309 while (@refines) {
487 11         16 my $new;
488 11         174 my $key = shift @refines;
489 11 100       53 if (blessed $r) {
    50          
490             # should be a HTML::Transmorgify::ObjectGlue
491 3         19 $new = $r->lookup($key, 0, $error_context);
492             } elsif (ref $r) {
493 8 100       46 if (reftype($r) eq 'ARRAY') {
    50          
494 4 50       126 unless ($key =~ /\A\d+\z/) {
495 0         0 die "attempt to index into array with non-number '$key' for $error_context starting from $HTML::Transmorgify::original_file"
496             }
497 4         15 $new = $r->[$key];
498             } elsif (reftype($r) eq 'HASH') {
499 4         13 $new = $r->{$key};
500             } else {
501 0         0 die;
502             }
503             } else {
504 0         0 return undef;
505             # die "could not look up $key with $r in $input_file:$input_line for $error_context";
506             }
507 11 100 66     326 if (defined($create) && ! defined($new)) {
508 1         6 return create_container($r, $key, @refines, $create);
509             }
510 10         32 $r = $new;
511             }
512              
513 118         488 return $r;
514             }
515              
516             #
517             # to_text() returns the text associated with the lookup of a
518             # variable.
519             #
520              
521             sub to_text
522             {
523 116     116 0 170 my ($r, $attr) = @_;
524              
525 116         352 my $vals = $attr->vals;
526 116         431 local(@HTML::Transmorgify::variables{keys %$vals}) = values %$vals;
527              
528 116 100       271 if (ref $r) {
529 16 50       102 if (blessed($r)) {
    100          
    50          
    0          
530             # should be a HTML::Transmorgify::ObjectGlue
531 0         0 $r = $r->text;
532             } elsif (reftype($r) eq 'CODE') {
533 3         10 $r = $r->($attr);
534             } elsif (reftype($r) eq 'ARRAY') {
535             # XXX is this a good idea?
536 13         27 $r = join(', ', @$r);
537             } elsif (reftype($r) eq 'HASH') {
538             # XXX is this a good idea?
539 0         0 $r = join(', ', map { "$_ => $r->{$_}" } keys %$r);
  0         0  
540             } else {
541 0         0 die;
542             }
543             }
544              
545 116 50       231 die if ref($r);
546              
547 116 50       227 return '' unless defined $r;
548 116         390 return $r;
549             }
550              
551              
552              
553             $tags{macro} = \¯o_tag;
554             sub macro_tag {
555 44     44 0 84 my ($attr, $closed) = @_;
556             rbuf (sub {
557 96     96   380 my $name = $attr->get('name', 0);
558 96   50     297 my $encode = $attr->get('encode') || 'none';
559              
560 96 50       234 printf STDERR "# macro '%s' encode='%s'\n", dstring($name), $encode if $debug;
561              
562 96         213 my $r = to_text(lookup($name, 0, $attr), $attr);
563              
564 96 50       299 die "no transformation '$encode' defined" unless $transformations{$encode};
565              
566 96 50       291 printf STDERR "# before tranform %s: '%s'\n", $encode, $r if $debug;
567 96         216 $r = $transformations{$encode}->($r);
568 96 50       203 printf STDERR "# after tranform %s: '%s'\n", $encode, $r if $debug;
569              
570 96 50       296 $HTML::Transmorgify::result->[0] .= $r if defined $r;
571 96 50       399 printf STDERR "# macro expansion '%s' = '%s'\n", "$attr", dstring($r) if $debug;
572 44         322 });
573 44         125 return undef;
574             };
575              
576              
577             $tags{"/foreach"} = \&dangling;
578             $tags{foreach} = \&foreach_tag;
579              
580             sub foreach_tag
581             {
582 5     5 0 10 my ($attr, $closed) = @_;
583 5 50       16 die if $closed;
584 5         20 eat_cr;
585              
586 5         165 my $buf = capture_compile("foreach", $attr, $tag_package, '/foreach' => \&return_false);
587 5         22 do_trim($attr, $buf);
588 5         20 eat_cr;
589             # write test for same
590             # document trim= for
591              
592             rbuf (sub {
593 7     7   137 my $var = $attr->get('var', 0);
594 7 50       25 printf STDERR "# running foreach: loop var %s\n", dstring($var) if $debug;
595              
596 7         23 my $container = $attr->get('container', 1);
597 7         26 my $lastpos = $attr->last_position;
598 7         14 my @containers;
599 7 50       19 if ($lastpos > 0) {
600 7         621 for my $p (1..$lastpos) {
601 10         38 push(@containers, $attr->get(undef, $p));
602             }
603             } else {
604 0         0 push(@containers, $attr->get('container'));
605             }
606              
607 7 50       28 die unless @containers;
608 7         11 my @a;
609 7         12 for my $container (@containers) {
610 10         27 my $r = lookup($container, 0, $attr);
611 10 50       31 printf STDERR "# container value: %s.\n", dstring($r) if $debug;
612 10 50       31 if (blessed($r)) {
613             # should be a HTML::Transmorgify::ObjectGlue
614 0         0 my @e = $r->expand();
615 0 0 0     0 unless (@e == 1 && ref $r) {
616 0         0 my $i = 0;
617 0         0 push(@a, map { $i++ => $_ } @e);
  0         0  
618 0         0 next;
619             }
620             }
621 10 100       44 if (reftype($r) eq 'ARRAY') {
    50          
    0          
    0          
622 9         26 my $i = 0;
623 9 100       16 push(@a, map { exists($r->[$i++]) ? ($i-1 => $_) : () } @$r);
  38         148  
624             } elsif (reftype($r) eq 'HASH') {
625 1         11 push(@a, map { $_ => $r->{$_} } sort keys %$r);
  2         9  
626             } elsif (ref($r)) {
627 0         0 die;
628             } elsif (defined $r) {
629 0         0 push(@a, $r => $r);
630             } else {
631             # undef
632             }
633             }
634              
635 7         28 for (my $i = 0; $i <= $#a; ) {
636 37         74 my $key = $a[$i++];
637 37         67 my $val = $a[$i++];
638 37         100 local($HTML::Transmorgify::variables{$var}) = $val;
639 37         93 local($HTML::Transmorgify::variables{"_$var"}) = $key;
640 37         92 run($buf);
641             }
642 5         65 });
643              
644 5         15 return undef;
645             };
646              
647             $tags{"/if"} = \&dangling;
648             $tags{if} = \&if_tag;
649             $tags{else} = sub { die " w/o " };
650             $tags{elsif} = sub { die " w/o " };
651              
652             sub if_tag
653             {
654 17     17 0 35 my ($attr, $closed) = @_;
655 17         66 eat_cr;
656              
657 17 50       43 die if $closed;
658              
659 17         25 my @sets;
660              
661 17         58 my $current = {
662             attr => $attr,
663             };
664 17         35 push(@sets, $current);
665              
666             {
667 17         33 my $found = sub {
668 8     8   22 my ($attr, $closed) = @_;
669              
670 8         22 eat_cr;
671 8 50       19 die if $closed;
672 8         25 $current->{rbuf} = [ @$HTML::Transmorgify::rbuf ];
673 8         19 @$HTML::Transmorgify::rbuf = ();
674 8         21 $current = {
675             attr => $attr,
676             };
677 8         14 push(@sets, $current);
678 8         23 return 0;
679 17         101 };
680              
681 17         48 local($HTML::Transmorgify::rbuf) = $current->{rbuf};
682 17         104 my $buf = capture_compile("if", $attr, $tag_package,
683             if => \&if_tag,
684             else => $found,
685             elsif => $found,
686             "/if" => \&return_false);
687 17 50       71 die if $current->{rbuf};
688 17         123 $current->{rbuf} = $buf;
689             }
690 17         59 eat_cr;
691              
692 17         38 for my $s (@sets) {
693 25         43 my $a = $attr;
694 25 50       102 $a = $s->{attr} if $s->{attr}->static('trim');
695 25         91 do_trim($a, $s->{rbuf});
696             }
697              
698 17         78 my %counters = (
699             else => 0,
700             if => 0,
701             elsif => 0,
702             );
703 17         37 for my $s (@sets) {
704 25         105 $counters{$s->{attr}->tag}++;
705             }
706 17 50       56 die if $counters{else} > 1;
707 17 50       47 die if $counters{if} > 1;
708              
709 17         30 for my $s (@sets) {
710 25 100       81 next if $s->{attr}->tag eq 'else';
711 20         65 $s->{evaluate} = conditional($s->{attr});
712             }
713              
714             rbuf (sub {
715 21 50   21   52 print STDERR "# processing if statement $attr\n" if $debug;
716 21         41 for my $s (@sets) {
717 33 100 100     127 next unless $s->{attr}->tag eq 'else' || $s->{evaluate}->();
718 16         74 run($s->{rbuf});
719 16         82 last;
720             }
721 17         141 });
722 17         85 return 0;
723             };
724              
725             our %allowed_functions = (
726             abs => sub { abs($_[0]) },
727             min => \&List::Util::min,
728             max => \&List::Util::max,
729             defined => sub { defined($_[0]) },
730             );
731              
732 4     4   21523 use HTML::Transmorgify::Conditionals;
  4         20  
  4         3442  
733              
734             my $expr_grammar = HTML::Transmorgify::Conditionals->new();
735              
736             sub conditional
737             {
738 20     20 0 42 my ($attr) = @_;
739 20         79 my $vals = $attr->vals;
740 20 100       76 if (exists $vals->{is_set}) {
    50          
741             return sub {
742 20 50   20   45 print STDERR "# checking set? $attr\n" if $debug;
743 20         61 return to_text(lookup($attr->get('is_set'), 0, $attr), $attr);
744 10         57 };
745             } elsif (exists $vals->{expr}) {
746 10         34 my $expr = $expr_grammar->conditional($attr->raw('expr'));
747 10 50       247 die sprintf("expression '%s' did not compile", $attr->raw('expr')) unless $expr;
748             return sub {
749             # print STDERR "# checking expr? $attr: $expr\n" if $debug;
750 10     10   39 return $expr->();
751 10         110 };
752             } else {
753 0         0 die;
754             }
755             }
756              
757             sub find_file
758             {
759 0     0 0 0 my ($file) = @_;
760 0 0       0 die if $file =~ m{^/};
761 0         0 for my $i (@include_dirs) {
762 0 0 0     0 if (ref($i) && ref($i) eq 'CODE') {
    0          
763 0         0 my $x = $i->($file);
764 0 0 0     0 return $x if $x && -e $x;
765             } elsif (ref($i)) {
766 0         0 die;
767             }
768 0 0       0 next unless -e "$i/$file";
769 0         0 return "$i/$file";
770             }
771 0         0 return undef;
772             }
773              
774              
775             #
776             # This is used by various control-flow tags to eliminate
777             # whitespace from around them. You want to be able to format
778             # the control-flow directives without adding whitespace to the
779             # final document. This function removes extra whitespace.
780             #
781              
782             sub do_trim
783             {
784 63     63 0 109 my ($attr, $buf) = @_;
785              
786 63         240 my $trim = $attr->raw('trim');
787 63 100 66     452 if (@$buf && boolean($trim, 0)) {
788 2 100       11 if ($trim eq 'all') {
789 4         25 (s/^\s+// || s/\s+$//)
790 1   33     4 for grep { ! ref($_) } @$buf;
791             } else {
792 1 50       7 if ($trim ne 'end') {
793 1 50       310 $buf->[0] =~ s/^\s+//
794             unless ref $buf->[0];
795             }
796 1 50       7 if ($trim ne 'start') {
797 0 0         $buf->[$#$buf] =~ s/\s+$//
798             unless ref $buf->[$#$buf];
799             }
800             }
801             }
802             }
803              
804             __END__