File Coverage

blib/lib/WebDyne/Compile.pm
Criterion Covered Total %
statement 323 357 90.4
branch 113 164 68.9
condition 79 146 54.1
subroutine 22 22 100.0
pod 0 6 0.0
total 537 695 77.2


line stmt bran cond sub pod time code
1             #
2             # This file is part of WebDyne.
3             #
4             # This software is copyright (c) 2026 by Andrew Speer .
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9             # Full license text is available at:
10             #
11             #
12             #
13             package WebDyne::Compile;
14              
15              
16             # Pragma
17             #
18 5     5   183167 use strict qw(vars);
  5         18  
  5         296  
19 5     5   58 use vars qw($VERSION %CGI_TAG_WEBDYNE %CGI_TAG_FORM %CGI_TAG_IMPLICIT);
  5         13  
  5         347  
20 5     5   31 use warnings;
  5         15  
  5         368  
21 5     5   26 no warnings qw(uninitialized redefine once);
  5         11  
  5         311  
22              
23              
24             # External Modules
25             #
26 5     5   1771 use WebDyne;
  5         14  
  5         309  
27 5     5   3495 use WebDyne::HTML::TreeBuilder;
  5         25  
  5         78  
28 5     5   259 use Storable;
  5         13  
  5         421  
29 5     5   31 use IO::File;
  5         11  
  5         865  
30 5     5   34 use Data::Dumper;
  5         12  
  5         258  
31              
32              
33             # WebDyne Modules
34             #
35 5     5   27 use WebDyne::HTML::Tiny;
  5         13  
  5         115  
36 5     5   27 use WebDyne::Constant;
  5         7  
  5         48  
37 5     5   43 use WebDyne::Util;
  5         11  
  5         67  
38              
39              
40             # Version information
41             #
42             $VERSION='2.075';
43              
44              
45             # Debug load
46             #
47             0 && debug("Loading %s version $VERSION", __PACKAGE__);
48              
49              
50             # Get WebDyne and CGI tags from TreeBuilder module
51             #
52             *CGI_TAG_WEBDYNE=\%WebDyne::CGI_TAG_WEBDYNE;
53             *CGI_TAG_FORM=\%WebDyne::HTML::TreeBuilder::CGI_TAG_FORM;
54             *CGI_TAG_IMPLICIT=\%WebDyne::HTML::TreeBuilder::CGI_TAG_IMPLICIT;
55              
56              
57             # Var to hold package wide hash, for data shared across package
58             #
59             my %Package;
60              
61              
62             # All done. Positive return
63             #
64             1;
65              
66              
67             #==================================================================================================
68              
69              
70             # Packace init, attempt to load optional Time::HiRes module
71             #
72             BEGIN {
73 5     5   52 eval {require Time::HiRes; Time::HiRes->import('time')};
  5         34  
  5         50  
74 5         280 eval {require Devel::Confess; Devel::Confess->import(qw(no_warnings))};
  5         23  
  5         34  
75 5 50       31909 eval {} if $@;
76             }
77              
78              
79             sub new {
80              
81              
82             # Only used when debugging from outside apache, eg test script. If so, user
83             # must create new object ref, then run the compile. See wdcompile script for
84             # example. wdcompile is only used for debugging - we do some q&d stuff here
85             # to make it work
86             #
87 2     2 0 1218 my ($class, @opt)=@_;
88 2 50       13 my %opt=(ref($opt[0]) eq 'HASH') ? %{$opt[0]} : @opt;
  0         0  
89 2         4 0 && debug("$class, opt: %s", Dumper(\%opt));
90              
91              
92             # Init WebDyne module
93             #
94 2         17 require WebDyne::Request::Fake;
95 2   33     21 my $r=WebDyne::Request::Fake->new( filename=> ( $opt{'filename'} || $opt{'srce'} ) );
96              
97              
98             # Get appropriate cgi_or
99             #
100 2   50     21 my $html_tag_or=WebDyne::HTML::Tiny->new(mode => $WEBDYNE_HTML_TINY_MODE, r=>$r ) ||
101             return err('unable to get new WebDyne::HTML::Tiny object');
102              
103              
104             # New self ref
105             #
106 2         8 my %self=(
107              
108             _r => $r,
109             _CGI => $html_tag_or,
110              
111             );
112              
113              
114             # And return blessed ref
115             #
116 2         11 return bless(\%self, 'WebDyne');
117              
118             }
119              
120              
121             sub compile {
122              
123              
124             # Compile HTML file into Storable structure
125             #
126 36     36 0 33811 my ($self, $param_hr)=@_;
127              
128              
129             # Start timer so we can log how long it takes us to compile a file
130             #
131 36         194 my $time=($self->{'_time'}=time());
132              
133              
134             # Init class if not yet done
135             #
136 36   66     262 (ref($self))->{_compile_init} ||= do {
137 5 50       28 $self->compile_init() || return err()
138             };
139              
140              
141             # Debug
142             #
143 36         61 0 && debug('compile %s', Dumper($param_hr));
144              
145              
146             # Get srce and dest
147             #
148 36         120 my ($html_cn, $dest_cn)=@{$param_hr}{qw(srce dest)};
  36         140  
149              
150              
151             # Need request object ref
152             #
153 36   50     218 my $r=$self->{'_r'} || $self->r() || return err();
154              
155              
156             # Open the file
157             #
158 36   50     380 my $html_fh=IO::File->new($html_cn, O_RDONLY) ||
159             return err("unable to open file $html_cn, $!");
160              
161              
162             # Read over file handle until we get to the first non-comment line (ignores auto added copyright statements). Update - do
163             # this in Treebuilder code now so can account for comments when counting line numbers
164             #
165 36         4584 while (0) {
166             my $pos=tell($html_fh);
167             my $line=<$html_fh>;
168             if ($line=~/^#/) {
169             next;
170             }
171             else {
172             seek($html_fh, $pos, 0);
173             last;
174             }
175             }
176            
177            
178             # Supply html_tiny object ref to Treebuilder so it can run things like start_html
179             #
180 36   50     258 my $html_tiny_or=$self->html_tiny() ||
181             return err('unable to get html_tiny_or ref');
182              
183              
184             # Get new TreeBuilder object. Note api_version flows through to HTML::Parser constructor
185             #
186 36   50     413 my $tree_or=WebDyne::HTML::TreeBuilder->new(
187              
188             api_version => 3,
189             html_tiny_or => $html_tiny_or,
190             r => $r
191              
192             ) || return err('unable to create HTML::TreeBuilder object');
193              
194              
195             # Make sure this is off
196             #
197 36         214 $tree_or->unbroken_text(0);
198              
199              
200             # Tell HTML::TreeBuilder we do *not* want to ignore tags it
201             # considers "unknown". Since we use and tags,
202             # amongst other things, we need these to be in the tree
203             #
204 36         400 $tree_or->ignore_unknown(0);
205              
206              
207             # Tell it if we also want to see comments, use XML mode
208             #
209             $tree_or->store_comments(exists($param_hr->{'store_comments'})
210 36 50       661 ? $param_hr->{'store_comments'}
211             : $WEBDYNE_STORE_COMMENTS
212             );
213 36         394 $tree_or->xml_mode(1); # Older versions on HTML::TreeBuilder
214              
215              
216             # No space compacting ?
217             #
218             $tree_or->ignore_ignorable_whitespace(exists($param_hr->{'ignore_ignorable_whitespace'})
219 36 50       191 ? $param_hr->{'ignore_ignorable_whitespace'}
220             : $WEBDYNE_COMPILE_IGNORE_WHITESPACE
221             );
222             $tree_or->no_space_compacting(exists($param_hr->{'no_space_compacting'})
223 36 50       456 ? $param_hr->{'no_space_compacting'}
224             : $WEBDYNE_COMPILE_NO_SPACE_COMPACTING
225             );
226              
227              
228             # Get code ref closure of file to be parsed
229             #
230 36   50     417 my $parse_cr=$tree_or->parse_fh($html_fh) ||
231             return err();
232              
233              
234             # Muck around with strictness of P tags
235             #
236             $tree_or->p_strict(
237             exists($param_hr->{'p_strict'})
238 36 50       279 ? $param_hr->{'p_strict'}
239             : $WEBDYNE_COMPILE_P_STRICT
240             );
241             $tree_or->implicit_body_p_tag(
242             exists($param_hr->{'implicit_body_p_tag'})
243 36 50       571 ? $param_hr->{'implicit_body_p_tag'}
244             : $WEBDYNE_COMPILE_IMPLICIT_BODY_P_TAG
245             );
246              
247              
248             # Now parse through the file, running eof at end as per HTML::TreeBuilder
249             # man page.
250             #
251 36         475 $tree_or->parse($parse_cr);
252            
253              
254             # Close handler if anything goes wrong below
255             #
256             my $close_cr=sub {
257              
258 36     36   190 $tree_or->delete;
259 36         4649 undef $tree_or;
260              
261 36         304 };
262              
263              
264             # Any errors ? Make sure clean-up before throwing error.
265             #
266 36 50       896 if (errstr()) {
267 0         0 return err($close_cr->())
268             }
269              
270              
271             # So far so good. Close tree and file
272             #
273 36         230 $tree_or->eof();
274 36         5549 $html_fh->close();
275            
276            
277             # Set flag (tagname) if we have seen or tags and want to compact
278             # our tree to remove unneccessary etc. since these
279             # tags will never emit a full html page when used for real. Do here as lost
280             # after elementify
281             #
282 36         1265 my $compact_tag=$tree_or->{'_webdyne_compact'};
283 36         52 0 && debug("compact_tag: $compact_tag");
284            
285              
286             # Elementify
287             #
288 36 50       189 $tree_or->elementify() ||
289             return $close_cr->();
290              
291              
292             # Now start iterating through the treebuilder object, creating
293             # our own array tree structure. Do this in a separate method that
294             # is rentrant as the tree is descended
295             #
296             my %meta=(
297 36 100       4907 manifest => $param_hr->{'nomanifest'} ? undef : [$html_cn]
298             );
299 36   33     266 my $data_ar=$self->parse($tree_or, \%meta) || do {
300             return err($close_cr->());
301             };
302 36         55 0 && debug("meta after parse %s", Dumper(\%meta));
303            
304            
305             # Now destroy the HTML::Treebuilder object, or else mem leak occurs
306             #
307 36         126 $close_cr->();
308              
309              
310             # Meta block. Add any webdyne meta data to parse tree
311             #
312 36   50     378 my $head_ar=$self->find_node(
313             {
314              
315             data_ar => $data_ar,
316             tag => 'head',
317              
318             }) || return err();
319 36   50     240 my $meta_ar=$self->find_node(
320             {
321              
322             data_ar => $head_ar->[0],
323             tag => 'meta',
324             all_fg => 1,
325              
326             }) || return err();
327 36         110 0 && debug('meta_ar: %s', Dumper($meta_ar));
328 36         66 foreach my $tag_ar (@{$meta_ar}) {
  36         110  
329 113   50     255 my $attr_hr=$tag_ar->[WEBDYNE_NODE_ATTR_IX] || next;
330 113         126 0 && debug('meta attr_hr: %s', Dumper($attr_hr));
331 113         1021 $attr_hr=$self->subst_attr(undef, $attr_hr);
332 113         150 0 && debug('meta attr_hr post subst: %s', Dumper($attr_hr));
333 113 100       523 if ($attr_hr->{'name'}=~/^webdyne$/i) {
    100          
334 1         6 my @meta=split(/;/, $attr_hr->{'content'});
335 1         3 0 && debug('meta %s', Dumper(\@meta));
336 1         3 foreach my $meta (@meta) {
337 1         6 my ($name, $value)=split(/[=:]/, $meta, 2);
338 1 50       5 defined($value) || ($value=1);
339              
340             # Eval any meta attrs like @{}, %{}..
341 1   50     5 my $hr=$self->subst_attr(undef, {$name => $value}) ||
342             return err();
343 1         5 $meta{$name}=$hr->{$name};
344 1 50       6 if ($name eq 'cache') {
345 0   0     0 $meta{'static'} ||= 1;
346             }
347             }
348              
349             # Do not want anymore
350             $self->delete_node(
351             {
352              
353 1 50       7 data_ar => $data_ar,
354             node_ar => $tag_ar
355              
356             }) || return err();
357             }
358             elsif (ref($attr_hr->{'content'}) eq 'HASH') {
359 1         3 while (my($meta_key, $meta_value)=each %{$attr_hr->{'content'}}) {
  2         14  
360 1 50       34 if ($meta_key=~/^webdyne$/i) {
361 1         5 my @meta=split(/;/, $meta_value);
362 1         2 0 && debug('meta %s', Dumper(\@meta));
363 1         3 foreach my $meta (@meta) {
364 1         6 my ($name, $value)=split(/[=:]/, $meta, 2);
365 1 50       4 defined($value) || ($value=1);
366 1         3 $meta{$name}=$value;
367 1 50       8 if ($name eq 'cache') {
368 0   0     0 $meta{'static'} ||= 1;
369             }
370             }
371             }
372             }
373             }
374             }
375            
376            
377             # And look for any static or cache tags found in start_html and noted
378             #
379 36         102 foreach my $attr (qw(static cache handler)) {
380 108 100       344 if (my $value=$html_tiny_or->{"_${attr}"}) {
381 1         3 $meta{$attr}=$value;
382             }
383             }
384 36         58 0 && debug('final inode meta: %s', Dumper(\%meta));
385            
386            
387             # If or / nodes and throw
388             # everything else away.
389             #
390 36 100       87 if ($compact_tag) {
391            
392             # Find all the api nodes
393             #
394 1         3 my $api_ar=$self->find_node({
395             data_ar => $data_ar,
396             tag => $compact_tag,
397             all_fg => 1
398             });
399            
400            
401             # And make a new data_ar structure to hold it, throwing everything else
402             # in the bin
403             #
404 1         3 $data_ar=[undef, undef, $api_ar];
405              
406             }
407            
408              
409             # Construct final webdyne container
410             #
411 36 50       138 my @container=(keys %meta ? \%meta : undef, $data_ar);
412              
413              
414             # Quit if user wants to see tree at this stage (stage0 | opt0)
415             #
416 36 100       213 $param_hr->{'stage0'} && (return \@container);
417              
418              
419             # Store meta information for this instance so that when perl_init (or code running under perl_init)
420             # runs it can access meta data via $self->meta();
421             #
422 32 50       219 $self->{'_meta_hr'}=\%meta if keys %meta;
423 32 100 100     188 if ((my $perl_ar=$meta{'perl'}) && !$param_hr->{'noperl'}) {
424              
425             # This is inline __PERL__ perl. Must be executed before filter so any filters added by the __PERL__
426             # block are seen
427             #
428 2         8 my $perl_debug_ar=$meta{'perl_debug'};
429 2 50       14 $self->perl_init($perl_ar, $perl_debug_ar) || return err();
430              
431              
432             }
433              
434              
435             # Quit if user wants to see tree at this stage
436             #
437 32 100       260 $param_hr->{'stage1'} && (return \@container);
438              
439              
440             # Filter ?
441             #
442 28         57 my @filter=@{$meta{'webdynefilter'}};
  28         102  
443 28 50       89 unless (@filter) {
444 28   33     269 my $filter=$self->{'_filter'} || $r->dir_config('WebDyneFilter');
445 28 50       74 @filter=split(/\s+/, $filter) if $filter;
446             }
447 28         56 0 && debug('filter %s', Dumper(\@filter));
448 28 50 33     93 if ((@filter) && !$param_hr->{'nofilter'}) {
449 0         0 local $SIG{'__DIE__'};
450 0         0 foreach my $filter (@filter) {
451 0         0 $filter=~s/::filter$//;
452 0 0       0 eval("require $filter") ||
453             return err("unable to load filter $filter, " . lcfirst($@));
454 0 0       0 UNIVERSAL::can($filter, 'filter') ||
455             return err("custom filter '$filter' does not seem to have a 'filter' method to call");
456 0         0 $filter.='::filter';
457 0   0     0 $data_ar=$self->$filter($data_ar, \%meta) || return err();
458             }
459             }
460              
461              
462             # Quit if user wants to see tree at this stage
463             #
464 28 100       181 $param_hr->{'stage2'} && (return \@container);
465              
466              
467             # Optimise tree, first step
468             #
469 24   50     147 $data_ar=$self->optimise_one($data_ar) || return err();
470 24         55 $container[1]=$data_ar;
471              
472              
473             # Quit if user wants to see tree at this stage (stage3|opt1)
474             #
475 24 100 66     286 ($param_hr->{'stage3'} || $param_hr->{'opt1'}) && (return \@container);
476              
477              
478             # Optimise tree, second step
479             #
480 20   50     116 $data_ar=$self->optimise_two($data_ar) || return err();
481 20         88 $container[1]=$data_ar;
482              
483              
484             # Quit if user wants to see tree at this stage (stage4|opt2)
485             #
486 20 100 66     249 ($param_hr->{'stage4'} || $param_hr->{'opt2'}) && (return \@container);
487              
488              
489             # Is there any dynamic data ? If not, set meta html flag to indicate
490             # document is complete HTML
491             #
492 16 100       29 unless (grep {ref($_)} @{$data_ar}) {
  38         99  
  16         38  
493 5         12 $meta{'html'}=1;
494             }
495              
496              
497             # Construct final webdyne container
498             #
499 16 50       81 @container=(keys %meta ? \%meta : undef, $data_ar);
500              
501              
502             # Quit if user wants to final container (stage5|final)
503             #
504 16 100       146 $param_hr->{'stage5'} && (return \@container);
505              
506              
507             # Save compiled object. Can't store code based cache refs, will be
508             # recreated anyway (when reloaded), so delete, save, then restore
509             #
510 12         21 my $cache_cr;
511 12 50       61 if (ref($meta{'cache'}) eq 'CODE') {$cache_cr=delete $meta{'cache'}}
  0         0  
512              
513              
514             # Store to cache file if dest filename given
515             #
516 12 50       46 if ($dest_cn) {
517 0         0 0 && debug("attempting to cache to dest $dest_cn");
518 0         0 local $SIG{'__DIE__'};
519 0 0       0 eval {Storable::lock_store(\@container, $dest_cn)} || do {
  0         0  
520              
521             # This used to be fatal
522             #
523             #return err("error storing compiled $html_cn to dest $dest_cn, $@");
524              
525              
526             # No more, just log warning and continue - no point crashing an otherwise
527             # perfectly good app because we can't write to a directory
528             #
529             $r->log_error(
530             "error storing compiled $html_cn to dest $dest_cn, $@ - " .
531             'please ensure destination directory is writeable.'
532             )
533 0 0       0 unless $Package{'warn_write'}++;
534 0         0 0 && debug("caching FAILED to $dest_cn");
535              
536             };
537             }
538             else {
539 12         24 0 && debug('no destination file for compile - not caching');
540             }
541              
542              
543             # Put the cache code ref back again now we have finished storing.
544             #
545 12 50       32 $cache_cr && ($meta{'cache'}=$cache_cr);
546              
547              
548             # Work out the page compile time, log
549             #
550 12         175 my $time_compile=sprintf('%0.4f', time()-$time);
551             $meta{'time_compile_elapsed'}=$time_compile unless
552 12 100       57 $param_hr->{'notimestamp'};
553             $meta{'time_compile'}=$time unless
554 12 100       45 $param_hr->{'notimestamp'};
555 12         19 0 && debug("form $html_cn compile time $time_compile");
556              
557              
558             # Destroy self
559             #
560 12         24 undef $self;
561              
562              
563             # Done
564             #
565 12         402 return \@container;
566              
567             }
568              
569              
570             sub compile_init {
571              
572              
573             # Used to init package, move ugliness out of handler
574             #
575 5     5 0 11 my $class=shift();
576 5         9 0 && debug("in compile_init class $class");
577              
578              
579             # Used to do some custom stuff here but now stub. Add anything wanted
580             #
581             #*WebDyne::HTML::Tiny::start_html0=sub {
582             # my ($self, $attr_hr)=@_;
583             # keys %{$attr_hr} || ($attr_hr=$WEBDYNE_HTML_PARAM);
584             # my $html_attr=join(' ', map {qq($_="$attr_hr->{$_}")} keys %{$attr_hr});
585             # return $WEBDYNE_DTD . ($html_attr ? "" : '');
586             #};
587              
588             # All done
589             #
590 5         31 return \undef;
591              
592              
593             }
594              
595              
596             sub optimise_one {
597              
598              
599             # Optimise a data tree
600             #
601 24     24 0 71 my ($self, $data_ar)=@_;
602              
603              
604             # Debug
605             #
606 24         37 0 && debug('optimise stage one');
607              
608              
609             # Get CGI object and disable shortcut tags (e.g. start_html);
610             #
611 24   50     138 my $html_tiny_or=$self->{'_html_tiny_or'} || $self->html_tiny() ||
612             return err("unable to get CGI object from self ref");
613 24         42 0 && debug("CGI $html_tiny_or");
614 24         161 $html_tiny_or->shortcut_disable();
615              
616              
617             # Recursive anon sub to do the render
618             #
619             my $compile_cr=sub {
620              
621              
622             # Get self ref, node array
623             #
624 228     228   368 my ($compile_cr, $data_ar)=@_;
625              
626              
627             # Only do if we have children, if we do a foreach over nonexistent child node
628             # it will spring into existance as empty array ref, which we then have to
629             # wastefully store
630             #
631 228 100       418 if ($data_ar->[WEBDYNE_NODE_CHLD_IX]) {
632              
633              
634             # Process sub nodes to get child html data
635             #
636 134         156 foreach my $data_chld_ix (0..$#{$data_ar->[WEBDYNE_NODE_CHLD_IX]}) {
  134         351  
637              
638              
639             # Get data child
640             #
641 258         349 my $data_chld_ar=$data_ar->[WEBDYNE_NODE_CHLD_IX][$data_chld_ix];
642 258         273 0 && debug("data_chld_ar $data_chld_ar");
643              
644              
645             # If ref, recursivly run through compile process
646             #
647 258 100       510 ref($data_chld_ar) && do {
648              
649              
650             # Run through compile sub-process
651             #
652 204   50     600 my $data_chld_xv=$compile_cr->($compile_cr, $data_chld_ar) ||
653             return err();
654 204 100       436 if (ref($data_chld_xv) eq 'SCALAR') {
655 151         199 $data_chld_xv=${$data_chld_xv}
  151         220  
656             }
657              
658              
659             # Replace in tree
660             #
661 204         549 $data_ar->[WEBDYNE_NODE_CHLD_IX][$data_chld_ix]=$data_chld_xv;
662              
663             }
664              
665             }
666              
667             }
668              
669              
670             # Get this node tag and attrs
671             #
672             my ($html_tag, $attr_hr)=
673 228         303 @{$data_ar}[WEBDYNE_NODE_NAME_IX, WEBDYNE_NODE_ATTR_IX];
  228         445  
674 228         266 0 && debug("tag $html_tag, attr %s", Dumper($attr_hr));
675              
676              
677             # Check to see if any of the attributes will require a subst to be carried out
678             #
679 228         260 my @subst_oper;
680             my $subst_fg=$data_ar->[WEBDYNE_NODE_SBST_IX] || delete $attr_hr->{'subst'} ||
681 228   66     872 grep {$_=~/([\$@%!+*^])\{(\1?)(.*?)\2\}/ && push(@subst_oper, $1)} values %{$attr_hr};
682              
683              
684             # Do not subst comments
685             #
686 228 100       464 ($html_tag=~/~comment$/) && ($subst_fg=undef);
687              
688              
689             # If subst_fg present, means we must do a subst on attr vars. Flag
690             #
691 228 100       395 $subst_fg && ($data_ar->[WEBDYNE_NODE_SBST_IX]=1);
692              
693              
694             # A CGI tag can be marked static, means that we can pre-render it for efficieny
695             #
696 228         336 my $static_fg=$attr_hr->{'static'};
697 228         261 0 && debug("tag $html_tag, static_fg: $static_fg, subst_fg: $subst_fg, subst_oper %s", Dumper(\@subst_oper));
698              
699              
700             # If static, but subst requires an eval, we can do now *only* if @ or % tags though,
701             # and some !'s that do not need request object etc. Cannot do on $
702             #
703 228 50 33     427 if ($static_fg && $subst_fg) {
704              
705              
706             # Cannot optimes subst values with ${value}, must do later
707             #
708 0 0       0 (grep {$_ eq '$'} @subst_oper) && return $data_ar;
  0         0  
709              
710              
711             # Do it
712             #
713 0   0     0 $attr_hr=$self->WebDyne::subst_attr(undef, $attr_hr) ||
714             return err();
715              
716             }
717              
718              
719             # If not special WebDyne tag, see if we can render node
720             #
721 228 100 66     1181 if ((!$CGI_TAG_WEBDYNE{$html_tag} && !$CGI_TAG_FORM{$html_tag} && !$subst_fg) || $static_fg) {
      66        
722             #if ((!$CGI_TAG_WEBDYNE{$html_tag} && !$subst_fg) || $static_fg) {
723              
724              
725             # Check all child nodes to see if ref or scalar
726             #
727 212         260 0 && debug("if 1");
728             my $ref_fv=$data_ar->[WEBDYNE_NODE_CHLD_IX] &&
729 212   100     404 grep {ref($_)} @{$data_ar->[WEBDYNE_NODE_CHLD_IX]};
730              
731              
732             # If all scalars (ie no refs found)t, we can simply pre render all child nodes
733             #
734 212         245 0 && debug("ref_fv: $ref_fv");
735 212 100       413 unless ($ref_fv) {
736              
737              
738             # Done with static tag, delete so not rendered
739             #
740 160         210 delete $attr_hr->{'static'};
741              
742              
743             # Special case. If WebDyne tag and static, render now via WebDyne. Experimental
744             #
745 160 50       269 if ($CGI_TAG_WEBDYNE{$html_tag}) {
746              
747              
748             # Render via WebDyne
749             #
750 0         0 0 && debug("about to render tag $html_tag, attr %s", Dumper($attr_hr));
751 0   0     0 my $html_sr=$self->$html_tag($data_ar, $attr_hr) ||
752             return err();
753 0         0 0 && debug("html *$html_sr*, *${$html_sr}*");
754 0         0 return $html_sr;
755              
756              
757             }
758             else {
759 160         189 0 && debug('not CGI_TAG_WEBDYNE')
760             }
761              
762              
763             # Wrap up in our HTML tag. Do in eval so we can catch errors from invalid tags etc
764             #
765             #
766 160 100       266 my @data_child=$data_ar->[WEBDYNE_NODE_CHLD_IX] ? @{$data_ar->[WEBDYNE_NODE_CHLD_IX]} : undef;
  81         190  
767 160         173 0 && debug("about to call $html_tag with attr_hr:%s, data_child: %s", Dumper($attr_hr, \@data_child));
768 160   50     239 my $html=eval {
769             $attr_hr=undef unless keys %{$attr_hr};
770             if ($html_tiny_or->can($html_tag)) {
771             0 && debug("calling HTML::Tiny->$html_tag directly to render");
772             $html_tiny_or->$html_tag(grep {$_} $attr_hr, join(undef, @data_child))
773             }
774             else {
775             0 && debug("calling HTML::Tiny->tag($html_tag) to render");
776             $html_tiny_or->tag($html_tag, grep {$_} $attr_hr, join(undef, @data_child))
777             }
778              
779             # Older attempts
780             #
781             #$html_tiny_or->$html_tag(grep {$_} $attr_hr || {}, join(undef, @data_child))
782             #$html_tiny_or->$html_tag($attr_hr || {}, join(undef, grep {$_} @data_child))
783             } ||
784            
785             # Use errsubst as CGI may have DIEd during eval and be caught by WebDyne SIG handler
786             return errsubst(
787             "CGI tag '<$html_tag>': %s",
788             $@ || sprintf("undefined error rendering tag '$html_tag', attr_hr:%s, data_child:%s", Dumper($attr_hr, \@data_child))
789             );
790              
791              
792             # Debug
793             #
794             #debug("html *$html*");
795              
796              
797             # Done
798             #
799 160         5084 return \$html;
800              
801             }
802              
803              
804             }
805             else {
806 16         27 0 && debug('fell through node render, no webdyne, subst tags etc.');
807             }
808              
809              
810             # Return current node, perhaps now somewhat optimised
811             #
812 68         232 $data_ar
813              
814 24         179 };
815              
816              
817             # Push data block onto stack as error hint
818             #
819 24         45 push @{$self->{'_data_ar_err'}}, $data_ar;
  24         102  
820            
821            
822             # Run it
823             #
824 24   50     67 $data_ar=$compile_cr->($compile_cr, $data_ar) || return err();
825            
826            
827             # No error, pop error hint
828             #
829 24         43 pop @{$self->{'_data_ar_err'}};
  24         57  
830            
831            
832             # Re-enable shortcuts
833             #
834 24         117 $html_tiny_or->shortcut_enable();
835              
836              
837             # If scalar ref returned it is all HTML - return as plain scalar
838             #
839 24 100       113 if (ref($data_ar) eq 'SCALAR') {
840 9         12 $data_ar=${$data_ar}
  9         18  
841             }
842              
843              
844             # Done
845             #
846 24         711 return $data_ar;
847              
848             }
849              
850              
851             sub optimise_two {
852              
853              
854             # Optimise a data tree
855             #
856 20     20 0 57 my ($self, $data_ar)=@_;
857              
858              
859             # Debug
860             #
861 20         51 0 && debug('optimise stage two');
862              
863              
864             # Get CGI object and turn off shortcuts like start_html
865             #
866 20   50     167 my $html_tiny_or=$self->{'_html_tiny_or'} || $self->html_tiny() ||
867             return err("unable to get CGI object from self ref");
868 20         71 $html_tiny_or->shortcut_disable();
869              
870              
871             # Recursive anon sub to do the render
872             #
873             my $compile_cr=sub {
874              
875              
876             # Get self ref, node array
877             #
878 58     58   150 my ($compile_cr, $data_ar, $data_uppr_ar)=@_;
879              
880              
881             # Only do if we have children, if do a foreach over nonexistent child node
882             # it will spring into existance as empty array ref, which we then have to
883             # wastefully store
884             #
885 58 100       121 if ($data_ar->[WEBDYNE_NODE_CHLD_IX]) {
886              
887              
888             # Process sub nodes to get child html data
889             #
890             my @data_child_ar=$data_ar->[WEBDYNE_NODE_CHLD_IX]
891             ?
892 45 50       85 @{$data_ar->[WEBDYNE_NODE_CHLD_IX]}
  45         112  
893             : undef;
894 45         83 foreach my $data_chld_ar (@data_child_ar) {
895              
896              
897             # Debug
898             #
899             #debug("found child node $data_chld_ar");
900              
901              
902             # If ref, run through compile process recursively
903             #
904 85 100       200 ref($data_chld_ar) && do {
905              
906              
907             # Run through compile sub-process
908             #
909 45   50     363 $data_ar=$compile_cr->($compile_cr, $data_chld_ar, $data_ar) ||
910             return err();
911              
912             }
913              
914              
915             }
916              
917             }
918              
919              
920             # Get this tag and attrs
921             #
922             my ($html_tag, $attr_hr)=
923 58         95 @{$data_ar}[WEBDYNE_NODE_NAME_IX, WEBDYNE_NODE_ATTR_IX];
  58         153  
924 58         87 0 && debug("tag $html_tag");
925              
926              
927             # Check if this tag attributes will need substitution (eg ${foo});
928             #
929             my $subst_fg=$data_ar->[WEBDYNE_NODE_SBST_IX] || delete $attr_hr->{'subst'} ||
930 58   33     315 grep {$_=~/([\$@%!+*^])\{(\1?)(.*?)\2\}/so} values %{$attr_hr};
931              
932              
933             # If subst_fg present, means we must do a subst on attr vars. Flag, also get static flag
934             #
935 58 100       129 $subst_fg && ($data_ar->[WEBDYNE_NODE_SBST_IX]=1);
936 58         127 my $static_fg=delete $attr_hr->{'static'};
937              
938              
939             # If not special WebDyne tag, and no dynamic params we can render this node into
940             # its final HTML format
941             #
942 58 100 66     617 if (!$CGI_TAG_WEBDYNE{$html_tag} && !$CGI_TAG_IMPLICIT{$html_tag} && $data_uppr_ar && !$subst_fg) {
    50 100        
    100 100        
    100 66        
      66        
      100        
      66        
943              
944              
945             # Get nodes into array now, removes risk of iterating over shifting ground
946             #
947 31         53 0 && debug("compile_cr: if 1");
948             my @data_child_ar=$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]
949             ?
950 31 50       95 @{$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]}
  31         85  
951             : undef;
952              
953              
954             # Get uppr node
955             #
956 31         102 foreach my $data_chld_ix (0..$#data_child_ar) {
957              
958              
959             # Get node, skip unless ref
960             #
961 59         110 my $data_chld_ar=$data_child_ar[$data_chld_ix];
962 59 100       128 ref($data_chld_ar) || next;
963              
964              
965             # Debug
966             #
967             #debug("looking at node $data_chld_ix, $data_chld_ar vs $data_ar");
968              
969              
970             # Skip unless eq us
971             #
972 32 100       101 next unless ($data_chld_ar eq $data_ar);
973              
974              
975             # Get start and end tag methods
976             #
977 31         93 my ($html_tag_start, $html_tag_end)=
978             ("start_${html_tag}", "end_${html_tag}");
979              
980              
981             # Translate tags into HTML
982             #
983             my ($html_start, $html_end)=map {
984 31         62 0 && debug("render tag $_");
  62         771  
985 62 50 0     93 eval {
986 62         96 $html_tiny_or->$_(grep {$_} $attr_hr)
  62         349  
987             } ||
988              
989             # Use errsubst as CGI may have DIEd during eval and be caught by WebDyne SIG handler
990             return errsubst(
991             "CGI tag '<$_>' error- %s",
992             $@ || "undefined error rendering tag '$_'"
993             );
994             } ($html_tag_start, $html_tag_end);
995              
996              
997             # Splice start and end tags for this HTML into appropriate place
998             #
999 31         73 splice @{$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]}, $data_chld_ix, 1, grep {$_}
  205         422  
1000             $html_start,
1001             (WEBDYNE_HTML_NEWLINE && "\n"),
1002 31         191 @{$data_ar->[WEBDYNE_NODE_CHLD_IX]},
  31         57  
1003             (WEBDYNE_HTML_NEWLINE && "\n"),
1004             $html_end;
1005              
1006             # Done, no need to iterate any more
1007             #
1008 31         84 last;
1009              
1010              
1011             }
1012              
1013              
1014             # Concatenate all non ref values in the parent. Var to hold results
1015             #
1016 31         51 my @data_uppr;
1017              
1018              
1019             # Repopulate data child array, as probably changed in above foreach
1020             # block.
1021             #
1022             @data_child_ar=$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]
1023             ?
1024 31 50       96 @{$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]}
  31         113  
1025             : undef;
1026              
1027             #@data_child_ar=@{$data_uppr_ar->[$WEBDYNE_NODE_CHLD_IX]};
1028              
1029              
1030             # Begin concatenation
1031             #
1032 31         108 foreach my $data_chld_ix (0..$#data_child_ar) {
1033              
1034              
1035             # Get child
1036             #
1037 172         251 my $data_chld_ar=$data_child_ar[$data_chld_ix];
1038              
1039              
1040             # Can we concatenate with above node
1041             #
1042 172 100 100     644 if (@data_uppr && !ref($data_chld_ar) && !ref($data_uppr[$#data_uppr])) {
      100        
1043              
1044              
1045             # Yes, concatentate
1046             #
1047 76         178 $data_uppr[$#data_uppr].=$data_chld_ar;
1048              
1049             }
1050             else {
1051              
1052             # No, push onto new data_uppr array
1053             #
1054 96         214 push @data_uppr, $data_chld_ar;
1055              
1056             }
1057             }
1058              
1059              
1060             # Replace with new optimised array
1061             #
1062 31         107 $data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]=\@data_uppr;
1063              
1064              
1065             }
1066             elsif ($CGI_TAG_WEBDYNE{$html_tag} && $data_uppr_ar && $static_fg) {
1067              
1068              
1069             # Now render to make HTML and modify the data arrat above us with the rendered code
1070             #
1071 0         0 0 && debug("compile_cr: if 2");
1072 0   0     0 my $html_sr=$self->render_data_ar(
1073             data => [$data_ar],
1074             ) || return err();
1075             my @data_child_ar=$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]
1076             ?
1077 0 0       0 @{$data_uppr_ar->[WEBDYNE_NODE_CHLD_IX]}
  0         0  
1078             : undef;
1079 0         0 foreach my $ix (0..$#data_child_ar) {
1080 0 0       0 if ($data_uppr_ar->[WEBDYNE_NODE_CHLD_IX][$ix] eq $data_ar) {
1081 0         0 $data_uppr_ar->[WEBDYNE_NODE_CHLD_IX][$ix]=${$html_sr};
  0         0  
1082 0         0 last;
1083             }
1084             }
1085              
1086              
1087             }
1088             elsif (!$data_uppr_ar && $html_tag) {
1089            
1090            
1091             # Must be at top node, as nothing above us,
1092             # get start and end tag methods
1093             #
1094 12         23 0 && debug("compile_cr: if 3");
1095 12         58 my ($html_tag_start, $html_tag_end)=
1096             ("start_${html_tag}", "end_${html_tag}");
1097              
1098              
1099             # Get resulting start and ending HTML
1100             #
1101             my ($html_start, $html_end)=map {
1102 12         27 0 && debug("render tag $_");
  24         226  
1103 24 50 0     47 eval {
1104 24         41 $html_tiny_or->$_(grep {$_} $attr_hr)
  24         106  
1105             } ||
1106             return errsubst(
1107             "CGI tag '<$_>': %s",
1108             $@ || "undefined error rendering tag '$_'"
1109             );
1110              
1111             #return err("$@" || "no html returned from tag $_")
1112             } ($html_tag_start, $html_tag_end);
1113             my @data_child_ar=$data_ar->[WEBDYNE_NODE_CHLD_IX]
1114             ?
1115 12 50       95 @{$data_ar->[WEBDYNE_NODE_CHLD_IX]}
  12         40  
1116             : undef;
1117              
1118             # Place start and end tags for this HTML into appropriate place
1119             #
1120 12         55 my @data=( grep {$_}
  86         173  
1121             $html_start,
1122             (WEBDYNE_HTML_NEWLINE && "\n"),
1123             @data_child_ar,
1124             (WEBDYNE_HTML_NEWLINE && "\n"),
1125             $html_end
1126             );
1127              
1128              
1129             # Concatenate all non ref vals
1130             #
1131 12         21 my @data_new;
1132 12         39 foreach my $data_chld_ix (0..$#data) {
1133              
1134 62 100 100     295 if ($data_chld_ix && !ref($data[$data_chld_ix]) && !(ref($data[$data_chld_ix-1]))) {
      100        
1135 24         68 $data_new[$#data_new].=$data[$data_chld_ix];
1136             }
1137             else {
1138 38         74 push @data_new, $data[$data_chld_ix]
1139             }
1140              
1141             }
1142              
1143              
1144             # Return completed array
1145             #
1146 12         46 $data_uppr_ar=\@data_new;
1147              
1148              
1149             }
1150             elsif (!$data_uppr_ar && !$html_tag) {
1151            
1152            
1153             # Special case generated by page with tags, means we're not going to wrap in
1154             #
1155 1         2 $data_uppr_ar=$data_ar->[WEBDYNE_NODE_CHLD_IX];
1156            
1157             }
1158            
1159            
1160             # Return current node
1161             #
1162 58         275 return $data_uppr_ar;
1163              
1164              
1165 20         209 };
1166            
1167            
1168             # Push data block onto error hint stack in case of compile error
1169             #
1170 20         43 push @{$self->{'_data_ar_err'}}, $data_ar;
  20         62  
1171              
1172              
1173             # Run it, return whatever it does, allowing for the special case that first stage
1174             # optimisation found no special tags, and precompiled the whole array into a
1175             # single HTML string. In which case return as array ref to allow for correct storage
1176             # and rendering.
1177             #
1178 20         51 my $ret;
1179 20 100       85 if (ref($data_ar)) {
1180 13   33     49 $ret=$compile_cr->($compile_cr, $data_ar, undef) ||
1181             err()
1182             }
1183             else {
1184 7         17 $ret=[$data_ar];
1185             }
1186            
1187            
1188             # No errors, pop error hint stack
1189             #
1190 20         31 pop @{$self->{'_data_ar_err'}};
  20         53  
1191            
1192              
1193             # Re-enable shortcuts
1194             #
1195 20         75 $html_tiny_or->shortcut_enable();
1196            
1197              
1198             # And return
1199             #
1200 20         904 return $ret;
1201              
1202             }
1203              
1204              
1205             sub parse {
1206              
1207              
1208             # A recusively called method to parse a HTML::Treebuilder tree. content is an
1209             # array ref of the HTML entity contents, return custom array tree from that
1210             # structure
1211             #
1212 373     373 0 651 my ($self, $html_or, $meta_hr)=@_;
1213 373         446 my ($line_no, $line_no_tag_end)=@{$html_or}{'_line_no', '_line_no_tag_end'};
  373         700  
1214 373         640 my $html_fn_sr=\$meta_hr->{'manifest'}[0];
1215 373         434 0 && debug("parse $self, $html_or line_no $line_no line_no_tag_end $line_no_tag_end");
1216              
1217              
1218             # Create array to hold this data node
1219             #
1220 373         469 my @data;
1221 373         1272 @data[
1222             WEBDYNE_NODE_NAME_IX, # Tag Name
1223             WEBDYNE_NODE_ATTR_IX, # Attributes
1224             WEBDYNE_NODE_CHLD_IX, # Child nodes
1225             WEBDYNE_NODE_SBST_IX, # Substitution Required
1226             WEBDYNE_NODE_LINE_IX, # Source Line Number
1227             WEBDYNE_NODE_LINE_TAG_END_IX, # What line this tag ends on
1228             WEBDYNE_NODE_SRCE_IX # Source file name
1229             ]=(
1230             #undef, undef, undef, undef, $line_no, $line_no_tag_end, $meta_hr->{'manifest'}[0]
1231             undef, undef, undef, undef, $line_no, $line_no_tag_end, $html_fn_sr
1232             );
1233              
1234              
1235             # Get tag
1236             #
1237 373         756 my $html_tag=$html_or->tag();
1238              
1239              
1240             # Get tag attr
1241             #
1242 373 100       2054 if (my %attr=map {$_ => $html_or->{$_}} (grep {!/^_/} keys %{$html_or})) {
  313         910  
  1951         3236  
  373         983  
1243              
1244              
1245             # Save tagm attr into node
1246             #
1247             #@data[$WEBDYNE_NODE_NAME_IX, $WEBDYNE_NODE_ATTR_IX]=($html_tag, \%attr);
1248              
1249              
1250             # Is this the inline perl __PERL__ block ?
1251             #
1252 198 100 66     516 if ($html_or->{'_code'} && $attr{'perl'}) {
1253 16         25 push @{$meta_hr->{'perl'}}, \$attr{'perl'};
  16         69  
1254 16         28 push @{$meta_hr->{'perl_debug'}}, [$line_no, $html_fn_sr];
  16         70  
1255             }
1256             else {
1257 182         391 @data[WEBDYNE_NODE_NAME_IX, WEBDYNE_NODE_ATTR_IX]=($html_tag, \%attr);
1258             }
1259              
1260             }
1261             else {
1262              
1263              
1264             # No attr, just save tag
1265             #
1266 175         321 $data[WEBDYNE_NODE_NAME_IX]=$html_tag;
1267              
1268             }
1269              
1270              
1271             # Child nodes
1272             #
1273 373         690 my @html_child=@{$html_or->content()};
  373         729  
1274              
1275              
1276             # Get child, parse down the tree
1277             #
1278 373         1367 foreach my $html_child_or (@html_child) {
1279              
1280 421         539 0 && debug("html_child_or $html_child_or");
1281              
1282              
1283             # Ref is a sub-tag, non ref is plain text
1284             #
1285 421 100       683 if (ref($html_child_or)) {
1286              
1287              
1288             # Sub tag. Recurse down tree, updating to nearest line number
1289             #
1290 337         451 $line_no=$html_child_or->{'_line_no'};
1291 337   50     721 my $data_ar=$self->parse($html_child_or, $meta_hr) ||
1292             return err();
1293              
1294              
1295             # If no node name returned is not an error, just a no-op
1296             #
1297 337 100       2046 if ($data_ar->[WEBDYNE_NODE_NAME_IX]) {
1298 321         386 push @{$data[WEBDYNE_NODE_CHLD_IX]}, $data_ar;
  321         657  
1299             }
1300              
1301             }
1302             else {
1303              
1304             # Node is just plain text. Used to not insert empty children, but this
1305             # stuffed up
 sections that use \n for spacing/formatting. Now we 
1306             # are more careful
1307             #
1308 84 0 66     469 push(@{$data[WEBDYNE_NODE_CHLD_IX]}, $html_child_or)
  84   33     243  
      33        
1309             unless (
1310             $html_child_or=~/^\s*$/
1311             &&
1312             ($html_tag ne 'pre') && ($html_tag ne 'textarea') && !$WEBDYNE_COMPILE_NO_SPACE_COMPACTING
1313             );
1314              
1315             }
1316              
1317             }
1318              
1319              
1320             # All done, return data node
1321             #
1322 373         995 return \@data;
1323              
1324             }
1325              
1326