File Coverage

blib/lib/WebDyne/HTML/TreeBuilder.pm
Criterion Covered Total %
statement 197 212 92.9
branch 48 56 85.7
condition 27 38 71.0
subroutine 27 30 90.0
pod 6 15 40.0
total 305 351 86.8


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::HTML::TreeBuilder;
15              
16              
17             # Compiler Pragma
18             #
19 1     1   6 use strict qw(vars);
  1         2  
  1         31  
20 1     1   4 use vars qw($VERSION @ISA %CGI_TAG_WEBDYNE %CGI_TAG_IMPLICIT %CGI_TAG_SPECIAL);
  1         2  
  1         49  
21 1     1   4 use warnings;
  1         2  
  1         22  
22 1     1   4 no warnings qw(uninitialized redefine once);
  1         1  
  1         28  
23              
24              
25             # WebDyne Modules
26             #
27 1     1   4 use WebDyne;
  1         2  
  1         18  
28 1     1   4 use WebDyne::Constant;
  1         2  
  1         415  
29 1     1   6 use WebDyne::Base;
  1         2  
  1         8  
30              
31              
32             # External Modules. Keep HTML::Entities or nullification of encode/decode
33             # subs will not work below
34             #
35 1     1   7 use HTML::TreeBuilder;
  1         2  
  1         15  
36 1     1   27 use HTML::Entities;
  1         2  
  1         49  
37 1     1   5 use HTML::Tagset;
  1         2  
  1         22  
38 1     1   4 use IO::File;
  1         1  
  1         138  
39 1     1   6 use Data::Dumper;
  1         1  
  1         2616  
40              
41              
42             # Inheritance
43             #
44             @ISA=qw(HTML::TreeBuilder);
45              
46              
47             # Version information
48             #
49             $VERSION='1.248';
50              
51              
52             # Debug load
53             #
54             0 && debug("Loading %s version $VERSION", __PACKAGE__);
55              
56              
57             # Make a hash of our implictly closed tags. TODO, expand to full list,
58             # instead of most used.
59             #
60             #%CGI_TAG_IMPLICIT=map { $_=>1 } (
61             #
62             # 'popup_menu',
63             # 'textfield',
64             # 'textarea',
65             # 'radio_group',
66             # 'password_field',
67             # 'filefield',
68             # 'scrolling_list',
69             # 'checkbox_group',
70             # 'checkbox',
71             # 'hidden',
72             # 'submit',
73             # 'reset',
74             # 'dump'
75             #
76             # );
77              
78              
79             # Update - get from CGI module, add special dump tag
80             %CGI_TAG_IMPLICIT=map {$_ => 1} (
81              
82             @{$CGI::EXPORT_TAGS{':form'}},
83             'dump'
84              
85             );
86              
87              
88             # Get WebDyne tags from main module
89             #
90             %CGI_TAG_WEBDYNE=%WebDyne::CGI_TAG_WEBDYNE;
91              
92              
93             # The tags below need to be handled specially at compile time - see the method
94             # associated with each tag below.
95             #
96             map {$CGI_TAG_SPECIAL{$_}++} qw(perl script style start_html end_html include);
97              
98              
99             # Nullify Entities encode & decode
100             #
101       0     *HTML::Entities::encode=sub { };
102       389     *HTML::Entities::decode=sub { };
103              
104              
105             # Add to islist items in TreeBuilder
106             #
107             map {$HTML::TreeBuilder::isList{$_}++} keys %CGI_TAG_WEBDYNE;
108              
109              
110             # Need to tell HTML::TagSet about our special elements so
111             #
112             map {$HTML::Tagset::isTableElement{$_}++} keys %CGI_TAG_WEBDYNE;
113              
114              
115             # And that we also block

tag closures

116             #
117             push @HTML::TreeBuilder::p_closure_barriers, keys %CGI_TAG_WEBDYNE;
118              
119              
120             # Local vars neeeded for cross sub comms
121             #
122             our ($Text_fg, $Line_no, $Line_no_next, $Line_no_start, $HTML_Perl_or, @HTML_Wedge);
123              
124              
125             # All done. Positive return
126             #
127             1;
128              
129              
130             #==================================================================================================
131              
132              
133             sub parse_fh {
134              
135              
136             # Get self ref, file handle
137             #
138 14     14 0 31 my ($self, $html_fh)=@_;
139 14         16 0 && debug("parse $html_fh");
140              
141              
142             # Turn off HTML_Perl object global, in case left over from a __PERL__ segment
143             # at the bottom of the last file parsed. Should never happen, as we check in
144             # delete() also
145             #
146 14 50       29 $HTML_Perl_or && ($HTML_Perl_or=$HTML_Perl_or->delete());
147 14         28 undef $Text_fg;
148 14         15 undef $Line_no;
149 14         20 undef $Line_no_start;
150 14         21 undef $Line_no_next;
151 14         23 undef @HTML_Wedge;
152              
153              
154             # Return closure code ref that understands how to count line
155             # numbers and wedge in extra code
156             #
157             my $parse_cr=sub {
158              
159             #$Line_no++;
160 388     388   527 my $line;
161 388 100       1353 my $html=@HTML_Wedge ? shift @HTML_Wedge : ($line=<$html_fh>);
162 388 100       709 if ($line) {
163 356         380 0 && debug("line $line");
164 356         975 my @cr=($line=~/\n/g);
165 356   100     647 $Line_no=$Line_no_next || 1;
166 356         459 $Line_no_next=$Line_no+@cr;
167 356         495 0 && debug("Line $Line_no, Line_no_next $Line_no_next, Line_no_start $Line_no_start cr %s", scalar @cr);
168             }
169 388         1559 return $html;
170              
171 14         94 };
172 14         55 return $parse_cr;
173              
174             }
175              
176              
177             sub delete {
178              
179              
180             # Destroy tree, reset any globals
181             #
182 14     14 1 22 my $self=shift();
183 14         20 0 && debug('delete');
184              
185              
186             # Get rid of inline HTML object, if still around
187             #
188 14 100       61 $HTML_Perl_or && ($HTML_Perl_or=$HTML_Perl_or->delete());
189              
190              
191             # Reset script and line number vars
192             #
193 14         307 undef $Text_fg;
194 14         24 undef $Line_no;
195 14         24 undef $Line_no_next;
196 14         16 undef $Line_no_start;
197 14         24 undef @HTML_Wedge;
198              
199              
200             # Run real deal from parent
201             #
202 14         78 $self->SUPER::delete(@_);
203              
204              
205             }
206              
207              
208             sub tag_parse {
209              
210              
211             # Get our self ref
212             #
213 513     513 0 810 my ($self, $method)=(shift, shift);
214              
215              
216             # Get the tag, tag attr
217             #
218 513         735 my ($tag, $attr_hr)=@_;
219              
220              
221             # Debug
222             #
223 513         2194 0 && debug("tag_parse $method, $tag, line $Line_no, line_no_start $Line_no_start");
224              
225              
226             # Get the parent tag
227             #
228 513         573 my $pos;
229             my $tag_parent=(
230             $pos=$self->{'_pos'} || $self
231 513   66     1281 )->{'_tag'};
232 513         548 0 && debug("tag $tag, tag_parent $tag_parent");
233              
234              
235             # Var to hold returned html object ref
236             #
237 513         606 my $html_or;
238              
239              
240             # If it is an below an implicit parent tag close that tag now.
241             #
242 513 100 66     4443 if ($CGI_TAG_IMPLICIT{$tag_parent} || $tag_parent=~/^start_/i || $tag_parent=~/^end_/i) {
    100 66        
    100 100        
    100 100        
    100 66        
    100 66        
      100        
243              
244             # End implicit parent if it was an implicit tag
245             #
246 2         9 0 && debug("ending implicit parent tag $tag_parent");
247 2         6 $self->end($tag_parent);
248 2         9 $html_or=$self->$method(@_);
249              
250             }
251              
252              
253             # Special case where wraps or tags. HTML::TreeBuilder assumes
254             # head is always under html - we have to hack.
255             #
256             elsif ($CGI_TAG_WEBDYNE{$tag_parent} && ($tag eq 'head')) {
257              
258             # Debug and modify tree
259             #
260 1         6 0 && debug("found $tag_parent above $tag, modifying tree");
261 1         8 $self->{'_head'}->preinsert($pos);
262 1         79 $self->{'_head'}->detach();
263 1         30 $pos->push_content($self->{'_head'});
264 1         27 $self->$method(@_);
265              
266             }
267              
268              
269             # Same for body tag as above
270             #
271             elsif ($CGI_TAG_WEBDYNE{$tag_parent} && ($tag eq 'body')) {
272              
273 1         6 0 && debug("found $tag_parent above $tag, modifying tree");
274 1         5 $self->{'_body'}->preinsert($pos);
275 1         46 $self->{'_body'}->detach();
276 1         16 $pos->push_content($self->{'_body'});
277 1         16 $self->$method(@_);
278              
279             }
280              
281              
282             # If it is an custom webdyne tag, massage with methods below
283             # before processing
284             #
285             elsif ($CGI_TAG_SPECIAL{$tag} && ($method ne 'SUPER::text')) {
286              
287              
288             # Yes, is WebDyne tag
289             #
290 47         63 0 && debug("webdyne tag ($tag) dispatch");
291 47         176 $html_or=$self->$tag($method, $tag, $attr_hr);
292              
293             }
294              
295              
296             # If it is an custom CGI tag that we need to close implicityly
297             #
298             elsif ($CGI_TAG_IMPLICIT{$tag_parent} || $tag=~/^start_/i || $tag=~/^end_/) {
299              
300              
301             # Yes, is CGI tag
302             #
303 8         38 0 && debug("webdyne tag ($tag) dispatch");
304 8         26 $html_or=$self->$method(@_);
305 8         825 $self->end($tag)
306              
307             }
308              
309              
310             # If its parent was a custom webdyne tag, the turn off implicitness
311             # before processing
312             #
313             elsif ($CGI_TAG_WEBDYNE{$tag_parent}) {
314              
315              
316             # Turn off implicitness here to stop us from being moved
317             # around in the parse tree if we are under a table or some
318             # such
319             #
320 77         99 0 && debug('turning off implicit tags');
321 77         189 $self->implicit_tags(0);
322              
323              
324             # Run the WebDyne tag method.
325             #
326 77         587 0 && debug("webdyne tag_parent ($tag_parent) dispatch");
327 77         147 $html_or=$self->$tag_parent($method, $tag, $attr_hr);
328              
329              
330             # Turn implicitness back on again
331             #
332 77         1838 0 && debug('turning on implicit tags');
333 77         138 $self->implicit_tags(1);
334              
335              
336             }
337             else {
338              
339              
340             # Pass onto our base class for further processing
341             #
342 377         458 0 && debug("base class method $method");
343 377         996 $html_or=$self->$method(@_);
344              
345              
346             }
347              
348              
349             # Insert line number if possible
350             #
351 513         24664 0 && debug("insert line_no $Line_no, line_no_start $Line_no_start into object ref $html_or");
352 513 100       977 ref($html_or) && (@{$html_or}{'_line_no', '_line_no_tag_end'}=($Line_no_start, $Line_no));
  201         436  
353              
354              
355             # Returm object ref
356             #
357 513         880 $html_or;
358              
359              
360             }
361              
362              
363             sub block {
364              
365              
366             # No special handling needed, just log for debugging purposes
367             #
368 30     30 0 46 my ($self, $method)=(shift, shift);
369 30         31 0 && debug("block self $self, method $method, @_ text_fg $Text_fg");
370 30         64 $self->$method(@_);
371              
372             }
373              
374              
375             sub script {
376              
377 2     2 0 14 my ($self, $method, $tag, $attr_hr)=@_;
378 2         6 0 && debug('script');
379 2         5 $Text_fg='script';
380 2         10 my $or=$self->$method($tag, $attr_hr, @_);
381 2 100       295 $or->postinsert('') if $attr_hr->{'src'};
382 2         35 $or;
383              
384             }
385              
386              
387             sub style {
388              
389 0     0 0 0 my ($self, $method)=(shift, shift);
390 0         0 0 && debug('style');
391 0         0 $Text_fg='style';
392 0         0 $self->$method(@_);
393              
394             }
395              
396              
397             sub perl {
398              
399              
400             # Special handling of perl tag
401             #
402 71     71 0 141 my ($self, $method, $tag, $attr_hr)=@_;
403 71         86 0 && debug("$tag $method");
404              
405              
406             # Call SUPER method, check if inline
407             #
408 71         156 my $html_perl_or=$self->$method($tag, $attr_hr);
409 71         6409 my $inline;
410 71 100       176 if ($tag eq 'perl') {
411 24 100       38 unless (grep {exists $attr_hr->{$_}} qw(package class method)) {
  72         157  
412 3         15 $html_perl_or->attr(inline => ++$inline);
413             }
414             }
415 71 100       156 if ($inline) {
416              
417             # Inline tag, set global var to this element so any extra text can be
418             # added here
419             #
420 3         5 $HTML_Perl_or=$html_perl_or;
421 3         7 $Text_fg='perl';
422              
423              
424             # And return it
425             #
426 3         7 return $html_perl_or;
427              
428             }
429             else {
430              
431              
432             # Not inline, just return object
433             #
434 68         117 return $html_perl_or;
435              
436             }
437              
438              
439             }
440              
441              
442             sub process {
443              
444             # Rough and ready process handler, try to handle perl code in . Not sure if I really
445             # want to support this yet ...
446             #
447 3     3 1 10 my ($self, $text)=@_;
448 3         5 0 && debug("process $text");
449 3         10 my $or=HTML::Element->new('perl', inline => 1, perl => $text);
450 3         88 0 && debug("insert line_no $Line_no into object ref $or");
451 3         5 @{$or}{'_line_no', '_line_no_tag_end'}=($Line_no_start, $Line_no);
  3         7  
452 3         6 $self->tag_parse('SUPER::text', $or)
453              
454             }
455              
456              
457             sub start {
458              
459              
460             # Ugly, make sure if in perl or script tag, whatever we see counts
461             # as text
462             #
463 203     203 1 401 my ($self, $tag)=(shift, shift);
464 203         289 my $text=$_[2];
465 203 50       424 ref($tag) || ($tag=lc($tag));
466 203         220 0 && debug("start $tag Line_no $Line_no, @_, %s", Data::Dumper::Dumper(\@_));
467 203         223 my $html_or;
468 203 50       286 if ($Text_fg) {
469 0         0 $html_or=$self->text($text)
470             }
471             else {
472 203         430 my @cr=($text=~/\n/g);
473 203         309 $Line_no_start=$Line_no-@cr;
474 203         219 0 && debug("tag $tag line_no $Line_no, line_no_start $Line_no_start");
475 203         392 $html_or=$self->tag_parse('SUPER::start', $tag, @_);
476              
477             }
478 203         982 $html_or;
479              
480             }
481              
482              
483             sub end {
484              
485              
486             # Ugly special case conditions, ensure end tag between perl or script
487             # blocks are treated as text
488             #
489 216     216 1 3089 my ($self, $tag)=(shift, shift);
490 216 100       449 ref($tag) || ($tag=lc($tag));
491 216         234 0 && debug("end $tag, text_fg $Text_fg, line $Line_no");
492 216         234 my $html_or;
493 216 100 66     461 if ($Text_fg && ($tag eq $Text_fg)) {
    50          
494 5         10 $Text_fg=undef;
495 5         13 $html_or=$self->SUPER::end($tag, @_)
496             }
497             elsif ($Text_fg) {
498 0         0 $html_or=$self->text($_[0])
499             }
500             else {
501 211         479 $html_or=$self->SUPER::end($tag, @_)
502             }
503 216         11101 $html_or;
504              
505              
506             }
507              
508              
509             # Reminder to self. Keep this in, or implicit CGI tags will not be closed
510             # if text block follows implicit CGI tag immediately
511             #
512             sub text {
513              
514              
515             # get self ref, text we will process
516             #
517 408     408 1 1261 my ($self, $text)=@_;
518 408         457 0 && debug("text *$text*, text_fg $Text_fg, pos %s", $self->{'_pos'});
519              
520              
521             # Are we in an inline perl block ?
522             #
523 408 100 66     1479 if ($Text_fg eq 'perl') {
    100          
    50          
524              
525              
526             # Yes. We have inline perl code, not text. Just add to perl attribute, which
527             # is treated specially when rendering
528             #
529 92         95 0 && debug('in __PERL__ tag, appending text to __PERL__ block');
530              
531             # Strip leading CR from Perl code so line numbers in errors make sense
532             #unless ($HTML_Perl_or->{'perl'}) { $text=~s/^\n// }
533 92         157 $HTML_Perl_or->{'perl'}.=$text;
534 92         112 $HTML_Perl_or->{'_line_no_tag_end'}=$Line_no;
535              
536              
537             }
538              
539             # Used to do this so __PERL__ block would only count if at end of file.
540             #elsif (($text=~/^\W*__CODE__/ || $text=~/^\W*__PERL__/) && !$self->{'_pos'}) {
541              
542             elsif (($text=~/^\W*__CODE__/ || $text=~/^\W*__PERL__/)) {
543              
544              
545             # Close off any HTML
546             #
547 9 50       25 delete $self->{'_pos'} if $self->{'_pos'};
548              
549              
550             # Perl code fragment. Will be last thing we do, as __PERL__ must be at the
551             # bottom of the file.
552             #
553 9         11 0 && debug('found __PERL__ tag');
554 9         16 $Text_fg='perl';
555 9         47 $self->implicit(0);
556 9         154 $self->push_content($HTML_Perl_or=HTML::Element->new('perl', inline => 1));
557 9         350 0 && debug("insert line_no $Line_no into object ref $HTML_Perl_or");
558 9         17 @{$HTML_Perl_or}{'_line_no', '_line_no_tag_end'}=($Line_no, $Line_no);
  9         25  
559 9         20 $HTML_Perl_or->{'_code'}++;
560              
561             }
562             elsif ($text=~/^\W*__END__/) {
563              
564              
565             # End of file
566             #
567 0         0 0 && debug('found __END__ tag, running eof');
568 0         0 $self->eof();
569              
570             }
571             else {
572              
573             # Normal text, process by parent class after handling any subst flags in code
574             #
575             #if ($text=~/([$|!|+|^|*]+)\{([$|!|+]?)(.*?)\2\}/gs) {
576 307 100       591 if ($text=~/([$|!|+|^|*]+)\{([$|!|+]?)(.*?)\2\}/s) {
577              
578             # Meeds subst. Get rid of cr's at start and end of text after a tag, stuffs up formatting in
 sections 
579             #
580 16         25 0 && debug("found subst tag line_no_start $Line_no_start, line_no $Line_no, text '$text'");
581 16         51 my @cr=($text=~/\n/g);
582 16 50       77 if (my $html_or=$self->{'_pos'}) {
583 16         20 0 && debug("parent %s", $html_or->tag());
584 16 100 66     45 if (($html_or->tag() eq 'perl') && !$html_or->attr('inline')) {
585 3         77 0 && debug('hit !');
586              
587             #$text=~s/^\n//;
588             #$text=~s/\n$//;
589             }
590             }
591              
592 16         118 my $or=HTML::Element->new('subst');
593 16         312 my $line_no_start=$Line_no;
594 16         20 0 && debug("insert line_no $Line_no_start, line_no_tag_end $Line_no into object ref $or for text $text, cr %s", scalar @cr);
595 16         22 @{$or}{'_line_no', '_line_no_tag_end'}=($line_no_start, $Line_no);
  16         31  
596 16         43 $or->push_content($text);
597 16         211 $self->tag_parse('SUPER::text', $or)
598             }
599             else {
600              
601             # No subst, process as normal
602             #
603 291         302 0 && debug('processing as normal text');
604 291         478 $self->tag_parse('SUPER::text', $text)
605             }
606              
607             }
608              
609              
610             # Return self ref. Not really sure if this is what we should really return, but
611             # seems to work
612             #
613 408         1931 $self;
614              
615             }
616              
617              
618             sub comment {
619              
620 0     0 1 0 0 && debug('comment');
621 0         0 my $self=shift()->SUPER::comment(@_);
622 0         0 0 && debug("insert line_no $Line_no into object ref $self");
623 0         0 @{$self}{'_line_no', '_line_no_tag_end'}=($Line_no_start, $Line_no);
  0         0  
624 0         0 $self;
625              
626             }
627              
628              
629             sub start_html {
630              
631             # Need to handle this specially ..
632 10     10 0 25 my ($self, $method, $tag, $attr_hr)=@_;
633 10 50       22 if ($WEBDYNE_CONTENT_TYPE_HTML_META) {
634 0   0     0 $attr_hr->{'head'} ||= &CGI::meta({"http-equiv" => "Content-Type", content => $WEBDYNE_CONTENT_TYPE_HTML})
635             }
636 10         60 my $html=&CGI::start_html_cgi($attr_hr);
637 10         5409 0 && debug("html is $html");
638 10         25 push @HTML_Wedge, $html;
639 10         25 $self;
640             }
641              
642              
643             sub end_html {
644              
645             # Need to handle this specially ..
646 8     8 0 35 my ($self, $method, $tag, $attr_hr)=@_;
647 8         50 my $html=&CGI::end_html_cgi($attr_hr);
648 8         38 0 && debug("html is $html");
649 8         15 push @HTML_Wedge, $html;
650 8         15 $self;
651             }
652              
653              
654             sub include {
655              
656              
657             # No special handling needed, just log for debugging purposes
658             #
659 3     3 0 14 my ($self, $method)=(shift, shift);
660 3         8 0 && debug("block self $self, method $method, @_ text_fg $Text_fg");
661 3         15 $self->$method(@_);
662              
663              
664             }
665              
666