File Coverage

blib/lib/HTML/Blitz/RuleSet.pm
Criterion Covered Total %
statement 417 456 91.6
branch 252 372 67.7
condition 68 131 51.9
subroutine 32 33 96.9
pod 0 6 0.0
total 769 998 77.1


line stmt bran cond sub pod time code
1             # This code can be redistributed and modified under the terms of the GNU
2             # General Public License as published by the Free Software Foundation, either
3             # version 3 of the License, or (at your option) any later version.
4             # See the "COPYING" file for details.
5             package HTML::Blitz::RuleSet;
6 11     11   83 use HTML::Blitz::pragma;
  11         21  
  11         88  
7 11     11   9622 use HTML::Blitz::Matcher ();
  11         32  
  11         266  
8 11     11   6320 use HTML::Blitz::Parser ();
  11         39  
  11         490  
9 11     11   124 use HTML::Blitz::CodeGen ();
  11         27  
  11         332  
10 11         931 use HTML::Blitz::TokenType qw(
11             TT_TAG_OPEN
12             TT_TAG_CLOSE
13             TT_TEXT
14             TT_COMMENT
15             TT_DOCTYPE
16 11     11   71 );
  11         27  
17 11         1010 use HTML::Blitz::ActionType qw(
18             AT_P_IMMEDIATE
19             AT_P_VARIABLE
20             AT_P_TRANSFORM
21             AT_P_FRAGMENT
22             AT_P_VARHTML
23              
24             AT_A_REMOVE_ATTR
25             AT_A_SET_ATTR
26             AT_A_MODIFY_ATTR
27              
28             AT_AS_REPLACE_ATTRS
29             AT_AS_MODIFY_ATTRS
30              
31             AT_REMOVE_IF
32             AT_REPLACE_OUTER
33             AT_REPEAT_OUTER
34             AT_REPLACE_INNER
35 11     11   5761 );
  11         31  
36 11     11   86 use List::Util qw(all reduce);
  11         25  
  11         2149  
37              
38             our $VERSION = '0.09';
39              
40 251 50   251 0 558 method new($class:) {
  251 50       525  
  251         385  
  251         317  
41 251         2094 bless {
42             rules => [],
43             keep_doctype => 1,
44             keep_comments_re => qr/\A/,
45             dummy_marker_re => qr/\A(?!)/,
46             }, $class
47             }
48              
49 1 50   1 0 3 method set_keep_doctype($val) {
  1 50       4  
  1         2  
  1         2  
  1         2  
50 1         4 $self->{keep_doctype} = !!$val;
51             }
52              
53 4 50   4 0 11 method set_keep_comments_re($keep_comments_re) {
  4 50       11  
  4         6  
  4         7  
  4         6  
54 4         82 $self->{keep_comments_re} = qr/(?#)$keep_comments_re/;
55             }
56              
57 5 50   5 0 12 method set_dummy_marker_re($dummy_marker_re) {
  5 50       12  
  5         8  
  5         9  
  5         8  
58 5         47 $self->{dummy_marker_re} = qr/(?#)$dummy_marker_re/;
59             }
60              
61 13 50   13   25 fun _combine_params_plain($p1, $p2) {
  13 50       24  
  13         20  
  13         18  
62 13 100       26 if ($p1->{type} eq AT_P_IMMEDIATE) {
63 11 100       23 if ($p2->{type} eq AT_P_IMMEDIATE) {
64 2 50       16 return length($p1->{value}) <= length($p2->{value}) ? $p1 : $p2;
65             }
66 9         60 return $p1;
67             }
68 2 100       10 if ($p2->{type} eq AT_P_IMMEDIATE) {
69 1         6 return $p2;
70             }
71 1 50 33     17 $p1->{type} eq AT_P_VARIABLE && $p2->{type} eq AT_P_VARIABLE
72             or die "Internal error: unexpected parameter types '$p1->{type}', '$p2->{type}'";
73 1         12 return $p1;
74             }
75              
76 4 50   4   10 fun _combine_transforms($t1, $t2) {
  4 50       9  
  4         7  
  4         6  
77 4 50 33     18 $t1->{type} eq AT_P_TRANSFORM && $t2->{type} eq AT_P_TRANSFORM
78             or die "Internal error: unexpected transform types '$t1->{type}', '$t2->{type}'";
79             return {
80             type => AT_P_TRANSFORM,
81             static => do {
82 4         9 my ($f1, $f2) = ($t1->{static}, $t2->{static});
83 4 50   8   18 fun ($x) { $f1->($f2->($x)) }
  8 50       24  
  8         17  
  8         14  
  8         10  
  8         20  
84             },
85 4         7 dynamic => [@{$t1->{dynamic}}, @{$t2->{dynamic}}],
  4         7  
  4         40  
86             };
87             }
88              
89 18 50   18   36 fun _combine_params($p1, $p2) {
  18 50       29  
  18         34  
  18         23  
90 18 100       45 if ($p1->{type} eq AT_P_FRAGMENT) {
91 2 50 33     14 if ($p2->{type} eq AT_P_FRAGMENT || $p2->{type} eq AT_P_TRANSFORM) {
92 0         0 return $p1;
93             }
94 2 0 33     7 $p2->{type} eq AT_P_IMMEDIATE || $p2->{type} eq AT_P_VARIABLE || $p2->{type} eq AT_P_VARHTML
      33        
95             or die "Internal error: unexpected parameter type '$p2->{type}'";
96 2         11 return $p2;
97             }
98              
99 16 100       33 if ($p2->{type} eq AT_P_FRAGMENT) {
100 3 50       8 if ($p1->{type} eq AT_P_TRANSFORM) {
101 3         15 return $p2;
102             }
103 0 0 0     0 $p1->{type} eq AT_P_IMMEDIATE || $p1->{type} eq AT_P_VARIABLE || $p1->{type} eq AT_P_VARHTML
      0        
104             or die "Internal error: unexpected parameter type '$p1->{type}'";
105 0         0 return $p1;
106             }
107              
108 13 50       29 if ($p1->{type} eq AT_P_VARHTML) {
109 0 0 0     0 if ($p2->{type} eq AT_P_VARHTML || $p2->{type} eq AT_P_TRANSFORM) {
110 0         0 return $p1;
111             }
112 0 0 0     0 $p2->{type} eq AT_P_IMMEDIATE || $p2->{type} eq AT_P_VARIABLE
113             or die "Internal error: unexpected parameter type '$p2->{type}'";
114             }
115              
116 13 50       25 if ($p2->{type} eq AT_P_VARHTML) {
117 0 0       0 if ($p1->{type} eq AT_P_TRANSFORM) {
118 0         0 return $p2;
119             }
120 0 0 0     0 $p1->{type} eq AT_P_IMMEDIATE || $p1->{type} eq AT_P_VARIABLE
121             or die "Internal error: unexpected parameter type '$p1->{type}'";
122 0         0 return $p1;
123             }
124              
125 13 100       28 if ($p1->{type} eq AT_P_TRANSFORM) {
126 5 100       14 if ($p2->{type} eq AT_P_TRANSFORM) {
127 4         12 return _combine_transforms($p1, $p2);
128             }
129 1 50 33     7 $p2->{type} eq AT_P_IMMEDIATE || $p2->{type} eq AT_P_VARIABLE
130             or die "Internal error: unexpected parameter type '$p2->{type}'";
131 1         6 return $p2;
132             }
133              
134 8 50       16 if ($p2->{type} eq AT_P_TRANSFORM) {
135 0 0 0     0 $p1->{type} eq AT_P_IMMEDIATE || $p1->{type} eq AT_P_VARIABLE
136             or die "Internal error: unexpected parameter type '$p1->{type}'";
137 0         0 return $p1;
138             }
139              
140 8         19 _combine_params_plain $p1, $p2
141             }
142              
143 79 50   79   170 fun _combine_contents($p1, $p2) {
  79 50       138  
  79         133  
  79         107  
144 79 100       322 return $p1 if !defined $p2;
145 21 100       49 return $p2 if !defined $p1;
146 18         52 _combine_params $p1, $p2
147             }
148              
149 35 50   35   61 fun _combine_attr_actions($aa1, $aa2) {
  35 50       60  
  35         50  
  35         46  
150 35 100       95 return $aa1 if $aa1->{type} eq AT_A_REMOVE_ATTR;
151 15 50       30 return $aa2 if $aa2->{type} eq AT_A_REMOVE_ATTR;
152 15 50       31 if ($aa1->{type} eq AT_A_SET_ATTR) {
153 15 100       29 if ($aa2->{type} eq AT_A_SET_ATTR) {
154 5         14 return { type => AT_A_SET_ATTR, param => _combine_params_plain($aa1->{param}, $aa2->{param}) };
155             }
156 10 50       23 $aa2->{type} eq AT_A_MODIFY_ATTR
157             or die "Internal error: unexpected attr action type '$aa2->{type}'";
158 10         25 return $aa1;
159             }
160 0 0       0 if ($aa2->{type} eq AT_A_SET_ATTR) {
161 0 0       0 $aa1->{type} eq AT_A_MODIFY_ATTR
162             or die "Internal error: unexpected attr action type '$aa1->{type}'";
163 0         0 return $aa2;
164             }
165 0 0 0     0 $aa1->{type} eq AT_A_MODIFY_ATTR && $aa2->{type} eq AT_A_MODIFY_ATTR
166             or die "Internal error: unexpected attr action types '$aa1->{type}', '$aa2->{type}'";
167 0         0 return { type => AT_A_MODIFY_ATTR, param => _combine_transforms($aa1->{param}, $aa2->{param}) };
168             }
169              
170 79 50   79   149 fun _combine_attrset_actions($asa1, $asa2) {
  79 50       139  
  79         129  
  79         99  
171 79 100       156 if ($asa1->{type} eq AT_AS_REPLACE_ATTRS) {
172 15 50       27 if ($asa2->{type} eq AT_AS_REPLACE_ATTRS) {
173             return
174 25     25   79 (all { $_->{type} eq AT_P_IMMEDIATE } values %{$asa1->{content}}) ? $asa1 :
  15         50  
175 14     14   55 (all { $_->{type} eq AT_P_IMMEDIATE } values %{$asa2->{content}}) ? $asa2 :
  5         15  
176 15 50       40 keys(%{$asa1->{content}}) <= keys(%{$asa2->{content}}) ? $asa1 :
  2 100       8  
  2 100       16  
177             $asa2;
178             }
179 0 0       0 $asa2->{type} eq AT_AS_MODIFY_ATTRS
180             or die "Internal error: unexpected attrset replacement type '$asa2->{type}'";
181 0         0 return $asa1;
182             }
183 64 50       131 if ($asa2->{type} eq AT_AS_REPLACE_ATTRS) {
184 0 0       0 $asa1->{type} eq AT_AS_MODIFY_ATTRS
185             or die "Internal error: unexpected attrset replacement type '$asa1->{type}'";
186 0         0 return $asa2;
187             }
188 64 50 33     202 $asa1->{type} eq AT_AS_MODIFY_ATTRS && $asa2->{type} eq AT_AS_MODIFY_ATTRS
189             or die "Internal error: unexpected attrset replacement types '$asa1->{type}', '$asa2->{type}'";
190 64         90 my %content = %{$asa1->{content}};
  64         183  
191 64         104 for my $k (keys %{$asa2->{content}}) {
  64         149  
192 43         73 my $v = $asa2->{content}{$k};
193 43 100       141 $content{$k} = exists $content{$k} ? _combine_attr_actions($content{$k}, $v) : $v;
194             }
195 64         271 return { type => AT_AS_MODIFY_ATTRS, content => \%content };
196             }
197              
198 0 0   0   0 fun _combine_actions_maybe($act1, $act2) {
  0 0       0  
  0         0  
  0         0  
199 0 0 0     0 defined($act1) && defined($act2)
      0        
200             ? _combine_actions($act1, $act2)
201             : $act1 // $act2
202             }
203              
204 79 50   79   166 fun _combine_actions($act1, $act2) {
  79 50       139  
  79         167  
  79         107  
205 79 50       175 if ($act1->{type} eq AT_REMOVE_IF) {
206 0 0       0 if ($act2->{type} eq AT_REMOVE_IF) {
207 0         0 return { type => AT_REMOVE_IF, cond => [@{$act1->{cond}}, @{$act2->{cond}}], else => _combine_actions_maybe($act1->{else}, $act2->{else}) };
  0         0  
  0         0  
208             }
209 0         0 return { type => AT_REMOVE_IF, cond => $act1->{cond}, else => _combine_actions_maybe($act1->{else}, $act2) };
210             }
211 79 50       152 if ($act2->{type} eq AT_REMOVE_IF) {
212 0         0 return { type => AT_REMOVE_IF, cond => $act2->{cond}, else => _combine_actions_maybe($act1, $act2->{else}) };
213             }
214 79 50       149 if ($act1->{type} eq AT_REPLACE_OUTER) {
215 0 0       0 if ($act2->{type} eq AT_REPLACE_OUTER) {
216 0         0 return { type => AT_REPLACE_OUTER, param => _combine_params($act1->{param}, $act2->{param}) };
217             }
218 0         0 return $act1;
219             }
220 79 50       143 if ($act2->{type} eq AT_REPLACE_OUTER) {
221 0         0 return $act2;
222             }
223 79 50       149 if ($act1->{type} eq AT_REPEAT_OUTER) {
224 0         0 return { %$act1, nested => _combine_actions($act1->{nested}, $act2) };
225             }
226 79 50       157 if ($act2->{type} eq AT_REPEAT_OUTER) {
227 0         0 return { %$act2, nested => _combine_actions($act1, $act2->{nested}) };
228             }
229 79 50 33     289 $act1->{type} eq AT_REPLACE_INNER && $act2->{type} eq AT_REPLACE_INNER
230             or die "Internal error: unexpected action types '$act1->{type}', '$act2->{type}'";
231             return {
232             type => AT_REPLACE_INNER,
233 79         130 repeat => [@{$act1->{repeat}}, @{$act2->{repeat}}],
  79         204  
234             attrset => _combine_attrset_actions($act1->{attrset}, $act2->{attrset}),
235 79         122 content => _combine_contents($act1->{content}, $act2->{content}),
236             };
237             }
238              
239 1803     1803   3279 fun _reduce_actions(@actions) {
  1803         2419  
240 78     78   178 reduce { _combine_actions $a, $b } @actions
241 1803         11539 }
242              
243 267 50   267   534 fun _bind_scope($scope, $action) {
  267 50       486  
  267         513  
  267         344  
244 267 100       1000 if ($action->{type} eq AT_REMOVE_IF) {
    100          
    100          
245             return {
246             type => AT_REMOVE_IF,
247 5         51 cond => [map [$_->[0] // $scope, $_->[1]], @{$action->{cond}}],
248 5   66     11 else => $action->{else} && _bind_scope($scope, $action->{else}),
      33        
249             };
250             } elsif ($action->{type} eq AT_REPLACE_OUTER) {
251 7         13 my $param = $action->{param};
252 7 100 100     43 if ($param->{type} eq AT_P_VARIABLE || $param->{type} eq AT_P_VARHTML) {
    100          
253 2         6 my $value = $param->{value};
254 2 50       6 if (!defined $value->[0]) {
255 2         17 return { %$action, param => { %$param, value => [$scope, $value->[1]] } };
256             }
257             } elsif ($param->{type} eq AT_P_TRANSFORM) {
258 2 100       3 if (@{$param->{dynamic}}) {
  2         7  
259 1   33     1634 return { %$action, param => { %$param, dynamic => [ map [$_->[0] // $scope, $_->[1]], @{$param->{dynamic}} ] } };
  1         19  
260             }
261             }
262             } elsif ($action->{type} eq AT_REPEAT_OUTER) {
263 1 50       5 if (!defined $action->{var}[0]) {
264 1         9 return { %$action, var => [$scope, $action->{var}[1]] };
265             }
266             } else {
267 254 50       588 $action->{type} eq AT_REPLACE_INNER
268             or die "Internal error: unexpected action type '$action->{type}'";
269              
270 254         368 my $did_replace = 0;
271 31 50   31   85 my $scope_var = fun ($var) {
  31 50       65  
  31         55  
  31         42  
272 31 50       72 defined $var->[0] ? $var : do {
273 31         41 $did_replace++;
274 31         171 [$scope, $var->[1]]
275             }
276 254         1005 };
277 254         954 my %replacement = (type => AT_REPLACE_INNER, attrset => {}, content => undef, repeat => []);
278              
279 254         429 my $asa = $action->{attrset};
280 254 100       541 if ($asa->{type} eq AT_AS_REPLACE_ATTRS) {
281 6         13 $replacement{attrset}{type} = AT_AS_REPLACE_ATTRS;
282 6         12 my $content = $asa->{content};
283 6         11 $replacement{attrset}{content} = \my %copy;
284 6         19 for my $key (keys %$content) {
285 12         19 my $value = $content->{$key};
286 12 100 66     38 if ($value->{type} eq AT_P_VARIABLE && !defined $value->{value}[0]) {
287 3         13 $copy{$key} = { type => AT_P_VARIABLE, value => [$scope, $value->{value}[1]] };
288 3         8 $did_replace++;
289             } else {
290 9         54 $copy{$key} = $value;
291             }
292             }
293             } else {
294 248 50       513 $asa->{type} eq AT_AS_MODIFY_ATTRS
295             or die "Internal error: unexpected attrset replacement type '$asa->{type}'";
296 248         496 $replacement{attrset}{type} = AT_AS_MODIFY_ATTRS;
297 248         384 my $content = $asa->{content};
298 248         468 $replacement{attrset}{content} = \my %copy;
299 248         736 for my $key (keys %$content) {
300 44         83 my $value = $content->{$key};
301 44 100 100     300 if ($value->{type} eq AT_A_SET_ATTR && $value->{param}{type} eq AT_P_VARIABLE && !defined $value->{param}{value}[0]) {
    100 66        
      100        
302 7         32 $copy{$key} = { type => AT_A_SET_ATTR, param => { type => AT_P_VARIABLE, value => [$scope, $value->{param}{value}[1]] } };
303 7         17 $did_replace++;
304             } elsif ($value->{type} eq AT_A_MODIFY_ATTR && (my $param = $value->{param})->{dynamic}->@*) {
305 5 50       17 $param->{type} eq AT_P_TRANSFORM
306             or die "Internal error: unexpected parameter type '$param->{type}'";
307             $copy{$key} = {
308             type => AT_A_MODIFY_ATTR,
309             param => {
310             type => AT_P_TRANSFORM,
311             static => $param->{static},
312             dynamic => [
313 5         15 map $scope_var->($_), @{$param->{dynamic}}
  5         15  
314             ],
315             },
316             };
317             } else {
318 32         96 $copy{$key} = $value;
319             }
320             }
321             }
322              
323 254 100       589 if (defined(my $param = $action->{content})) {
324 205 100 100     1136 if (($param->{type} eq AT_P_VARIABLE || $param->{type} eq AT_P_VARHTML) && !defined $param->{value}[0]) {
    100 66        
      100        
325 36         210 $replacement{content} = { %$param, value => [$scope, $param->{value}[1]] };
326 36         78 $did_replace++;
327 45         132 } elsif ($param->{type} eq AT_P_TRANSFORM && @{$param->{dynamic}}) {
328             $replacement{content} = {
329             type => AT_P_TRANSFORM,
330             static => $param->{static},
331             dynamic => [
332 23         52 map $scope_var->($_), @{$param->{dynamic}}
  23         69  
333             ],
334             };
335             } else {
336 146         257 $replacement{content} = $param;
337             }
338             }
339              
340 254 100       423 if (@{$replacement{repeat}} = @{$action->{repeat}}) {
  254         623  
  254         448  
341 3         9 my $rfirst = \$replacement{repeat}[0];
342 3 50       13 if (!defined $$rfirst->{var}[0]) {
343 3         11 $$rfirst = { var => $scope_var->($$rfirst->{var}), rules => $$rfirst->{rules} };
344 3         10 $did_replace++;
345             }
346             }
347              
348 254 100       1639 return \%replacement if $did_replace;
349             }
350 182         483 $action
351             }
352              
353 249 50   249 0 592 method add_rule($selector, $action, @actions) {
  249         406  
  249         483  
  249         329  
354 249         363 push @{$self->{rules}}, {
  249         747  
355             selector => $selector,
356             result => _reduce_actions($action, @actions),
357             };
358             }
359              
360 243 50 66 243   536 fun _skip_children($name, $parser, :$collect_content = undef) {
  243 50       621  
  243 50       524  
  243         425  
  243         539  
  243         297  
361 243         353 my $content = '';
362 243         321 my $depth = 0;
363 243         324 while () {
364 532   50     1306 my $token = $parser->parse // die "Internal error: missing '' in parser results";
365 532 100 66     1947 if ($token->{type} eq TT_TAG_CLOSE) {
    100          
    100          
366 259 100       710 last if $depth == 0;
367 16         36 $depth--;
368             } elsif ($token->{type} eq TT_TAG_OPEN) {
369 32 100       105 $depth++ if !$token->{is_self_closing};
370             } elsif ($collect_content && $token->{type} eq TT_TEXT) {
371 49         147 $content .= $token->{content};
372             }
373             }
374 243 100       577 $collect_content ? $content : undef
375             }
376              
377 266 50   266   569 fun _with_stopper($alternatives, $result) {
  266 50       499  
  266         510  
  266         365  
378 266         1526 map [ @$_, $result ], @$alternatives
379             }
380              
381 276 50   276 0 594 method compile($name, $html) {
  276 50       635  
  276         423  
  276         476  
  276         356  
382 276         950 my $codegen = HTML::Blitz::CodeGen->new(name => $name);
383             my $matcher = HTML::Blitz::Matcher->new([
384             map _with_stopper($_->{selector}, _bind_scope($codegen->scope, $_->{result})),
385 276         509 @{$self->{rules}}
  276         1054  
386             ]);
387 276         1145 my $parser = HTML::Blitz::Parser->new($name, $html);
388              
389 276         846 while (my $token = $parser->parse) {
390 4866 100       16726 if ($token->{type} eq TT_DOCTYPE) {
    100          
    100          
    100          
    50          
391 10 100       35 if ($self->{keep_doctype}) {
392 9         30 $codegen->emit_doctype;
393             }
394             } elsif ($token->{type} eq TT_COMMENT) {
395 42 100       253 if ($token->{content} =~ /$self->{keep_comments_re}/) {
396 34         121 $codegen->emit_comment($token->{content});
397             }
398             } elsif ($token->{type} eq TT_TEXT) {
399 2175 100       10260 if ($token->{content} =~ /$self->{dummy_marker_re}/) {
400 3         20 $parser->throw_for($token, "raw text contains forbidden dummy marker $self->{dummy_marker_re}");
401             }
402 2172         5636 my $cur_tag = $parser->current_tag;
403 2172 100       5115 if ($cur_tag eq 'script') {
    100          
404 28         95 $codegen->emit_script_text($token->{content});
405             } elsif ($cur_tag eq 'style') {
406 66         190 $codegen->emit_style_text($token->{content});
407             } else {
408 2078         5078 $codegen->emit_text($token->{content});
409             }
410             } elsif ($token->{type} eq TT_TAG_CLOSE) {
411 1093         3622 $matcher->leave(\my %ret);
412 1093   66     5014 ($ret{codegen} // $codegen)->emit_close_tag($token->{name});
413             } elsif ($token->{type} eq TT_TAG_OPEN) {
414 1546         4496 my $action = _reduce_actions $matcher->enter($token->{name}, $token->{attrs});
415              
416 1546 100 100     6612 if (defined($action) && $action->{type} eq AT_REMOVE_IF) {
417 5         48 my $cond_gen = $codegen->insert_cond($action->{cond});
418 5         10 $action = $action->{else};
419              
420 5         9 my $outer_gen = $codegen;
421 5         8 $codegen = $cond_gen;
422 5 50   5   16 $matcher->on_leave(fun ($ret = {}) {
  5 100       18  
  5         6  
423 5   33     38 $ret->{codegen} //= $cond_gen;
424 5         19 $codegen = $outer_gen;
425 5         30 });
426             }
427              
428 1546 100       3171 if (!defined $action) {
429 1188         1846 my $attrs = $token->{attrs};
430 1188         6683 my @bad_attrs = sort grep $attrs->{$_} =~ /$self->{dummy_marker_re}/, keys %$attrs;
431 1188 100       2774 if (@bad_attrs) {
432 1         11 $parser->throw_for($token, "'$token->{name}' tag contains forbidden dummy marker $self->{dummy_marker_re} in the following attribute(s): " . join(', ', @bad_attrs));
433             }
434 1187   100     5132 $codegen->emit_open_tag($token->{name}, $attrs, self_closing => $token->{is_self_closing} && !$token->{is_void});
435 1187 100       2763 $matcher->leave if $token->{is_self_closing};
436 1187         5614 next;
437             }
438              
439 358 100       790 if ($action->{type} eq AT_REPLACE_OUTER) {
440 7         13 my $param = $action->{param};
441 7         10 my $skipped = 0;
442 7 100       30 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
    100          
    100          
443 2         8 $codegen->emit_text($param->{value});
444             } elsif ($param->{type} eq AT_P_VARIABLE) {
445 1         5 $codegen->emit_variable($param->{value});
446             } elsif ($param->{type} eq AT_P_FRAGMENT) {
447 1         5 $codegen->incorporate($param->{value});
448             } elsif ($param->{type} eq AT_P_VARHTML) {
449 1         5 $codegen->emit_variable_html($param->{value});
450             } else {
451 2 50       9 $param->{type} eq AT_P_TRANSFORM
452             or die "Internal error: unexpected parameter type '$param->{type}'";
453 2 50       7 if (!$token->{is_void}) {
454 2 50       10 my $text_content = $token->{is_self_closing} ? '' : _skip_children $token->{name}, $parser, collect_content => 1;
455 2         5 $skipped = 1;
456 2         8 $text_content = '' . $param->{static}->($text_content);
457 2 100       7 if (@{$param->{dynamic}}) {
  2         8  
458 1         4 $codegen->emit_call($param->{dynamic}, $text_content);
459             } else {
460 1         5 $codegen->emit_text($text_content);
461             }
462             }
463             }
464              
465 7 50 66     37 _skip_children $token->{name}, $parser if !$skipped && !$token->{is_self_closing};
466 7         22 $matcher->leave;
467 7         28 next;
468             }
469              
470 351         743 while ($action->{type} eq AT_REPEAT_OUTER) {
471 1         7 my $loop_gen = $codegen->insert_loop($action->{var});
472              
473 1         2 for my $proto_rule (@{$action->{rules}}) {
  1         4  
474 1         3 my ($selector, @actions) = @$proto_rule;
475 1         4 my $action = _bind_scope $loop_gen->scope, _reduce_actions @actions;
476 1         5 $matcher->add_temp_rule(_with_stopper($selector, $action));
477             }
478              
479 1         3 my $inplace = _reduce_actions @{$action->{inplace}};
  1         4  
480             $action = defined($inplace)
481             ? _combine_actions $action->{nested}, _bind_scope $loop_gen->scope, $inplace
482 1 50       11 : $action->{nested};
483              
484 1         4 my $outer_gen = $codegen;
485 1         2 $codegen = $loop_gen;
486 1 50   1   4 $matcher->on_leave(fun ($ret = {}) {
  1 50       5  
  1         2  
487 1   33     6 $ret->{codegen} //= $loop_gen;
488 1         4 $codegen = $outer_gen;
489 1         8 });
490             }
491              
492 351 50       693 $action->{type} eq AT_REPLACE_INNER
493             or die "Internal error: unexpected action type '$action->{type}'";
494              
495 351         1237 $codegen->emit_open_tag_name_fragment($token->{name});
496              
497 351         665 my $attrset = $action->{attrset};
498 351 100       760 if ($attrset->{type} eq AT_AS_REPLACE_ATTRS) {
499 16         43 my $attrs = $attrset->{content};
500 16         66 for my $attr (sort keys %$attrs) {
501 37         65 my $param = $attrs->{$attr};
502 37 100       62 if ($param->{type} eq AT_P_IMMEDIATE) {
503 33         82 $codegen->emit_open_tag_attr_fragment($attr, $param->{value});
504             } else {
505 4 50       13 $param->{type} eq AT_P_VARIABLE
506             or die "Internal error: unexpected parameter type '$param->{type}'";
507 4         15 $codegen->emit_open_tag_attr_var_fragment($attr, $param->{value});
508             }
509             }
510             } else {
511 335 50       734 $attrset->{type} eq AT_AS_MODIFY_ATTRS
512             or die "Internal error: unexpected attrset replacement type '$attrset->{type}'";
513              
514 335         539 my $token_attrs = $token->{attrs};
515             my %attrs = map +(
516             $_ => {
517             type => AT_P_IMMEDIATE,
518 335         1984 value => $token_attrs->{$_},
519             pristine => 1,
520             }
521             ), keys %$token_attrs;
522              
523 335         702 my $attr_actions = $attrset->{content};
524 335         814 for my $attr (keys %$attr_actions) {
525 99         155 my $attr_action = $attr_actions->{$attr};
526 99 100       234 if ($attr_action->{type} eq AT_A_REMOVE_ATTR) {
    100          
527 14         44 delete $attrs{$attr};
528             } elsif ($attr_action->{type} eq AT_A_SET_ATTR) {
529 39         99 $attrs{$attr} = $attr_action->{param};
530             } else {
531 46 50       100 $attr_action->{type} eq AT_A_MODIFY_ATTR
532             or die "Internal error: unexpected attr action type '$attr_action->{type}'";
533 46         62 my $param = $attr_action->{param};
534 46 50       89 $param->{type} eq AT_P_TRANSFORM
535             or die "Internal error: unexpected parameter type '$param->{type}'";
536 46         174 my $value = $param->{static}->($attrs{$attr}{value});
537 46 100       104 if (@{$param->{dynamic}}) {
  46 100       143  
538 5         25 $attrs{$attr} = { type => AT_P_TRANSFORM, dynamic => $param->{dynamic}, value => $value };
539             } elsif (!defined $value) {
540 6         22 delete $attrs{$attr};
541             } else {
542 35         148 $attrs{$attr} = { type => AT_P_IMMEDIATE, value => '' . $value };
543             }
544             }
545             }
546              
547 335         527 my @bad_attrs;
548 335         875 for my $attr (sort keys %attrs) {
549 348         592 my $param = $attrs{$attr};
550 348 100       681 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
551 333 100 100     2002 if ($param->{pristine} && $param->{value} =~ /$self->{dummy_marker_re}/) {
552 1         3 push @bad_attrs, $attr;
553             }
554 333         997 $codegen->emit_open_tag_attr_fragment($attr, $param->{value});
555             } elsif ($param->{type} eq AT_P_VARIABLE) {
556 10         48 $codegen->emit_open_tag_attr_var_fragment($attr, $param->{value});
557             } else {
558 5 50       15 $param->{type} eq AT_P_TRANSFORM
559             or die "Internal error: unexpected parameter type '$param->{type}'";
560 5         30 $codegen->emit_open_tag_attr_transform_fragment($attr, $param->{dynamic}, $param->{value});
561             }
562             }
563             @bad_attrs
564 335 100       1176 and $parser->throw_for($token, "<$token->{name}> tag contains forbidden dummy marker $self->{dummy_marker_re} in the following attribute(s): " . join(', ', @bad_attrs));
565             }
566              
567 350         1089 $codegen->emit_open_tag_close_fragment;
568              
569 350         531 for my $repeat (@{$action->{repeat}}) {
  350         827  
570 3         14 my $loop_gen = $codegen->insert_loop($repeat->{var});
571 3         7 for my $proto_rule (@{$repeat->{rules}}) {
  3         8  
572 6         15 my ($selector, @actions) = @$proto_rule;
573 6         18 my $action = _bind_scope $loop_gen->scope, _reduce_actions @actions;
574 6         23 $matcher->add_temp_rule(_with_stopper($selector, $action));
575             }
576              
577 3         8 my $outer_gen = $codegen;
578 3         5 $codegen = $loop_gen;
579 3     3   15 $matcher->on_leave(fun (@) { $codegen = $outer_gen; });
  3         14  
580             }
581              
582 350 100       1041 if (defined(my $param = $action->{content})) {
    100          
583             $token->{is_void}
584 247 50       539 and $parser->throw_for($token, "<$token->{name}> tag cannot have content");
585              
586 247         356 my $skipped = 0;
587 247 100       665 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
    100          
    100          
588 157 100       379 if ($token->{name} eq 'script') {
    100          
589 21         62 $codegen->emit_script_text($param->{value});
590             } elsif ($token->{name} eq 'style') {
591 1         8 $codegen->emit_style_text($param->{value});
592             } else {
593 135         360 $codegen->emit_text($param->{value});
594             }
595             } elsif ($param->{type} eq AT_P_VARIABLE) {
596 31 100       115 if ($token->{name} eq 'script') {
    100          
597 18         58 $codegen->emit_variable_script($param->{value});
598             } elsif ($token->{name} eq 'style') {
599 1         5 $codegen->emit_variable_style($param->{value});
600             } else {
601 12         47 $codegen->emit_variable($param->{value});
602             }
603             } elsif ($param->{type} eq AT_P_FRAGMENT) {
604 8 100 100     81 $token->{name} eq 'script' || $token->{name} eq 'style' || $token->{name} eq 'title'
      100        
605             and $parser->throw_for($token, "<$token->{name}> tag cannot have descendant elements");
606 5         24 $codegen->incorporate($param->{value});
607             } elsif ($param->{type} eq AT_P_VARHTML) {
608 4 100 100     42 $token->{name} eq 'script' || $token->{name} eq 'style' || $token->{name} eq 'title'
      100        
609             and $parser->throw_for($token, "<$token->{name}> tag cannot have descendant elements");
610 1         5 $codegen->emit_variable_html($param->{value});
611             } else {
612 47 50       99 $param->{type} eq AT_P_TRANSFORM
613             or die "Internal error: unexpected parameter type '$param->{type}'";
614 47 50       148 my $text_content = $token->{is_self_closing} ? '' : _skip_children $token->{name}, $parser, collect_content => 1;
615 47         86 $skipped = 1;
616 47         162 $text_content = '' . $param->{static}->($text_content);
617 47 100       150 if (@{$param->{dynamic}}) {
  47         120  
618 25 100       68 if ($token->{name} eq 'script') {
    100          
619 18         60 $codegen->emit_call_script($param->{dynamic}, $text_content);
620             } elsif ($token->{name} eq 'style') {
621 1         6 $codegen->emit_call_style($param->{dynamic}, $text_content);
622             } else {
623 6         25 $codegen->emit_call($param->{dynamic}, $text_content);
624             }
625             } else {
626 22 100       59 if ($token->{name} eq 'script') {
    100          
627 18         68 $codegen->emit_script_text($text_content);
628             } elsif ($token->{name} eq 'style') {
629 1         8 $codegen->emit_style_text($text_content);
630             } else {
631 3         14 $codegen->emit_text($text_content);
632             }
633             }
634             }
635              
636 231 50 66     1114 _skip_children $token->{name}, $parser if !$skipped && !$token->{is_self_closing};
637 231         824 $matcher->leave(\my %ret);
638 231   33     1157 ($ret{codegen} // $codegen)->emit_close_tag($token->{name});
639             } elsif ($token->{is_self_closing}) {
640 42         170 $matcher->leave;
641 42 50       285 $codegen->emit_close_tag($token->{name}) if !$token->{is_void};
642             }
643              
644             } else {
645             # uncoverable statement
646 0         0 die "Internal error: unhandled token type '$token->{type}'";
647             }
648             }
649              
650             $codegen
651 238         2348 }
652              
653             1