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   82 use HTML::Blitz::pragma;
  11         25  
  11         89  
7 11     11   9482 use HTML::Blitz::Matcher ();
  11         27  
  11         273  
8 11     11   6013 use HTML::Blitz::Parser ();
  11         36  
  11         448  
9 11     11   109 use HTML::Blitz::CodeGen ();
  11         25  
  11         292  
10 11         857 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   58 );
  11         33  
17 11         1011 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   5461 );
  11         32  
36 11     11   90 use List::Util qw(all reduce);
  11         25  
  11         2126  
37              
38             our $VERSION = '0.08';
39              
40 249 50   249 0 621 method new($class:) {
  249 50       544  
  249         422  
  249         332  
41 249         2061 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 4 method set_keep_doctype($val) {
  1 50       5  
  1         2  
  1         2  
  1         2  
50 1         4 $self->{keep_doctype} = !!$val;
51             }
52              
53 4 50   4 0 14 method set_keep_comments_re($keep_comments_re) {
  4 50       10  
  4         9  
  4         8  
  4         5  
54 4         85 $self->{keep_comments_re} = qr/(?#)$keep_comments_re/;
55             }
56              
57 5 50   5 0 14 method set_dummy_marker_re($dummy_marker_re) {
  5 50       9  
  5         8  
  5         8  
  5         6  
58 5         51 $self->{dummy_marker_re} = qr/(?#)$dummy_marker_re/;
59             }
60              
61 13 50   13   25 fun _combine_params_plain($p1, $p2) {
  13 50       31  
  13         21  
  13         18  
62 13 100       27 if ($p1->{type} eq AT_P_IMMEDIATE) {
63 11 100       22 if ($p2->{type} eq AT_P_IMMEDIATE) {
64 2 50       15 return length($p1->{value}) <= length($p2->{value}) ? $p1 : $p2;
65             }
66 9         41 return $p1;
67             }
68 2 100       7 if ($p2->{type} eq AT_P_IMMEDIATE) {
69 1         6 return $p2;
70             }
71 1 50 33     8 $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         6 return $p1;
74             }
75              
76 4 50   4   9 fun _combine_transforms($t1, $t2) {
  4 50       9  
  4         7  
  4         5  
77 4 50 33     17 $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   33 fun ($x) { $f1->($f2->($x)) }
  8 50       22  
  8         18  
  8         13  
  8         10  
  8         32  
84             },
85 4         7 dynamic => [@{$t1->{dynamic}}, @{$t2->{dynamic}}],
  4         9  
  4         41  
86             };
87             }
88              
89 18 50   18   38 fun _combine_params($p1, $p2) {
  18 50       34  
  18         29  
  18         27  
90 18 100       33 if ($p1->{type} eq AT_P_FRAGMENT) {
91 2 50 33     12 if ($p2->{type} eq AT_P_FRAGMENT || $p2->{type} eq AT_P_TRANSFORM) {
92 0         0 return $p1;
93             }
94 2 0 33     6 $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         12 return $p2;
97             }
98              
99 16 100       38 if ($p2->{type} eq AT_P_FRAGMENT) {
100 3 50       10 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       28 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       30 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       24 if ($p1->{type} eq AT_P_TRANSFORM) {
126 5 100       13 if ($p2->{type} eq AT_P_TRANSFORM) {
127 4         10 return _combine_transforms($p1, $p2);
128             }
129 1 50 33     8 $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         17 _combine_params_plain $p1, $p2
141             }
142              
143 79 50   79   162 fun _combine_contents($p1, $p2) {
  79 50       134  
  79         159  
  79         96  
144 79 100       353 return $p1 if !defined $p2;
145 21 100       46 return $p2 if !defined $p1;
146 18         47 _combine_params $p1, $p2
147             }
148              
149 35 50   35   79 fun _combine_attr_actions($aa1, $aa2) {
  35 50       63  
  35         55  
  35         41  
150 35 100       85 return $aa1 if $aa1->{type} eq AT_A_REMOVE_ATTR;
151 15 50       28 return $aa2 if $aa2->{type} eq AT_A_REMOVE_ATTR;
152 15 50       30 if ($aa1->{type} eq AT_A_SET_ATTR) {
153 15 100       28 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         23 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   162 fun _combine_attrset_actions($asa1, $asa2) {
  79 50       198  
  79         125  
  79         110  
171 79 100       155 if ($asa1->{type} eq AT_AS_REPLACE_ATTRS) {
172 15 50       27 if ($asa2->{type} eq AT_AS_REPLACE_ATTRS) {
173             return
174 27     27   80 (all { $_->{type} eq AT_P_IMMEDIATE } values %{$asa1->{content}}) ? $asa1 :
  15         45  
175 18     18   37 (all { $_->{type} eq AT_P_IMMEDIATE } values %{$asa2->{content}}) ? $asa2 :
  5         15  
176 15 50       39 keys(%{$asa1->{content}}) <= keys(%{$asa2->{content}}) ? $asa1 :
  2 100       5  
  2 100       13  
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       122 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     205 $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         89 my %content = %{$asa1->{content}};
  64         169  
191 64         108 for my $k (keys %{$asa2->{content}}) {
  64         140  
192 43         69 my $v = $asa2->{content}{$k};
193 43 100       126 $content{$k} = exists $content{$k} ? _combine_attr_actions($content{$k}, $v) : $v;
194             }
195 64         251 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   164 fun _combine_actions($act1, $act2) {
  79 50       144  
  79         134  
  79         102  
205 79 50       178 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       148 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       163 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       157 if ($act2->{type} eq AT_REPLACE_OUTER) {
221 0         0 return $act2;
222             }
223 79 50       171 if ($act1->{type} eq AT_REPEAT_OUTER) {
224 0         0 return { %$act1, nested => _combine_actions($act1->{nested}, $act2) };
225             }
226 79 50       151 if ($act2->{type} eq AT_REPEAT_OUTER) {
227 0         0 return { %$act2, nested => _combine_actions($act1, $act2->{nested}) };
228             }
229 79 50 33     279 $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         134 repeat => [@{$act1->{repeat}}, @{$act2->{repeat}}],
  79         196  
234             attrset => _combine_attrset_actions($act1->{attrset}, $act2->{attrset}),
235 79         129 content => _combine_contents($act1->{content}, $act2->{content}),
236             };
237             }
238              
239 1793     1793   3243 fun _reduce_actions(@actions) {
  1793         2436  
240 78     78   176 reduce { _combine_actions $a, $b } @actions
241 1793         11503 }
242              
243 264 50   264   588 fun _bind_scope($scope, $action) {
  264 50       486  
  264         521  
  264         376  
244 264 100       918 if ($action->{type} eq AT_REMOVE_IF) {
    100          
    100          
245             return {
246             type => AT_REMOVE_IF,
247 5         54 cond => [map [$_->[0] // $scope, $_->[1]], @{$action->{cond}}],
248 5   66     10 else => $action->{else} && _bind_scope($scope, $action->{else}),
      33        
249             };
250             } elsif ($action->{type} eq AT_REPLACE_OUTER) {
251 7         14 my $param = $action->{param};
252 7 100 100     37 if ($param->{type} eq AT_P_VARIABLE || $param->{type} eq AT_P_VARHTML) {
    100          
253 2         4 my $value = $param->{value};
254 2 50       7 if (!defined $value->[0]) {
255 2         18 return { %$action, param => { %$param, value => [$scope, $value->[1]] } };
256             }
257             } elsif ($param->{type} eq AT_P_TRANSFORM) {
258 2 100       4 if (@{$param->{dynamic}}) {
  2         7  
259 1   33     5 return { %$action, param => { %$param, dynamic => [ map [$_->[0] // $scope, $_->[1]], @{$param->{dynamic}} ] } };
  1         13  
260             }
261             }
262             } elsif ($action->{type} eq AT_REPEAT_OUTER) {
263 1 50       4 if (!defined $action->{var}[0]) {
264 1         8 return { %$action, var => [$scope, $action->{var}[1]] };
265             }
266             } else {
267 251 50       555 $action->{type} eq AT_REPLACE_INNER
268             or die "Internal error: unexpected action type '$action->{type}'";
269              
270 251         351 my $did_replace = 0;
271 30 50   30   78 my $scope_var = fun ($var) {
  30 50       65  
  30         53  
  30         43  
272 30 50       70 defined $var->[0] ? $var : do {
273 30         44 $did_replace++;
274 30         150 [$scope, $var->[1]]
275             }
276 251         967 };
277 251         934 my %replacement = (type => AT_REPLACE_INNER, attrset => {}, content => undef, repeat => []);
278              
279 251         463 my $asa = $action->{attrset};
280 251 100       553 if ($asa->{type} eq AT_AS_REPLACE_ATTRS) {
281 6         15 $replacement{attrset}{type} = AT_AS_REPLACE_ATTRS;
282 6         8 my $content = $asa->{content};
283 6         13 $replacement{attrset}{content} = \my %copy;
284 6         18 for my $key (keys %$content) {
285 12         22 my $value = $content->{$key};
286 12 100 66     36 if ($value->{type} eq AT_P_VARIABLE && !defined $value->{value}[0]) {
287 3         12 $copy{$key} = { type => AT_P_VARIABLE, value => [$scope, $value->{value}[1]] };
288 3         6 $did_replace++;
289             } else {
290 9         17 $copy{$key} = $value;
291             }
292             }
293             } else {
294 245 50       509 $asa->{type} eq AT_AS_MODIFY_ATTRS
295             or die "Internal error: unexpected attrset replacement type '$asa->{type}'";
296 245         550 $replacement{attrset}{type} = AT_AS_MODIFY_ATTRS;
297 245         377 my $content = $asa->{content};
298 245         454 $replacement{attrset}{content} = \my %copy;
299 245         698 for my $key (keys %$content) {
300 44         77 my $value = $content->{$key};
301 44 100 100     283 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         30 $copy{$key} = { type => AT_A_SET_ATTR, param => { type => AT_P_VARIABLE, value => [$scope, $value->{param}{value}[1]] } };
303 7         18 $did_replace++;
304             } elsif ($value->{type} eq AT_A_MODIFY_ATTR && (my $param = $value->{param})->{dynamic}->@*) {
305 5 50       25 $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         21 map $scope_var->($_), @{$param->{dynamic}}
  5         22  
314             ],
315             },
316             };
317             } else {
318 32         83 $copy{$key} = $value;
319             }
320             }
321             }
322              
323 251 100       579 if (defined(my $param = $action->{content})) {
324 203 100 100     1171 if (($param->{type} eq AT_P_VARIABLE || $param->{type} eq AT_P_VARHTML) && !defined $param->{value}[0]) {
    100 66        
      100        
325 35         195 $replacement{content} = { %$param, value => [$scope, $param->{value}[1]] };
326 35         74 $did_replace++;
327 45         140 } 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         51 map $scope_var->($_), @{$param->{dynamic}}
  23         72  
333             ],
334             };
335             } else {
336 145         246 $replacement{content} = $param;
337             }
338             }
339              
340 251 100       381 if (@{$replacement{repeat}} = @{$action->{repeat}}) {
  251         614  
  251         499  
341 2         8 my $rfirst = \$replacement{repeat}[0];
342 2 50       8 if (!defined $$rfirst->{var}[0]) {
343 2         8 $$rfirst = { var => $scope_var->($$rfirst->{var}), rules => $$rfirst->{rules} };
344 2         5 $did_replace++;
345             }
346             }
347              
348 251 100       1493 return \%replacement if $did_replace;
349             }
350 181         450 $action
351             }
352              
353 247 50   247 0 622 method add_rule($selector, $action, @actions) {
  247         402  
  247         502  
  247         337  
354 247         369 push @{$self->{rules}}, {
  247         766  
355             selector => $selector,
356             result => _reduce_actions($action, @actions),
357             };
358             }
359              
360 241 50 66 241   498 fun _skip_children($name, $parser, :$collect_content = undef) {
  241 50       650  
  241 50       564  
  241         466  
  241         518  
  241         284  
361 241         380 my $content = '';
362 241         316 my $depth = 0;
363 241         336 while () {
364 525   50     1310 my $token = $parser->parse // die "Internal error: missing '' in parser results";
365 525 100 66     1888 if ($token->{type} eq TT_TAG_CLOSE) {
    100          
    100          
366 256 100       699 last if $depth == 0;
367 15         33 $depth--;
368             } elsif ($token->{type} eq TT_TAG_OPEN) {
369 30 100       99 $depth++ if !$token->{is_self_closing};
370             } elsif ($collect_content && $token->{type} eq TT_TEXT) {
371 49         142 $content .= $token->{content};
372             }
373             }
374 241 100       614 $collect_content ? $content : undef
375             }
376              
377 263 50   263   621 fun _with_stopper($alternatives, $result) {
  263 50       502  
  263         465  
  263         337  
378 263         1533 map [ @$_, $result ], @$alternatives
379             }
380              
381 274 50   274 0 609 method compile($name, $html) {
  274 50       542  
  274         418  
  274         472  
  274         350  
382 274         1026 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 274         515 @{$self->{rules}}
  274         1070  
386             ]);
387 274         1143 my $parser = HTML::Blitz::Parser->new($name, $html);
388              
389 274         835 while (my $token = $parser->parse) {
390 4850 100       17011 if ($token->{type} eq TT_DOCTYPE) {
    100          
    100          
    100          
    50          
391 10 100       28 if ($self->{keep_doctype}) {
392 9         26 $codegen->emit_doctype;
393             }
394             } elsif ($token->{type} eq TT_COMMENT) {
395 42 100       226 if ($token->{content} =~ /$self->{keep_comments_re}/) {
396 34         110 $codegen->emit_comment($token->{content});
397             }
398             } elsif ($token->{type} eq TT_TEXT) {
399 2170 100       9689 if ($token->{content} =~ /$self->{dummy_marker_re}/) {
400 3         15 $parser->throw_for($token, "raw text contains forbidden dummy marker $self->{dummy_marker_re}");
401             }
402 2167         5707 my $cur_tag = $parser->current_tag;
403 2167 100       4884 if ($cur_tag eq 'script') {
    100          
404 28         105 $codegen->emit_script_text($token->{content});
405             } elsif ($cur_tag eq 'style') {
406 66         192 $codegen->emit_style_text($token->{content});
407             } else {
408 2073         5075 $codegen->emit_text($token->{content});
409             }
410             } elsif ($token->{type} eq TT_TAG_CLOSE) {
411 1089         3691 $matcher->leave(\my %ret);
412 1089   66     6473 ($ret{codegen} // $codegen)->emit_close_tag($token->{name});
413             } elsif ($token->{type} eq TT_TAG_OPEN) {
414 1539         4604 my $action = _reduce_actions $matcher->enter($token->{name}, $token->{attrs});
415              
416 1539 100 100     6394 if (defined($action) && $action->{type} eq AT_REMOVE_IF) {
417 5         55 my $cond_gen = $codegen->insert_cond($action->{cond});
418 5         14 $action = $action->{else};
419              
420 5         8 my $outer_gen = $codegen;
421 5         9 $codegen = $cond_gen;
422 5 50   5   18 $matcher->on_leave(fun ($ret = {}) {
  5 100       17  
  5         8  
423 5   33     35 $ret->{codegen} //= $cond_gen;
424 5         21 $codegen = $outer_gen;
425 5         30 });
426             }
427              
428 1539 100       3356 if (!defined $action) {
429 1184         1935 my $attrs = $token->{attrs};
430 1184         6406 my @bad_attrs = sort grep $attrs->{$_} =~ /$self->{dummy_marker_re}/, keys %$attrs;
431 1184 100       2877 if (@bad_attrs) {
432 1         13 $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 1183   100     5258 $codegen->emit_open_tag($token->{name}, $attrs, self_closing => $token->{is_self_closing} && !$token->{is_void});
435 1183 100       2763 $matcher->leave if $token->{is_self_closing};
436 1183         5510 next;
437             }
438              
439 355 100       772 if ($action->{type} eq AT_REPLACE_OUTER) {
440 7         17 my $param = $action->{param};
441 7         9 my $skipped = 0;
442 7 100       36 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
    100          
    100          
443 2         9 $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       6 $param->{type} eq AT_P_TRANSFORM
452             or die "Internal error: unexpected parameter type '$param->{type}'";
453 2 50       5 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       6 if (@{$param->{dynamic}}) {
  2         8  
458 1         5 $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     46 _skip_children $token->{name}, $parser if !$skipped && !$token->{is_self_closing};
466 7         24 $matcher->leave;
467 7         30 next;
468             }
469              
470 348         840 while ($action->{type} eq AT_REPEAT_OUTER) {
471 1         6 my $loop_gen = $codegen->insert_loop($action->{var});
472              
473 1         3 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         6 $matcher->add_temp_rule(_with_stopper($selector, $action));
477             }
478              
479 1         3 my $inplace = _reduce_actions @{$action->{inplace}};
  1         5  
480             $action = defined($inplace)
481             ? _combine_actions $action->{nested}, _bind_scope $loop_gen->scope, $inplace
482 1 50       10 : $action->{nested};
483              
484 1         5 my $outer_gen = $codegen;
485 1         2 $codegen = $loop_gen;
486 1 50   1   5 $matcher->on_leave(fun ($ret = {}) {
  1 50       5  
  1         2  
487 1   33     17 $ret->{codegen} //= $loop_gen;
488 1         6 $codegen = $outer_gen;
489 1         11 });
490             }
491              
492 348 50       692 $action->{type} eq AT_REPLACE_INNER
493             or die "Internal error: unexpected action type '$action->{type}'";
494              
495 348         1236 $codegen->emit_open_tag_name_fragment($token->{name});
496              
497 348         658 my $attrset = $action->{attrset};
498 348 100       703 if ($attrset->{type} eq AT_AS_REPLACE_ATTRS) {
499 16         29 my $attrs = $attrset->{content};
500 16         66 for my $attr (sort keys %$attrs) {
501 37         64 my $param = $attrs->{$attr};
502 37 100       72 if ($param->{type} eq AT_P_IMMEDIATE) {
503 33         73 $codegen->emit_open_tag_attr_fragment($attr, $param->{value});
504             } else {
505 4 50       12 $param->{type} eq AT_P_VARIABLE
506             or die "Internal error: unexpected parameter type '$param->{type}'";
507 4         14 $codegen->emit_open_tag_attr_var_fragment($attr, $param->{value});
508             }
509             }
510             } else {
511 332 50       715 $attrset->{type} eq AT_AS_MODIFY_ATTRS
512             or die "Internal error: unexpected attrset replacement type '$attrset->{type}'";
513              
514 332         502 my $token_attrs = $token->{attrs};
515             my %attrs = map +(
516             $_ => {
517             type => AT_P_IMMEDIATE,
518 332         1973 value => $token_attrs->{$_},
519             pristine => 1,
520             }
521             ), keys %$token_attrs;
522              
523 332         654 my $attr_actions = $attrset->{content};
524 332         802 for my $attr (keys %$attr_actions) {
525 99         171 my $attr_action = $attr_actions->{$attr};
526 99 100       260 if ($attr_action->{type} eq AT_A_REMOVE_ATTR) {
    100          
527 14         42 delete $attrs{$attr};
528             } elsif ($attr_action->{type} eq AT_A_SET_ATTR) {
529 39         102 $attrs{$attr} = $attr_action->{param};
530             } else {
531 46 50       109 $attr_action->{type} eq AT_A_MODIFY_ATTR
532             or die "Internal error: unexpected attr action type '$attr_action->{type}'";
533 46         71 my $param = $attr_action->{param};
534 46 50       98 $param->{type} eq AT_P_TRANSFORM
535             or die "Internal error: unexpected parameter type '$param->{type}'";
536 46         183 my $value = $param->{static}->($attrs{$attr}{value});
537 46 100       125 if (@{$param->{dynamic}}) {
  46 100       134  
538 5         27 $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         146 $attrs{$attr} = { type => AT_P_IMMEDIATE, value => '' . $value };
543             }
544             }
545             }
546              
547 332         501 my @bad_attrs;
548 332         884 for my $attr (sort keys %attrs) {
549 346         575 my $param = $attrs{$attr};
550 346 100       691 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
551 331 100 100     1950 if ($param->{pristine} && $param->{value} =~ /$self->{dummy_marker_re}/) {
552 1         4 push @bad_attrs, $attr;
553             }
554 331         951 $codegen->emit_open_tag_attr_fragment($attr, $param->{value});
555             } elsif ($param->{type} eq AT_P_VARIABLE) {
556 10         37 $codegen->emit_open_tag_attr_var_fragment($attr, $param->{value});
557             } else {
558 5 50       14 $param->{type} eq AT_P_TRANSFORM
559             or die "Internal error: unexpected parameter type '$param->{type}'";
560 5         20 $codegen->emit_open_tag_attr_transform_fragment($attr, $param->{dynamic}, $param->{value});
561             }
562             }
563             @bad_attrs
564 332 100       1221 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 347         1014 $codegen->emit_open_tag_close_fragment;
568              
569 347         547 for my $repeat (@{$action->{repeat}}) {
  347         809  
570 2         11 my $loop_gen = $codegen->insert_loop($repeat->{var});
571 2         6 for my $proto_rule (@{$repeat->{rules}}) {
  2         7  
572 5         14 my ($selector, @actions) = @$proto_rule;
573 5         15 my $action = _bind_scope $loop_gen->scope, _reduce_actions @actions;
574 5         20 $matcher->add_temp_rule(_with_stopper($selector, $action));
575             }
576              
577 2         7 my $outer_gen = $codegen;
578 2         5 $codegen = $loop_gen;
579 2     2   13 $matcher->on_leave(fun (@) { $codegen = $outer_gen; });
  2         8  
580             }
581              
582 347 100       1099 if (defined(my $param = $action->{content})) {
    100          
583             $token->{is_void}
584 245 50       524 and $parser->throw_for($token, "<$token->{name}> tag cannot have content");
585              
586 245         350 my $skipped = 0;
587 245 100       648 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
    100          
    100          
588 157 100       443 if ($token->{name} eq 'script') {
    100          
589 21         58 $codegen->emit_script_text($param->{value});
590             } elsif ($token->{name} eq 'style') {
591 1         3 $codegen->emit_style_text($param->{value});
592             } else {
593 135         327 $codegen->emit_text($param->{value});
594             }
595             } elsif ($param->{type} eq AT_P_VARIABLE) {
596 30 100       101 if ($token->{name} eq 'script') {
    100          
597 18         82 $codegen->emit_variable_script($param->{value});
598             } elsif ($token->{name} eq 'style') {
599 1         6 $codegen->emit_variable_style($param->{value});
600             } else {
601 11         41 $codegen->emit_variable($param->{value});
602             }
603             } elsif ($param->{type} eq AT_P_FRAGMENT) {
604 7 100 100     72 $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 4         17 $codegen->incorporate($param->{value});
607             } elsif ($param->{type} eq AT_P_VARHTML) {
608 4 100 100     72 $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       116 $param->{type} eq AT_P_TRANSFORM
613             or die "Internal error: unexpected parameter type '$param->{type}'";
614 47 50       155 my $text_content = $token->{is_self_closing} ? '' : _skip_children $token->{name}, $parser, collect_content => 1;
615 47         88 $skipped = 1;
616 47         178 $text_content = '' . $param->{static}->($text_content);
617 47 100       200 if (@{$param->{dynamic}}) {
  47         119  
618 25 100       82 if ($token->{name} eq 'script') {
    100          
619 18         82 $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         23 $codegen->emit_call($param->{dynamic}, $text_content);
624             }
625             } else {
626 22 100       67 if ($token->{name} eq 'script') {
    100          
627 18         57 $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         12 $codegen->emit_text($text_content);
632             }
633             }
634             }
635              
636 229 50 66     1200 _skip_children $token->{name}, $parser if !$skipped && !$token->{is_self_closing};
637 229         799 $matcher->leave(\my %ret);
638 229   33     1236 ($ret{codegen} // $codegen)->emit_close_tag($token->{name});
639             } elsif ($token->{is_self_closing}) {
640 42         175 $matcher->leave;
641 42 50       257 $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 236         2574 }
652              
653             1