File Coverage

blib/lib/HTML/Template/Compiled/Parser.pm
Criterion Covered Total %
statement 341 357 95.5
branch 115 142 80.9
condition 21 32 65.6
subroutine 60 64 93.7
pod 0 25 0.0
total 537 620 86.6


line stmt bran cond sub pod time code
1             package HTML::Template::Compiled::Parser;
2 36     36   134 use Carp qw(croak carp confess);
  36         42  
  36         1956  
3 36     36   138 use strict;
  36         40  
  36         572  
4 36     36   107 use warnings;
  36         38  
  36         779  
5 36     36   104 use base qw(Exporter);
  36         35  
  36         2089  
6 36     36   12867 use HTML::Template::Compiled::Token qw(:tagtypes);
  36         57  
  36         3995  
7 36     36   160 use Scalar::Util;
  36         36  
  36         1977  
8             our $VERSION = '1.002_001'; # TRIAL VERSION
9             my @vars;
10             BEGIN {
11 36     36   923 @vars = qw(
12             $CASE_SENSITIVE_DEFAULT
13             $NEW_CHECK
14             $ENABLE_SUB
15             $DEBUG_DEFAULT
16             $SEARCHPATH
17             %FILESTACK %COMPILE_STACK %PATHS $DEFAULT_ESCAPE $DEFAULT_QUERY
18             $UNTAINT $DEFAULT_TAGSTYLE $MAX_RECURSE
19             );
20             }
21             our @EXPORT_OK = @vars;
22 36     36   124 use vars @vars;
  36         34  
  36         5002  
23             $MAX_RECURSE = 10;
24              
25             $NEW_CHECK = 60 * 10; # 10 minutes default
26             $DEBUG_DEFAULT = 0;
27             $CASE_SENSITIVE_DEFAULT = 1; # set to 0 for H::T compatibility
28             $ENABLE_SUB = 0;
29             $SEARCHPATH = 0;
30             $DEFAULT_ESCAPE = 0;
31             $UNTAINT = 0;
32             $DEFAULT_QUERY = 0;
33             $DEFAULT_TAGSTYLE = [qw(classic comment asp)];
34              
35 36     36   131 use constant ATTR_TAGSTYLE => 0;
  36         41  
  36         1574  
36 36     36   117 use constant ATTR_TAGNAMES => 1;
  36         35  
  36         1393  
37 36     36   117 use constant ATTR_PERL => 2;
  36         40  
  36         1233  
38 36     36   113 use constant ATTR_EXPRESSION => 3;
  36         41  
  36         1281  
39 36     36   120 use constant ATTR_CHOMP => 4;
  36         38  
  36         1223  
40 36     36   108 use constant ATTR_STRICT => 5;
  36         36  
  36         1357  
41              
42 36     36   121 use constant T_VAR => 'VAR';
  36         37  
  36         1310  
43 36     36   125 use constant T_IF => 'IF';
  36         36  
  36         1322  
44 36     36   118 use constant T_UNLESS => 'UNLESS';
  36         42  
  36         1289  
45 36     36   118 use constant T_ELSIF => 'ELSIF';
  36         40  
  36         1284  
46 36     36   117 use constant T_ELSE => 'ELSE';
  36         37  
  36         1189  
47 36     36   111 use constant T_IF_DEFINED => 'IF_DEFINED';
  36         41  
  36         1184  
48 36     36   110 use constant T_END => '__EOT__';
  36         37  
  36         1173  
49 36     36   107 use constant T_WITH => 'WITH';
  36         33  
  36         1146  
50 36     36   109 use constant T_SWITCH => 'SWITCH';
  36         40  
  36         1199  
51 36     36   110 use constant T_CASE => 'CASE';
  36         36  
  36         1288  
52 36     36   113 use constant T_INCLUDE => 'INCLUDE';
  36         36  
  36         1186  
53 36     36   115 use constant T_LOOP => 'LOOP';
  36         35  
  36         1291  
54 36     36   120 use constant T_WHILE => 'WHILE';
  36         34  
  36         1305  
55 36     36   127 use constant T_INCLUDE_VAR => 'INCLUDE_VAR';
  36         40  
  36         1241  
56              
57 36     36   117 use constant CHOMP_NONE => 0;
  36         34  
  36         1496  
58 36     36   119 use constant CHOMP_ONE => 1;
  36         41  
  36         1312  
59 36     36   156 use constant CHOMP_COLLAPSE => 2;
  36         40  
  36         1249  
60 36     36   124 use constant CHOMP_GREEDY => 3;
  36         33  
  36         90721  
61              
62             # under construction (sic!)
63             sub new {
64 69     69 0 94 my $class = shift;
65 69         121 my %args = @_;
66 69         77 my $self = [];
67 69         99 bless $self, $class;
68 69         179 $self->init(%args);
69 69         243 $self;
70             }
71              
72 0     0 0 0 sub set_tagstyle { $_[0]->[ATTR_TAGSTYLE] = $_[1] }
73 161     161 0 209 sub get_tagstyle { $_[0]->[ATTR_TAGSTYLE] }
74              
75 0     0 0 0 sub set_tagnames { $_[0]->[ATTR_TAGNAMES] = $_[1] }
76 553     553 0 1704 sub get_tagnames { $_[0]->[ATTR_TAGNAMES] }
77              
78 131     131 0 232 sub set_perl { $_[0]->[ATTR_PERL] = $_[1] }
79 4     4 0 10 sub get_perl { $_[0]->[ATTR_PERL] }
80              
81 98     98 0 201 sub set_expressions { $_[0]->[ATTR_EXPRESSION] = $_[1] }
82 409     409 0 645 sub get_expressions { $_[0]->[ATTR_EXPRESSION] }
83              
84 131     131 0 237 sub set_chomp { $_[0]->[ATTR_CHOMP] = $_[1] }
85 1018     1018 0 1157 sub get_chomp { $_[0]->[ATTR_CHOMP] }
86              
87 98     98 0 165 sub set_strict { $_[0]->[ATTR_STRICT] = $_[1] }
88 4     4 0 8 sub get_strict { $_[0]->[ATTR_STRICT] }
89              
90             sub add_tagnames {
91 4     4 0 5 my ($self, $hash) = @_;
92 4         5 my $open = $hash->{OPENING_TAG()};
93 4         7 my $close = $hash->{CLOSING_TAG()};
94 4         10 @{ $_[0]->[ATTR_TAGNAMES]->{OPENING_TAG()} }{keys %$open} = values %$open;
  4         13  
95 4         7 @{ $_[0]->[ATTR_TAGNAMES]->{CLOSING_TAG()} }{keys %$close} = values %$close;
  4         11  
96             }
97              
98             sub remove_tags {
99 1     1 0 1 my ($self, @tags) = @_;
100 1         3 my $open = $self->[ATTR_TAGNAMES]->{OPENING_TAG()};
101 1         1 my $close = $self->[ATTR_TAGNAMES]->{CLOSING_TAG()};
102 1         2 delete @$open{@tags};
103 1         3 delete @$close{@tags};
104             }
105              
106             my $_default_tags = {
107             classic => ['', '', ],
108              
109             comment => ['','',],
110              
111             asp => ['<%' ,'%>', '<%/', '%>', ],
112              
113             php => ['<\?' ,'\?>', '<\?/', '\?>', ],
114              
115             tt => ['\[%' ,'%\]', '\[%/', '%\]' , ],
116             };
117             sub default_tags {
118 247     247 0 551 return $_default_tags;
119             }
120              
121             my $default_validation = sub {
122             my ($p, $attr) = @_;
123             my $test = $p->get_expressions;
124             exists $attr->{NAME} or
125             ($p->get_expressions and exists $attr->{EXPR})
126             };
127             my %allowed_tagnames = (
128             OPENING_TAG() => {
129             VAR => [$default_validation, qw(NAME ESCAPE DEFAULT EXPR)],
130             # just an alias for VAR
131             '=' => [$default_validation, qw(NAME ESCAPE DEFAULT EXPR)],
132             IF_DEFINED => [$default_validation, qw(NAME EXPR)],
133             IF => [$default_validation, qw(NAME EXPR)],
134             UNLESS => [$default_validation, qw(NAME EXPR)],
135             ELSIF => [$default_validation, qw(NAME EXPR)],
136             ELSE => [undef, qw(NAME)],
137             WITH => [$default_validation, qw(NAME EXPR)],
138             COMMENT => [undef, qw(NAME)],
139             VERBATIM => [undef, qw(NAME)],
140             NOPARSE => [undef, qw(NAME)],
141             LOOP => [$default_validation, qw(NAME ALIAS JOIN BREAK EXPR CONTEXT)],
142             WHILE => [$default_validation, qw(NAME ALIAS BREAK EXPR)],
143             EACH => [$default_validation, qw(NAME BREAK EXPR SORT SORTBY REVERSE CONTEXT)],
144             SWITCH => [$default_validation, qw(NAME EXPR)],
145             CASE => [undef, qw(NAME)],
146             INCLUDE_VAR => [$default_validation, qw(NAME EXPR)],
147             INCLUDE_STRING => [$default_validation, qw(NAME EXPR)],
148             INCLUDE => [$default_validation, qw(NAME)],
149             USE_VARS => [$default_validation, qw(NAME)],
150             SET_VAR => [$default_validation, qw(NAME VALUE EXPR)],
151             WRAPPER => [$default_validation, qw(NAME)],
152             },
153             CLOSING_TAG() => {
154             IF_DEFINED => [undef, qw(NAME)],
155             IF => [undef, qw(NAME)],
156             UNLESS => [undef, qw(NAME)],
157             ELSIF => [undef, qw(NAME)],
158             WITH => [undef, qw(NAME)],
159             COMMENT => [undef, qw(NAME)],
160             VERBATIM => [undef, qw(NAME)],
161             NOPARSE => [undef, qw(NAME)],
162             LOOP => [undef, qw(NAME)],
163             WHILE => [undef, qw(NAME)],
164             EACH => [undef, qw(NAME)],
165             SWITCH => [undef, qw(NAME)],
166             WRAPPER => [undef, qw(NAME)],
167             }
168             );
169              
170              
171             sub init {
172 69     69 0 97 my ( $self, %args ) = @_;
173 69   50     407 my $tagnames = $args{tagnames} || {};
174 69         194 my $tagstyle = $self->_create_tagstyle( $args{tagstyle} );
175 69         253 $self->[ATTR_TAGSTYLE] = $tagstyle;
176 69         84 $self->[ATTR_EXPRESSION] = $args{use_expressions};
177 69         90 $self->[ATTR_CHOMP] = $args{chomp};
178 69         119 $self->[ATTR_STRICT] = $args{strict};
179             $self->[ATTR_TAGNAMES] = {
180             OPENING_TAG() => {
181 69         266 %{ $allowed_tagnames{ OPENING_TAG() } },
182 69 50       653 %{ $tagnames->{ OPENING_TAG() }||{} },
183             },
184             CLOSING_TAG() => {
185 69         201 %{ $allowed_tagnames{ CLOSING_TAG() } },
186 69 50       69 %{ $tagnames->{ CLOSING_TAG() }||{} },
  69         523  
187             },
188             };
189             } ## end sub init
190              
191             sub _create_tagstyle {
192 69     69   1249 my ($self, $tagstyle_def) = @_;
193 69   100     259 $tagstyle_def ||= [];
194 69         62 my $tagstyle;
195             my $named_styles = {
196             map {
197 69         118 ($_ => $self->default_tags->{$_})
  207         283  
198             } @$DEFAULT_TAGSTYLE
199             };
200 69         155 for my $def (@$tagstyle_def) {
201 131 50 33     314 if (ref $def eq 'ARRAY' && @$def == 4) {
    50          
202             # we got user defined regexes
203 0         0 push @$tagstyle, $def;
204             }
205             elsif (!ref $def) {
206             # strings
207 131 100       331 if ($def =~ m/^-(.*)/) {
    50          
208             # deactivate style
209 91         152 delete $named_styles->{"$1"};
210             }
211             elsif ($def =~ m/^\+?(.*)/) {
212             # activate style
213 40         60 $named_styles->{"$1"} = $self->default_tags->{"$1"};
214             }
215             }
216             }
217 69         165 push @$tagstyle, values %$named_styles;
218 69         148 return $tagstyle;
219             }
220              
221             sub find_start_of_tag {
222 1068     1068 0 933 my ($self, $arg) = @_;
223 1068         852 my $re = $arg->{start_close_re};
224 1068 100   1   6150 if ($arg->{template} =~ s/^($re)//) {
  1         495  
  1         7  
  1         10  
225 519         425 my %open_close_map = %{$arg->{open_close_map}};
  519         1741  
226             # $open contains
227 519         1019 $arg->{open} = $1;
228 519         667 $arg->{token} .= $1;
229             # check which type of tag we got
230 519         1016 TYPES: for my $key (keys %open_close_map) {
231             #print STDERR "try $key '$arg->{open}'\n";
232 1620 100       12300 if ($arg->{open} =~ m/^$key$/i) {
233 519         530 my $val = $open_close_map{$key};
234 519         597 $arg->{close_match} = $val->[1];
235 519         600 $arg->{open_or_close} = $val->[0];
236             #print STDERR "=== tag type $key, searching for $arg->{close_match}\n";
237 519         841 last TYPES;
238             }
239             }
240             #print STDERR "got start_close_re\n";
241 519         1628 return 1;
242             }
243             else {
244 549         17614 return;
245             }
246             }
247              
248             sub find_attributes {
249 509     509 0 579 my ($self, $arg) = @_;
250             #warn Data::Dumper->Dump([\%args], ['args']);
251 509         431 my $allowed = [@{ $arg->{allowed_names} }, 'PRE_CHOMP', 'POST_CHOMP'];
  509         1190  
252 509         496 my $attr = $arg->{attr};
253 509         458 my $fname = $arg->{fname};
254 509         443 my $line = $arg->{line};
255              
256 509         808 my ($validate_sub, @allowed) = @$allowed;
257             my $allowed_names = [ sort {
258 509         1067 length($b) <=> length($a)
  3318         2991  
259             } @allowed ];
260 509         963 my $re = join '|', @$allowed_names;
261 509         462 ATTR: while (1) {
262 969 100       3785 last if $arg->{template} =~ m/^($arg->{close_match})/;
263 462         854 my ($name, $val, $orig) = $self->find_attribute( $arg, $re );
264 462 50       813 last unless defined $name;
265 462         510 my $key = uc $name;
266 462 50 66     954 if ($key =~ m/^(?:PRE|POST)_CHOMP\z/ and $val !~ m/^(?:0|1|2|3)\z/) {
267             $self->_error_wrong_tag_syntax(
268             $arg,
269 0         0 $orig.$arg->{template}, '(PRE|POST)_CHOMP=(0|1|2|3)',
270             );
271             }
272 462 100       747 if (exists $attr->{$key}) {
273             $self->_error_wrong_tag_syntax(
274             $arg,
275 2         5 $orig.$arg->{template}, 'duplicate attribute',
276             );
277             }
278 460         688 $attr->{$key} = $val;
279 460         669 $arg->{token} .= $orig;
280             }
281 507 100       1056 my $ok = $validate_sub ? $validate_sub->($self, $attr) : 1;
282             $self->_error_wrong_tag_syntax(
283             $arg, $arg->{template}
284 507 100       802 ) unless $ok;
285 505         1606 return $ok;
286             }
287              
288             {
289             my $callbacks_found_text;
290             my $encode_tag = sub {
291             my ( $p, $arg ) = @_;
292             $arg->{token} = HTML::Template::Compiled::Utils::escape_html($arg->{token});
293             $callbacks_found_text->[0]->($p, $arg);
294             $arg->{token} = "";
295             };
296              
297             my $ignore_tag = sub {
298             my ( $p, $arg ) = @_;
299             $arg->{token} = "";
300             };
301             my $default_callback_text = sub {
302             my ($self, $arg) = @_;
303             $arg->{line} += $arg->{token} =~ tr/\n//;
304             #print STDERR "we found text: '$arg->{token}}'\n";
305             push @{$arg->{tags}},
306             HTML::Template::Compiled::Token::Text->new([
307             $arg->{token}, $arg->{line},
308             undef, undef, undef, $arg->{fname}, $arg->{level}
309             ]);
310             $arg->{token} = "";
311             };
312             my $default_callback_tag = sub {
313             my ($self, $arg) = @_;
314             #print STDERR "####found tag $arg->{name}, $arg->{open_or_close}\n";
315             $arg->{line} += $arg->{token} =~ tr/\n//;
316             my $class = 'HTML::Template::Compiled::Token::' .
317             ($arg->{open_or_close} == OPENING_TAG
318             ? 'open'
319             : 'close');
320              
321             my $token = $class->new([
322             $arg->{token}, $arg->{line},
323             [$arg->{open}, $arg->{close}], $arg->{name},
324             { %{ $arg->{attr} } },
325             $arg->{fname}, $arg->{level},
326             ]);
327             push @{$arg->{tags}}, $token;
328             if ($token->is_open &&
329             exists
330             $self->get_tagnames->{CLOSING_TAG()}->{ $arg->{name} }) {
331             $arg->{level}++
332             }
333             elsif ($token->is_close) {
334             $arg->{level}--
335             }
336             $self->checkstack( $arg );
337             $arg->{token} = "";
338             };
339             $callbacks_found_text = [ $default_callback_text ];
340              
341             sub parse {
342 161     161 0 190 my ($self, $fname, $template) = @_;
343 161         326 my $tagnames = $self->get_tagnames;
344 161         174 my %allowed_ident;
345             $allowed_ident{OPENING_TAG()} = "(?i:" . join("|", sort {
346 11728         8154 length $b <=> length $a
347 161         175 } keys %{ $tagnames->{OPENING_TAG()} }) . ")";
  161         1139  
348             $allowed_ident{CLOSING_TAG()} = "(?i:" . join("|", sort {
349 5660         3976 length $b <=> length $a
350 161         301 } keys %{ $tagnames->{CLOSING_TAG()} }) . ")";
  161         478  
351 161         383 my $tagstyle = $self->get_tagstyle;
352             # make (?i:IF_DEFINED|LOOP|IF|=|...) out of the list of identifiers
353             my $start_close_re = '(?i:' . join("|", sort {
354 1101         1094 length($b) <=> length($a)
355             } map {
356 161         284 $_->[0], $_->[2]
  424         772  
357             } @$tagstyle) . ")";
358             my $close_re = '(?i:' . join("|", sort {
359 1100         985 length($b) <=> length($a)
360             } map {
361 161         262 $_->[1], $_->[3]
  424         586  
362             } @$tagstyle) . ")";
363             my %open_close = map {
364 161         227 (
365 424         1468 $_->[0] => [
366             OPENING_TAG, $_->[1]
367             ],
368             $_->[2] => [
369             CLOSING_TAG, $_->[3]
370             ],
371             ),
372             } @$tagstyle;
373              
374 161         230 my $comment_info;
375 161         265 my $callback_found_tag = [ $default_callback_tag ];
376              
377             my $callback = sub {
378 509     509   497 my ( $p, $arg ) = @_;
379 509         555 my $name = $arg->{name};
380             #print STDERR "callback found tag $name\n";
381 509 100       1994 if ( $name eq 'COMMENT' ) {
    100          
    100          
    100          
    100          
    100          
382 4 100       9 if ( $arg->{open_or_close} == OPENING_TAG ) {
    50          
383 2 100       6 ++$comment_info->{$name} == 1
384             and push @$callbacks_found_text, $ignore_tag;
385             }
386             elsif ( $arg->{open_or_close} == CLOSING_TAG ) {
387 2 100       4 --$comment_info->{$name} == 0
388             and pop @$callbacks_found_text;
389             }
390 4         9 $arg->{token} = "";
391             }
392             elsif ( $comment_info->{COMMENT} ) {
393 2         5 $arg->{token} = "";
394             }
395             elsif ($name eq 'NOPARSE') {
396 4 100       9 if ( $arg->{open_or_close} == OPENING_TAG ) {
    50          
397 2 100       4 if (++$comment_info->{$name} == 1) {
398 1         4 $arg->{token} = "";
399             }
400             else {
401 1         2 $callbacks_found_text->[0]->(@_);
402             }
403             }
404             elsif ( $arg->{open_or_close} == CLOSING_TAG ) {
405 2 100       5 if (--$comment_info->{$name} == 0) {
406 1         3 $arg->{token} = "";
407             }
408             else {
409 1         2 $callbacks_found_text->[0]->(@_);
410             }
411             }
412             }
413             elsif ( $comment_info->{NOPARSE} ) {
414 2         18 $callbacks_found_text->[0]->(@_);
415             }
416             elsif ($name eq 'VERBATIM') {
417 2 100       6 if ( $arg->{open_or_close} == OPENING_TAG ) {
    50          
418 1 50       3 if (++$comment_info->{$name} == 1) {
419 1         3 $arg->{token} = "";
420             }
421             else {
422 0         0 $encode_tag->(@_);
423             }
424             }
425             elsif ( $arg->{open_or_close} == CLOSING_TAG ) {
426 1 50       3 if (--$comment_info->{$name} == 0) {
427 1         3 $arg->{token} = "";
428             }
429             else {
430 0         0 $encode_tag->(@_);
431             }
432             }
433             }
434             elsif ( $comment_info->{VERBATIM} ) {
435 1         3 $encode_tag->(@_);
436             }
437             else {
438 494         780 $callback_found_tag->[-2]->(@_);
439             }
440 161         791 };
441 161         235 push @$callback_found_tag, $callback;
442              
443 161         3744 my $arg = {
444             fname => $fname,
445             level => 0,
446             line => 1,
447             name => '',
448             template => $template,
449             token => '',
450             open_or_close => undef,
451             open => undef,
452             open_close_map => \%open_close,
453             start_close_re => qr{$start_close_re},
454             close_match => qr{close_re},
455             attr => {},
456             allowed_names => [],
457             tags => [],
458             close => undef,
459             stack => [T_END],
460             };
461 161         487 while (length $arg->{template}) {
462             #warn Data::Dumper->Dump([\@tags], ['tags']);
463             #print STDERR "TEXT: $template ($start_close_re)\n";
464             #print STDERR "TOKEN: '$arg->{token}'\n";
465 1068         827 my $found_tag = 0;
466 1068         1072 $arg->{attr} = {};
467             MATCH_TAGS: {
468 1068 100       1261 last MATCH_TAGS unless $self->find_start_of_tag($arg);
  1068         1557  
469             # at this point we have a start of a tag. everything
470             # that does not look like correct tag content generates
471             # a die!
472 519         809 my $re = $allowed_ident{$arg->{open_or_close}};
473 519 100       17765 if ($arg->{template} =~ s/^(($re)\s*)//) {
    100          
    50          
    100          
    100          
474 513         1132 $arg->{name} = uc $2;
475 513         679 $arg->{token} .= $1;
476 513 100       1011 if ($arg->{name} eq '=') { $arg->{name} = 'VAR' }
  124         180  
477             }
478             elsif ($comment_info->{NOPARSE}) {
479 1         2 $callbacks_found_text->[0]->($self, $arg);
480 1         2 last MATCH_TAGS;
481             }
482             elsif ($comment_info->{VERBATIM}) {
483 0         0 $encode_tag->($self, $arg);
484 0         0 last MATCH_TAGS;
485             }
486             elsif ($comment_info->{COMMENT}) {
487 1         1 last MATCH_TAGS;
488             }
489             elsif ($self->get_strict) {
490             $self->_error_wrong_tag_syntax(
491 3         10 $arg, $arg->{template}, "Unknown tag"
492             );
493 0         0 last MATCH_TAGS;
494             }
495             else {
496 1         3 $arg->{template} =~ s/^(\w+)//;
497 1         3 $arg->{token} .= $1;
498 1         2 $callbacks_found_text->[0]->($self, $arg);
499 1         1 last MATCH_TAGS;
500             }
501             #print STDERR "got ident $arg->{name} ('$arg->{template}')\n";
502             $arg->{allowed_names}
503 513         1499 = $tagnames->{ $arg->{open_or_close} }->{ $arg->{name} };
504 513 100 66     1121 if ($arg->{name} eq 'PERL' && $self->get_perl) {
505 4 50       5 last MATCH_TAGS unless $self->find_perlcode($arg);
506             }
507             else {
508 509 50       873 last MATCH_TAGS unless $self->find_attributes($arg);
509             }
510              
511 509 50       2380 if ($arg->{template} =~ s/^($arg->{close_match})//) {
512 509         767 $arg->{close} = $1;
513 509         649 $arg->{token} .= $1;
514             }
515             else {
516 0         0 $self->_error_wrong_tag_syntax( $arg, "" );
517 0         0 last MATCH_TAGS;
518             }
519 509         778 $found_tag = 1;
520             }
521 1061 100       5712 if ($found_tag) {
    50          
522 509         755 my $pre_chomp = $self->get_chomp->[0];
523 509         462 my $attr = $arg->{attr};
524 509 100       813 $pre_chomp = $attr->{PRE_CHOMP} if exists $attr->{PRE_CHOMP};
525 509         627 my $post_chomp = $self->get_chomp->[1];
526 509 100       778 $post_chomp = $attr->{POST_CHOMP} if exists $attr->{POST_CHOMP};
527 509 100 100     347 if (@{$arg->{tags}} > 0 and $pre_chomp) {
  509         1900  
528 3         13 my $text = $arg->{tags}->[-1]->get_text;
529 3 50       14 if ($pre_chomp == CHOMP_ONE) {
    50          
    50          
530 0         0 $text =~ s/ +\z//;
531             }
532             elsif ($pre_chomp == CHOMP_COLLAPSE) {
533 0         0 $text =~ s/\s+\z/ /;
534             }
535             elsif ($pre_chomp == CHOMP_GREEDY) {
536 3         14 $text =~ s/\s+\z//;
537             }
538 3         14 $arg->{tags}->[-1]->set_text($text);
539             }
540 509 100 100     1560 if (length $arg->{template} and $post_chomp) {
541 7 100       18 if ($post_chomp == CHOMP_ONE) {
    50          
    50          
542 4         5 $arg->{template} =~ s/^ +//;
543             }
544             elsif ($post_chomp == CHOMP_COLLAPSE) {
545 0         0 $arg->{template} =~ s/^\s+/ /;
546             }
547             elsif ($post_chomp == CHOMP_GREEDY) {
548 3         7 $arg->{template} =~ s/^\s+//;
549             }
550             }
551             #print STDERR "found tag $arg->{name}\n";
552             #my $test = $callback_found_tag->[-1];
553             #print STDERR "(found_tags: @$callback_found_tag) $test\n";
554 509   50 0   1122 ( $callback_found_tag->[-1] || sub { } )->(
555             $self,
556             $arg,
557             );
558             #print STDERR "===== ($open, $line, $ident, $close)\n";
559             #warn Data::Dumper->Dump([\@tags], ['tags']);
560             }
561             elsif ($arg->{template} =~ s/^(.+?)(?=($start_close_re|\Z))//s) {
562 552         1016 $arg->{token} .= $1;
563 552   50 0   1333 ($callbacks_found_text->[-1] || sub {} )->(
564             $self,
565             $arg,
566             );
567             #print "got no tag: '$arg->{token}'\n";
568             }
569              
570             }
571 152         582 Scalar::Util::weaken($callback_found_tag);
572 152         1586 $self->checkstack({
573             %$arg, name => T_END, open_or_close => CLOSING_TAG
574             } );
575 151         459 return @{$arg->{tags} };
  151         2859  
576             }
577             }
578              
579 36     36   13100 use HTML::Template::Compiled::Exception;
  36         56  
  36         27209  
580             sub _error_wrong_tag_syntax {
581 8     8   11 my ($self, $arg, $text, $add_info) = @_;
582 8         43 my ($substr) = $text =~ m/^(.{0,10})/s;
583 8   33     22 my $class = ref $self || $self;
584 8         33 my $info = "$class : Syntax error in tag at $arg->{fname} :"
585             . "$arg->{line} near '$arg->{token}$substr...'";
586 8 100       23 $info .= " $add_info" if defined $add_info;
587             my $ex = HTML::Template::Compiled::Exception->new(
588             text => $info,
589             parser => $self,
590             tokens => $arg->{tags},
591 8         43 near => $text,
592             );
593 8         326 croak $ex;
594             }
595              
596             sub find_perlcode {
597 4     4 0 5 my ($self, $arg) = @_;
598 4         4 my $attr = $arg->{attr};
599 4 50       36 if ($arg->{template} =~ s{^ (.*?)
600             (?=$arg->{close_match})
601             }{}xs) {
602 4         8 $attr->{PERL} = "$1";
603 4         6 $arg->{token} .= $1;
604 4         9 return 1;
605             }
606 0         0 return;
607             }
608              
609             sub find_attribute {
610 462     462 0 508 my ($self, $arg, $re) = @_;
611 462         353 my ($name, $var, $orig);
612             #print STDERR "=====(($arg->{template}))\n";
613 462 100       9583 if ($arg->{template} =~ s/^(\s*($re)=)//i) {
614 155         264 $name = "$2";
615 155         256 $orig .= $1;
616             }
617             #print STDERR "match '$$text' (?=$until|\\s)\n";
618 462 100       4349 if ($arg->{template} =~ s{^ (\s* (['"]) (.+?) \2 \s*) }{}x) {
    50          
619             #print STDERR qq{matched $2$3$2\n};
620 113         181 $var = "$3";
621 113         152 $orig .= $1;
622             }
623             elsif ($arg->{template} =~ s{^ (\s* (\S+?) \s*)
624             (?=$arg->{close_match} | \s) }{}x) {
625             #print STDERR qq{matched <$2>\n};
626 349         505 $var = "$2";
627 349         500 $orig .= $1;
628             }
629 0         0 else { return }
630 462 100       902 unless (defined $name) {
631 307         317 $name = "NAME";
632             }
633 462         1115 return ($name, $var, $orig);
634             }
635              
636             {
637             my @map;
638             $map[OPENING_TAG] = {
639             ELSE => [ T_IF, T_UNLESS, T_ELSIF, T_IF_DEFINED ],
640             T_CASE() => [T_SWITCH],
641             };
642             $map[CLOSING_TAG] = {
643             IF => [ T_IF, T_UNLESS, T_ELSE, T_IF_DEFINED ],
644             T_IF_DEFINED() => [ T_ELSE, T_IF_DEFINED ],
645             UNLESS => [T_UNLESS, T_ELSE, T_IF_DEFINED],
646             ELSIF => [ T_IF, T_UNLESS, T_IF_DEFINED ],
647             LOOP => [T_LOOP],
648             WHILE => [T_WHILE],
649             WITH => [T_WITH],
650             T_SWITCH() => [T_SWITCH],
651             T_END() => [T_END],
652             };
653              
654             sub validate_stack {
655 646     646 0 492 my ( $self, $arg ) = @_;
656 646 100       2184 if (defined( my $allowed
657             = $map[$arg->{open_or_close}]->{$arg->{name}})) {
658 254 50 33     220 return 1 if @{ $arg->{stack} } == 0 and @$allowed == 0;
  254         623  
659             die "Closing tag 'TMPL_$arg->{name}' does not have opening tag"
660             . "at $arg->{fname} line $arg->{line}\n"
661 254 50       227 unless @{ $arg->{stack} };
  254         601  
662 254 100 100     869 if ( $allowed->[0] eq T_END and $arg->{stack}->[-1] ne T_END ) {
663             # we hit the end of the template but still have an opening tag to close
664 1         42 die "Missing closing tag for '$arg->{stack}->[-1]' at"
665             . "end of $arg->{fname} line $arg->{line}\n";
666             }
667 253         414 for (@$allowed) {
668 277 100       741 return 1 if $_ eq $arg->{stack}->[-1];
669             }
670 2         317 croak "'TMPL_$arg->{name}' does not match opening tag ($arg->{stack}->[-1])"
671             . "at $arg->{fname} line $arg->{line}\n";
672             }
673             }
674              
675             sub checkstack {
676 646     646 0 596 my ( $self, $arg ) = @_;
677 646         852 my $ok = $self->validate_stack($arg );
678 643 100       1220 if ($arg->{open_or_close} == OPENING_TAG) {
    50          
679 391 100       424 if (
    100          
680 2737         3476 grep { $arg->{name} eq $_ } (
681             T_WITH, T_LOOP, T_WHILE, T_IF, T_UNLESS, T_SWITCH, T_IF_DEFINED
682             )
683             ) {
684 92         81 push @{ $arg->{stack} }, $arg->{name};
  92         184  
685             }
686             elsif ($arg->{name} eq T_ELSE) {
687 6         6 pop @{ $arg->{stack} };
  6         10  
688 6         7 push @{ $arg->{stack} }, T_ELSE;
  6         9  
689             }
690             }
691             elsif ($arg->{open_or_close} == CLOSING_TAG) {
692 252 100       308 if (grep { $arg->{name} eq $_ } (
  1764         2003  
693             T_IF, T_IF_DEFINED, T_UNLESS, T_WITH, T_LOOP, T_WHILE, T_SWITCH
694             )) {
695 89         78 pop @{ $arg->{stack} };
  89         133  
696             }
697             }
698 643         629 return $ok;
699             }
700              
701             }
702              
703             {
704             my $default_parser = __PACKAGE__->new;
705 98     98 0 527 sub default { return bless [@$default_parser], __PACKAGE__ }
706             }
707              
708             1;
709              
710             __END__