File Coverage

blib/lib/HTML/Blitz/RuleSet.pm
Criterion Covered Total %
statement 411 456 90.3
branch 246 372 66.1
condition 63 131 48.0
subroutine 32 33 96.9
pod 0 6 0.0
total 752 998 75.4


line stmt bran cond sub pod time code
1             # This code can be redistributed and modified under the terms of the GNU Affero
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   81 use HTML::Blitz::pragma;
  11         31  
  11         84  
7 11     11   9970 use HTML::Blitz::Matcher ();
  11         30  
  11         265  
8 11     11   6540 use HTML::Blitz::Parser ();
  11         39  
  11         508  
9 11     11   108 use HTML::Blitz::CodeGen ();
  11         20  
  11         306  
10 11         865 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   59 );
  11         20  
17 11         1057 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   5723 );
  11         85  
36 11     11   87 use List::Util qw(all reduce);
  11         23  
  11         2091  
37              
38             our $VERSION = '0.07';
39              
40 248 50   248 0 573 method new($class:) {
  248 50       544  
  248         390  
  248         357  
41 248         1959 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 5 method set_keep_doctype($val) {
  1 50       4  
  1         2  
  1         3  
  1         2  
50 1         5 $self->{keep_doctype} = !!$val;
51             }
52              
53 4 50   4 0 12 method set_keep_comments_re($keep_comments_re) {
  4 50       11  
  4         7  
  4         8  
  4         6  
54 4         80 $self->{keep_comments_re} = qr/(?#)$keep_comments_re/;
55             }
56              
57 4 50   4 0 12 method set_dummy_marker_re($dummy_marker_re) {
  4 50       8  
  4         8  
  4         7  
  4         5  
58 4         45 $self->{dummy_marker_re} = qr/(?#)$dummy_marker_re/;
59             }
60              
61 13 50   13   32 fun _combine_params_plain($p1, $p2) {
  13 50       24  
  13         25  
  13         14  
62 13 100       37 if ($p1->{type} eq AT_P_IMMEDIATE) {
63 11 100       31 if ($p2->{type} eq AT_P_IMMEDIATE) {
64 2 50       16 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         5 return $p2;
70             }
71 1 50 33     10 $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         8  
  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         8 my ($f1, $f2) = ($t1->{static}, $t2->{static});
83 4 50   8   29 fun ($x) { $f1->($f2->($x)) }
  8 50       33  
  8         17  
  8         14  
  8         10  
  8         21  
84             },
85 4         7 dynamic => [@{$t1->{dynamic}}, @{$t2->{dynamic}}],
  4         10  
  4         40  
86             };
87             }
88              
89 14 50   14   27 fun _combine_params($p1, $p2) {
  14 50       27  
  14         32  
  14         17  
90 14 50       35 if ($p1->{type} eq AT_P_FRAGMENT) {
91 0 0 0     0 if ($p2->{type} eq AT_P_FRAGMENT || $p2->{type} eq AT_P_TRANSFORM) {
92 0         0 return $p1;
93             }
94 0 0 0     0 $p2->{type} eq AT_P_IMMEDIATE || $p2->{type} eq AT_P_VARIABLE || $p2->{type} eq AT_P_VARHTML
      0        
95             or die "Internal error: unexpected parameter type '$p2->{type}'";
96 0         0 return $p2;
97             }
98              
99 14 100       29 if ($p2->{type} eq AT_P_FRAGMENT) {
100 2 50       7 if ($p1->{type} eq AT_P_TRANSFORM) {
101 2         11 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 12 50       31 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 12 50       22 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 12 100       26 if ($p1->{type} eq AT_P_TRANSFORM) {
126 4 50       14 if ($p2->{type} eq AT_P_TRANSFORM) {
127 4         14 return _combine_transforms($p1, $p2);
128             }
129 0 0 0     0 $p2->{type} eq AT_P_IMMEDIATE || $p2->{type} eq AT_P_VARIABLE
130             or die "Internal error: unexpected parameter type '$p2->{type}'";
131 0         0 return $p2;
132             }
133              
134 8 50       18 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 75 50   75   165 fun _combine_contents($p1, $p2) {
  75 50       150  
  75         144  
  75         119  
144 75 100       352 return $p1 if !defined $p2;
145 17 100       48 return $p2 if !defined $p1;
146 14         31 _combine_params $p1, $p2
147             }
148              
149 35 50   35   64 fun _combine_attr_actions($aa1, $aa2) {
  35 50       66  
  35         54  
  35         46  
150 35 100       86 return $aa1 if $aa1->{type} eq AT_A_REMOVE_ATTR;
151 15 50       38 return $aa2 if $aa2->{type} eq AT_A_REMOVE_ATTR;
152 15 50       33 if ($aa1->{type} eq AT_A_SET_ATTR) {
153 15 100       31 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       21 $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 75 50   75   153 fun _combine_attrset_actions($asa1, $asa2) {
  75 50       179  
  75         131  
  75         118  
171 75 100       164 if ($asa1->{type} eq AT_AS_REPLACE_ATTRS) {
172 15 50       29 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         49  
175 18     18   39 (all { $_->{type} eq AT_P_IMMEDIATE } values %{$asa2->{content}}) ? $asa2 :
  5         15  
176 15 50       38 keys(%{$asa1->{content}}) <= keys(%{$asa2->{content}}) ? $asa1 :
  2 100       4  
  2 100       14  
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 60 50       112 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 60 50 33     199 $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 60         90 my %content = %{$asa1->{content}};
  60         186  
191 60         104 for my $k (keys %{$asa2->{content}}) {
  60         135  
192 43         67 my $v = $asa2->{content}{$k};
193 43 100       108 $content{$k} = exists $content{$k} ? _combine_attr_actions($content{$k}, $v) : $v;
194             }
195 60         292 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 75 50   75   171 fun _combine_actions($act1, $act2) {
  75 50       138  
  75         138  
  75         91  
205 75 50       163 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 75 50       154 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 75 50       146 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 75 50       140 if ($act2->{type} eq AT_REPLACE_OUTER) {
221 0         0 return $act2;
222             }
223 75 50       146 if ($act1->{type} eq AT_REPEAT_OUTER) {
224 0         0 return { %$act1, nested => _combine_actions($act1->{nested}, $act2) };
225             }
226 75 50       148 if ($act2->{type} eq AT_REPEAT_OUTER) {
227 0         0 return { %$act2, nested => _combine_actions($act1, $act2->{nested}) };
228             }
229 75 50 33     316 $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 75         151 repeat => [@{$act1->{repeat}}, @{$act2->{repeat}}],
  75         206  
234             attrset => _combine_attrset_actions($act1->{attrset}, $act2->{attrset}),
235 75         109 content => _combine_contents($act1->{content}, $act2->{content}),
236             };
237             }
238              
239 1778     1778   3252 fun _reduce_actions(@actions) {
  1778         2344  
240 74     74   193 reduce { _combine_actions $a, $b } @actions
241 1778         11038 }
242              
243 261 50   261   566 fun _bind_scope($scope, $action) {
  261 50       528  
  261         502  
  261         367  
244 261 100       963 if ($action->{type} eq AT_REMOVE_IF) {
    100          
    100          
245             return {
246             type => AT_REMOVE_IF,
247 5         78 cond => [map [$_->[0] // $scope, $_->[1]], @{$action->{cond}}],
248 5   66     20 else => $action->{else} && _bind_scope($scope, $action->{else}),
      33        
249             };
250             } elsif ($action->{type} eq AT_REPLACE_OUTER) {
251 7         17 my $param = $action->{param};
252 7 100 100     34 if ($param->{type} eq AT_P_VARIABLE || $param->{type} eq AT_P_VARHTML) {
    100          
253 2         15 my $value = $param->{value};
254 2 50       8 if (!defined $value->[0]) {
255 2         16 return { %$action, param => { %$param, value => [$scope, $value->[1]] } };
256             }
257             } elsif ($param->{type} eq AT_P_TRANSFORM) {
258 2 100       10 if (@{$param->{dynamic}}) {
  2         8  
259 1   33     5 return { %$action, param => { %$param, dynamic => [ map [$_->[0] // $scope, $_->[1]], @{$param->{dynamic}} ] } };
  1         12  
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 248 50       581 $action->{type} eq AT_REPLACE_INNER
268             or die "Internal error: unexpected action type '$action->{type}'";
269              
270 248         339 my $did_replace = 0;
271 30 50   30   73 my $scope_var = fun ($var) {
  30 50       65  
  30         55  
  30         44  
272 30 50       60 defined $var->[0] ? $var : do {
273 30         40 $did_replace++;
274 30         147 [$scope, $var->[1]]
275             }
276 248         967 };
277 248         964 my %replacement = (type => AT_REPLACE_INNER, attrset => {}, content => undef, repeat => []);
278              
279 248         432 my $asa = $action->{attrset};
280 248 100       533 if ($asa->{type} eq AT_AS_REPLACE_ATTRS) {
281 6         18 $replacement{attrset}{type} = AT_AS_REPLACE_ATTRS;
282 6         10 my $content = $asa->{content};
283 6         11 $replacement{attrset}{content} = \my %copy;
284 6         19 for my $key (keys %$content) {
285 12         18 my $value = $content->{$key};
286 12 100 66     34 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 242 50       554 $asa->{type} eq AT_AS_MODIFY_ATTRS
295             or die "Internal error: unexpected attrset replacement type '$asa->{type}'";
296 242         501 $replacement{attrset}{type} = AT_AS_MODIFY_ATTRS;
297 242         364 my $content = $asa->{content};
298 242         437 $replacement{attrset}{content} = \my %copy;
299 242         688 for my $key (keys %$content) {
300 43         73 my $value = $content->{$key};
301 43 100 100     294 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         19 $did_replace++;
304             } elsif ($value->{type} eq AT_A_MODIFY_ATTR && (my $param = $value->{param})->{dynamic}->@*) {
305 5 50       19 $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         13 map $scope_var->($_), @{$param->{dynamic}}
  5         16  
314             ],
315             },
316             };
317             } else {
318 31         81 $copy{$key} = $value;
319             }
320             }
321             }
322              
323 248 100       607 if (defined(my $param = $action->{content})) {
324 201 100 100     1200 if (($param->{type} eq AT_P_VARIABLE || $param->{type} eq AT_P_VARHTML) && !defined $param->{value}[0]) {
    100 66        
      100        
325 35         160 $replacement{content} = { %$param, value => [$scope, $param->{value}[1]] };
326 35         75 $did_replace++;
327 45         137 } 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         46 map $scope_var->($_), @{$param->{dynamic}}
  23         58  
333             ],
334             };
335             } else {
336 143         297 $replacement{content} = $param;
337             }
338             }
339              
340 248 100       346 if (@{$replacement{repeat}} = @{$action->{repeat}}) {
  248         685  
  248         441  
341 2         8 my $rfirst = \$replacement{repeat}[0];
342 2 50       21 if (!defined $$rfirst->{var}[0]) {
343 2         10 $$rfirst = { var => $scope_var->($$rfirst->{var}), rules => $$rfirst->{rules} };
344 2         7 $did_replace++;
345             }
346             }
347              
348 248 100       1478 return \%replacement if $did_replace;
349             }
350 178         453 $action
351             }
352              
353 244 50   244 0 579 method add_rule($selector, $action, @actions) {
  244         395  
  244         474  
  244         330  
354 244         367 push @{$self->{rules}}, {
  244         796  
355             selector => $selector,
356             result => _reduce_actions($action, @actions),
357             };
358             }
359              
360 237 50 66 237   498 fun _skip_children($name, $parser, :$collect_content = undef) {
  237 50       659  
  237 50       511  
  237         418  
  237         526  
  237         299  
361 237         359 my $content = '';
362 237         321 my $depth = 0;
363 237         328 while () {
364 520   50     1266 my $token = $parser->parse // die "Internal error: missing '' in parser results";
365 520 100 66     1883 if ($token->{type} eq TT_TAG_CLOSE) {
    100          
    100          
366 252 100       757 last if $depth == 0;
367 15         33 $depth--;
368             } elsif ($token->{type} eq TT_TAG_OPEN) {
369 30 100       152 $depth++ if !$token->{is_self_closing};
370             } elsif ($collect_content && $token->{type} eq TT_TEXT) {
371 49         140 $content .= $token->{content};
372             }
373             }
374 237 100       575 $collect_content ? $content : undef
375             }
376              
377 260 50   260   570 fun _with_stopper($alternatives, $result) {
  260 50       475  
  260         447  
  260         313  
378 260         1456 map [ @$_, $result ], @$alternatives
379             }
380              
381 273 50   273 0 610 method compile($name, $html) {
  273 50       550  
  273         387  
  273         499  
  273         386  
382 273         966 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 273         506 @{$self->{rules}}
  273         1044  
386             ]);
387 273         1133 my $parser = HTML::Blitz::Parser->new($name, $html);
388              
389 273         780 while (my $token = $parser->parse) {
390 4811 100       16261 if ($token->{type} eq TT_DOCTYPE) {
    100          
    100          
    100          
    50          
391 9 100       25 if ($self->{keep_doctype}) {
392 8         25 $codegen->emit_doctype;
393             }
394             } elsif ($token->{type} eq TT_COMMENT) {
395 38 100       213 if ($token->{content} =~ /$self->{keep_comments_re}/) {
396 30         90 $codegen->emit_comment($token->{content});
397             }
398             } elsif ($token->{type} eq TT_TEXT) {
399 2151 100       9537 if ($token->{content} =~ /$self->{dummy_marker_re}/) {
400 3         17 $parser->throw_for($token, "raw text contains forbidden dummy marker $self->{dummy_marker_re}");
401             }
402 2148         5322 my $cur_tag = $parser->current_tag;
403 2148 100       4944 if ($cur_tag eq 'script') {
    100          
404 28         91 $codegen->emit_script_text($token->{content});
405             } elsif ($cur_tag eq 'style') {
406 66         170 $codegen->emit_style_text($token->{content});
407             } else {
408 2054         5069 $codegen->emit_text($token->{content});
409             }
410             } elsif ($token->{type} eq TT_TAG_CLOSE) {
411 1086         3655 $matcher->leave(\my %ret);
412 1086   66     5168 ($ret{codegen} // $codegen)->emit_close_tag($token->{name});
413             } elsif ($token->{type} eq TT_TAG_OPEN) {
414 1527         4536 my $action = _reduce_actions $matcher->enter($token->{name}, $token->{attrs});
415              
416 1527 100 100     6420 if (defined($action) && $action->{type} eq AT_REMOVE_IF) {
417 5         49 my $cond_gen = $codegen->insert_cond($action->{cond});
418 5         12 $action = $action->{else};
419              
420 5         8 my $outer_gen = $codegen;
421 5         7 $codegen = $cond_gen;
422 5 50   5   16 $matcher->on_leave(fun ($ret = {}) {
  5 100       19  
  5         7  
423 5   33     29 $ret->{codegen} //= $cond_gen;
424 5         20 $codegen = $outer_gen;
425 5         43 });
426             }
427              
428 1527 100       3064 if (!defined $action) {
429 1177         1913 my $attrs = $token->{attrs};
430 1177         6312 my @bad_attrs = sort grep $attrs->{$_} =~ /$self->{dummy_marker_re}/, keys %$attrs;
431 1177 100       2758 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 1176   100     5177 $codegen->emit_open_tag($token->{name}, $attrs, self_closing => $token->{is_self_closing} && !$token->{is_void});
435 1176 100       2833 $matcher->leave if $token->{is_self_closing};
436 1176         5255 next;
437             }
438              
439 350 100       771 if ($action->{type} eq AT_REPLACE_OUTER) {
440 7         33 my $param = $action->{param};
441 7         12 my $skipped = 0;
442 7 100       31 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
    100          
    100          
443 2         10 $codegen->emit_text($param->{value});
444             } elsif ($param->{type} eq AT_P_VARIABLE) {
445 1         7 $codegen->emit_variable($param->{value});
446             } elsif ($param->{type} eq AT_P_FRAGMENT) {
447 1         6 $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       14 $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       9 my $text_content = $token->{is_self_closing} ? '' : _skip_children $token->{name}, $parser, collect_content => 1;
455 2         5 $skipped = 1;
456 2         7 $text_content = '' . $param->{static}->($text_content);
457 2 100       8 if (@{$param->{dynamic}}) {
  2         6  
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     51 _skip_children $token->{name}, $parser if !$skipped && !$token->{is_self_closing};
466 7         29 $matcher->leave;
467 7         27 next;
468             }
469              
470 343         828 while ($action->{type} eq AT_REPEAT_OUTER) {
471 1         8 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         5 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       9 : $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         10 });
490             }
491              
492 343 50       707 $action->{type} eq AT_REPLACE_INNER
493             or die "Internal error: unexpected action type '$action->{type}'";
494              
495 343         1209 $codegen->emit_open_tag_name_fragment($token->{name});
496              
497 343         643 my $attrset = $action->{attrset};
498 343 100       721 if ($attrset->{type} eq AT_AS_REPLACE_ATTRS) {
499 16         26 my $attrs = $attrset->{content};
500 16         64 for my $attr (sort keys %$attrs) {
501 37         72 my $param = $attrs->{$attr};
502 37 100       68 if ($param->{type} eq AT_P_IMMEDIATE) {
503 33         76 $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 327 50       746 $attrset->{type} eq AT_AS_MODIFY_ATTRS
512             or die "Internal error: unexpected attrset replacement type '$attrset->{type}'";
513              
514 327         541 my $token_attrs = $token->{attrs};
515             my %attrs = map +(
516             $_ => {
517             type => AT_P_IMMEDIATE,
518 327         1901 value => $token_attrs->{$_},
519             pristine => 1,
520             }
521             ), keys %$token_attrs;
522              
523 327         667 my $attr_actions = $attrset->{content};
524 327         748 for my $attr (keys %$attr_actions) {
525 98         168 my $attr_action = $attr_actions->{$attr};
526 98 100       257 if ($attr_action->{type} eq AT_A_REMOVE_ATTR) {
    100          
527 14         47 delete $attrs{$attr};
528             } elsif ($attr_action->{type} eq AT_A_SET_ATTR) {
529 38         145 $attrs{$attr} = $attr_action->{param};
530             } else {
531 46 50       97 $attr_action->{type} eq AT_A_MODIFY_ATTR
532             or die "Internal error: unexpected attr action type '$attr_action->{type}'";
533 46         76 my $param = $attr_action->{param};
534 46 50       100 $param->{type} eq AT_P_TRANSFORM
535             or die "Internal error: unexpected parameter type '$param->{type}'";
536 46         209 my $value = $param->{static}->($attrs{$attr}{value});
537 46 100       99 if (@{$param->{dynamic}}) {
  46 100       131  
538 5         26 $attrs{$attr} = { type => AT_P_TRANSFORM, dynamic => $param->{dynamic}, value => $value };
539             } elsif (!defined $value) {
540 6         20 delete $attrs{$attr};
541             } else {
542 35         146 $attrs{$attr} = { type => AT_P_IMMEDIATE, value => '' . $value };
543             }
544             }
545             }
546              
547 327         541 my @bad_attrs;
548 327         901 for my $attr (sort keys %attrs) {
549 339         584 my $param = $attrs{$attr};
550 339 100       648 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
551 324 50 66     1819 if ($param->{pristine} && $param->{value} =~ /$self->{dummy_marker_re}/) {
552 0         0 push @bad_attrs, $attr;
553             }
554 324         946 $codegen->emit_open_tag_attr_fragment($attr, $param->{value});
555             } elsif ($param->{type} eq AT_P_VARIABLE) {
556 10         41 $codegen->emit_open_tag_attr_var_fragment($attr, $param->{value});
557             } else {
558 5 50       29 $param->{type} eq AT_P_TRANSFORM
559             or die "Internal error: unexpected parameter type '$param->{type}'";
560 5         23 $codegen->emit_open_tag_attr_transform_fragment($attr, $param->{dynamic}, $param->{value});
561             }
562             }
563             @bad_attrs
564 327 50       1141 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 343         1059 $codegen->emit_open_tag_close_fragment;
568              
569 343         506 for my $repeat (@{$action->{repeat}}) {
  343         782  
570 2         10 my $loop_gen = $codegen->insert_loop($repeat->{var});
571 2         13 for my $proto_rule (@{$repeat->{rules}}) {
  2         14  
572 5         13 my ($selector, @actions) = @$proto_rule;
573 5         25 my $action = _bind_scope $loop_gen->scope, _reduce_actions @actions;
574 5         39 $matcher->add_temp_rule(_with_stopper($selector, $action));
575             }
576              
577 2         9 my $outer_gen = $codegen;
578 2         4 $codegen = $loop_gen;
579 2     2   14 $matcher->on_leave(fun (@) { $codegen = $outer_gen; });
  2         23  
580             }
581              
582 343 100       1068 if (defined(my $param = $action->{content})) {
    100          
583             $token->{is_void}
584 241 50       524 and $parser->throw_for($token, "<$token->{name}> tag cannot have content");
585              
586 241         342 my $skipped = 0;
587 241 100       612 if ($param->{type} eq AT_P_IMMEDIATE) {
    100          
    100          
    100          
588 153 100       351 if ($token->{name} eq 'script') {
    100          
589 20         50 $codegen->emit_script_text($param->{value});
590             } elsif ($token->{name} eq 'style') {
591 1         8 $codegen->emit_style_text($param->{value});
592             } else {
593 132         325 $codegen->emit_text($param->{value});
594             }
595             } elsif ($param->{type} eq AT_P_VARIABLE) {
596 30 100       108 if ($token->{name} eq 'script') {
    100          
597 18         49 $codegen->emit_variable_script($param->{value});
598             } elsif ($token->{name} eq 'style') {
599 1         12 $codegen->emit_variable_style($param->{value});
600             } else {
601 11         40 $codegen->emit_variable($param->{value});
602             }
603             } elsif ($param->{type} eq AT_P_FRAGMENT) {
604 7 100 100     83 $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         24 $codegen->incorporate($param->{value});
607             } elsif ($param->{type} eq AT_P_VARHTML) {
608 4 100 100     56 $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       97 $param->{type} eq AT_P_TRANSFORM
613             or die "Internal error: unexpected parameter type '$param->{type}'";
614 47 50       136 my $text_content = $token->{is_self_closing} ? '' : _skip_children $token->{name}, $parser, collect_content => 1;
615 47         82 $skipped = 1;
616 47         161 $text_content = '' . $param->{static}->($text_content);
617 47 100       145 if (@{$param->{dynamic}}) {
  47         102  
618 25 100       74 if ($token->{name} eq 'script') {
    100          
619 18         66 $codegen->emit_call_script($param->{dynamic}, $text_content);
620             } elsif ($token->{name} eq 'style') {
621 1         12 $codegen->emit_call_style($param->{dynamic}, $text_content);
622             } else {
623 6         31 $codegen->emit_call($param->{dynamic}, $text_content);
624             }
625             } else {
626 22 100       63 if ($token->{name} eq 'script') {
    100          
627 18         50 $codegen->emit_script_text($text_content);
628             } elsif ($token->{name} eq 'style') {
629 1         7 $codegen->emit_style_text($text_content);
630             } else {
631 3         12 $codegen->emit_text($text_content);
632             }
633             }
634             }
635              
636 225 50 66     1084 _skip_children $token->{name}, $parser if !$skipped && !$token->{is_self_closing};
637 225         843 $matcher->leave(\my %ret);
638 225   33     1176 ($ret{codegen} // $codegen)->emit_close_tag($token->{name});
639             } elsif ($token->{is_self_closing}) {
640 42         136 $matcher->leave;
641 42 50       260 $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         2386 }
652              
653             1