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