File Coverage

blib/lib/WebDyne/Compile.pm
Criterion Covered Total %
statement 280 328 85.3
branch 83 132 62.8
condition 72 131 54.9
subroutine 26 30 86.6
pod 0 6 0.0
total 461 627 73.5


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