line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::DOM58::_HTML; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# This file is part of Mojo::DOM58 which is released under: |
4
|
|
|
|
|
|
|
# The Artistic License 2.0 (GPL Compatible) |
5
|
|
|
|
|
|
|
# See the documentation for Mojo::DOM58 for full license details. |
6
|
|
|
|
|
|
|
|
7
|
2
|
|
|
2
|
|
15
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
67
|
|
8
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
67
|
|
9
|
2
|
|
|
2
|
|
12
|
use Exporter 'import'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
80
|
|
10
|
2
|
|
|
2
|
|
1154
|
use Mojo::DOM58::Entities qw(html_attr_unescape html_escape html_unescape); |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
425
|
|
11
|
2
|
|
|
2
|
|
24
|
use Scalar::Util 'weaken'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
3442
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '3.000'; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our @EXPORT_OK = 'tag_to_html'; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
my $ATTR_RE = qr/ |
18
|
|
|
|
|
|
|
([^<>=\s\/]+|\/) # Key |
19
|
|
|
|
|
|
|
(?: |
20
|
|
|
|
|
|
|
\s*=\s* |
21
|
|
|
|
|
|
|
(?s:(?:"(.*?)")|(?:'(.*?)')|([^>\s]*)) # Value |
22
|
|
|
|
|
|
|
)? |
23
|
|
|
|
|
|
|
\s* |
24
|
|
|
|
|
|
|
/x; |
25
|
|
|
|
|
|
|
my $TOKEN_RE = qr/ |
26
|
|
|
|
|
|
|
([^<]+)? # Text |
27
|
|
|
|
|
|
|
(?: |
28
|
|
|
|
|
|
|
<(?: |
29
|
|
|
|
|
|
|
!(?: |
30
|
|
|
|
|
|
|
DOCTYPE( |
31
|
|
|
|
|
|
|
\s+\w+ # Doctype |
32
|
|
|
|
|
|
|
(?:(?:\s+\w+)?(?:\s+(?:"[^"]*"|'[^']*'))+)? # External ID |
33
|
|
|
|
|
|
|
(?:\s+\[.+?\])? # Int Subset |
34
|
|
|
|
|
|
|
\s*) |
35
|
|
|
|
|
|
|
| |
36
|
|
|
|
|
|
|
--(.*?)--\s* # Comment |
37
|
|
|
|
|
|
|
| |
38
|
|
|
|
|
|
|
\[CDATA\[(.*?)\]\] # CDATA |
39
|
|
|
|
|
|
|
) |
40
|
|
|
|
|
|
|
| |
41
|
|
|
|
|
|
|
\?(.*?)\? # Processing Instruction |
42
|
|
|
|
|
|
|
| |
43
|
|
|
|
|
|
|
\s*([^<>\s]+\s*(?>(?:$ATTR_RE){0,32766})*) # Tag |
44
|
|
|
|
|
|
|
# Workaround for perl's limit of * to {0,32767} |
45
|
|
|
|
|
|
|
)> |
46
|
|
|
|
|
|
|
| |
47
|
|
|
|
|
|
|
(<) # Runaway "<" |
48
|
|
|
|
|
|
|
)?? |
49
|
|
|
|
|
|
|
/xis; |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# HTML elements that only contain raw text |
52
|
|
|
|
|
|
|
my %RAW = map { $_ => 1 } qw(script style); |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# HTML elements that only contain raw text and entities |
55
|
|
|
|
|
|
|
my %RCDATA = map { $_ => 1 } qw(title textarea); |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# HTML elements with optional end tags |
58
|
|
|
|
|
|
|
my %END = (body => 'head', optgroup => 'optgroup', option => 'option'); |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# HTML elements that break paragraphs |
61
|
|
|
|
|
|
|
$END{$_} = 'p' for |
62
|
|
|
|
|
|
|
qw(address article aside blockquote details dialog div dl fieldset), |
63
|
|
|
|
|
|
|
qw(figcaption figure footer form h1 h2 h3 h4 h5 h6 header hgroup hr main), |
64
|
|
|
|
|
|
|
qw(menu nav ol p pre section table ul); |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# HTML table elements with optional end tags |
67
|
|
|
|
|
|
|
my %TABLE = map { $_ => 1 } qw(colgroup tbody td tfoot th thead tr); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# HTML elements with optional end tags and scoping rules |
70
|
|
|
|
|
|
|
my %CLOSE |
71
|
|
|
|
|
|
|
= (li => [{li => 1}, {ul => 1, ol => 1}], tr => [{tr => 1}, {table => 1}]); |
72
|
|
|
|
|
|
|
$CLOSE{$_} = [\%TABLE, {table => 1}] for qw(colgroup tbody tfoot thead); |
73
|
|
|
|
|
|
|
$CLOSE{$_} = [{dd => 1, dt => 1}, {dl => 1}] for qw(dd dt); |
74
|
|
|
|
|
|
|
$CLOSE{$_} = [{rp => 1, rt => 1}, {ruby => 1}] for qw(rp rt); |
75
|
|
|
|
|
|
|
$CLOSE{$_} = [{th => 1, td => 1}, {table => 1}] for qw(td th); |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# HTML parent elements that signal no more content when closed, but that are also phrasing content |
78
|
|
|
|
|
|
|
my %NO_MORE_CONTENT = (ruby => [qw(rt rp)], select => [qw(option optgroup)]); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# HTML elements without end tags |
81
|
|
|
|
|
|
|
my %EMPTY = map { $_ => 1 } ( |
82
|
|
|
|
|
|
|
qw(area base br col embed hr img input keygen link menuitem meta param), |
83
|
|
|
|
|
|
|
qw(source track wbr) |
84
|
|
|
|
|
|
|
); |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# HTML elements categorized as phrasing content (and obsolete inline elements) |
87
|
|
|
|
|
|
|
my @PHRASING = ( |
88
|
|
|
|
|
|
|
qw(a abbr area audio b bdi bdo br button canvas cite code data datalist), |
89
|
|
|
|
|
|
|
qw(del dfn em embed i iframe img input ins kbd keygen label link map mark), |
90
|
|
|
|
|
|
|
qw(math meta meter noscript object output picture progress q ruby s samp), |
91
|
|
|
|
|
|
|
qw(script select slot small span strong sub sup svg template textarea time u), |
92
|
|
|
|
|
|
|
qw(var video wbr) |
93
|
|
|
|
|
|
|
); |
94
|
|
|
|
|
|
|
my @OBSOLETE = qw(acronym applet basefont big font strike tt); |
95
|
|
|
|
|
|
|
my %PHRASING = map { $_ => 1 } @OBSOLETE, @PHRASING; |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# HTML elements that don't get their self-closing flag acknowledged |
98
|
|
|
|
|
|
|
my %BLOCK = map { $_ => 1 } ( |
99
|
|
|
|
|
|
|
qw(a address applet article aside b big blockquote body button caption), |
100
|
|
|
|
|
|
|
qw(center code col colgroup dd details dialog dir div dl dt em fieldset), |
101
|
|
|
|
|
|
|
qw(figcaption figure font footer form frameset h1 h2 h3 h4 h5 h6 head), |
102
|
|
|
|
|
|
|
qw(header hgroup html i iframe li listing main marquee menu nav nobr), |
103
|
|
|
|
|
|
|
qw(noembed noframes noscript object ol optgroup option p plaintext pre rp), |
104
|
|
|
|
|
|
|
qw(rt s script section select small strike strong style summary table), |
105
|
|
|
|
|
|
|
qw(tbody td template textarea tfoot th thead title tr tt u ul xmp) |
106
|
|
|
|
|
|
|
); |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
sub new { |
109
|
2194
|
|
|
2194
|
0
|
3372
|
my $class = shift; |
110
|
2194
|
50
|
33
|
|
|
12668
|
bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class; |
|
0
|
100
|
|
|
|
0
|
|
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
11
|
|
|
11
|
0
|
42
|
sub tag { shift->tree(['root', _tag(@_)]) } |
114
|
|
|
|
|
|
|
|
115
|
1
|
|
|
1
|
0
|
5
|
sub tag_to_html { _render(_tag(@_), undef) } |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub tree { |
118
|
5055
|
|
|
5055
|
0
|
8119
|
my $self = shift; |
119
|
5055
|
100
|
|
|
|
18265
|
return exists $self->{tree} ? $self->{tree} : ($self->{tree} = ['root']) unless @_; |
|
|
100
|
|
|
|
|
|
120
|
2203
|
|
|
|
|
4587
|
$self->{tree} = shift; |
121
|
2203
|
|
|
|
|
8169
|
return $self; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub xml { |
125
|
3669
|
|
|
3669
|
0
|
5391
|
my $self = shift; |
126
|
3669
|
100
|
|
|
|
9013
|
return $self->{xml} unless @_; |
127
|
1980
|
|
|
|
|
3239
|
$self->{xml} = shift; |
128
|
1980
|
|
|
|
|
8878
|
return $self; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub parse { |
132
|
225
|
|
|
225
|
0
|
980
|
my ($self, $html) = (shift, "$_[0]"); |
133
|
|
|
|
|
|
|
|
134
|
225
|
|
|
|
|
602
|
my $xml = $self->xml; |
135
|
225
|
|
|
|
|
596
|
my $current = my $tree = ['root']; |
136
|
225
|
|
|
|
|
2003
|
while ($html =~ /\G$TOKEN_RE/gcso) { |
137
|
4359
|
|
|
|
|
14014
|
my ($text, $doctype, $comment, $cdata, $pi, $tag, $runaway) |
138
|
|
|
|
|
|
|
= ($1, $2, $3, $4, $5, $6, $11); |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# Text (and runaway "<") |
141
|
4359
|
100
|
|
|
|
8313
|
$text .= '<' if defined $runaway; |
142
|
4359
|
100
|
|
|
|
8236
|
_node($current, 'text', html_unescape $text) if defined $text; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# Tag |
145
|
4359
|
100
|
|
|
|
160680
|
if (defined $tag) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# End |
148
|
1468
|
100
|
|
|
|
5481
|
if ($tag =~ /^\/\s*(\S+)/) { |
|
|
50
|
|
|
|
|
|
149
|
605
|
100
|
|
|
|
1415
|
my $end = $xml ? $1 : lc $1; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# No more content |
152
|
605
|
100
|
100
|
|
|
2218
|
if (!$xml && (my $tags = $NO_MORE_CONTENT{$end})) { _end($_, $xml, \$current) for @$tags } |
|
11
|
|
|
|
|
50
|
|
153
|
|
|
|
|
|
|
|
154
|
605
|
100
|
|
|
|
1772
|
_end($xml ? $1 : lc $1, $xml, \$current); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# Start |
158
|
|
|
|
|
|
|
elsif ($tag =~ m!^([^\s/]+)([\s\S]*)!) { |
159
|
863
|
100
|
|
|
|
3110
|
my ($start, $attr) = ($xml ? $1 : lc $1, $2); |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Attributes |
162
|
863
|
|
|
|
|
1406
|
my (%attrs, $closing); |
163
|
863
|
|
|
|
|
2652
|
while ($attr =~ /$ATTR_RE/go) { |
164
|
33102
|
100
|
|
|
|
70177
|
my $key = $xml ? $1 : lc $1; |
165
|
33102
|
100
|
|
|
|
68674
|
my $value = defined($2) ? $2 : defined($3) ? $3 : $4; |
|
|
100
|
|
|
|
|
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# Empty tag |
168
|
33102
|
100
|
50
|
|
|
56236
|
++$closing and next if $key eq '/'; |
169
|
|
|
|
|
|
|
|
170
|
33058
|
100
|
|
|
|
68541
|
$attrs{$key} = defined $value ? html_attr_unescape $value : $value; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# "image" is an alias for "img" |
174
|
863
|
100
|
100
|
|
|
3028
|
$start = 'img' if !$xml && $start eq 'image'; |
175
|
863
|
|
|
|
|
2452
|
_start($start, \%attrs, $xml, \$current); |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# Element without end tag (self-closing) |
178
|
|
|
|
|
|
|
_end($start, $xml, \$current) |
179
|
863
|
100
|
100
|
|
|
5595
|
if !$xml && $EMPTY{$start} || ($xml || !$BLOCK{$start}) && $closing; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# Raw text elements |
182
|
863
|
100
|
100
|
|
|
6436
|
next if $xml || !$RAW{$start} && !$RCDATA{$start}; |
|
|
|
100
|
|
|
|
|
183
|
40
|
100
|
|
|
|
970
|
next unless $html =~ m!\G(.*?)<\s*/\s*\Q$start\E\s*>!gcsi; |
184
|
39
|
100
|
|
|
|
226
|
_node($current, 'raw', $RCDATA{$start} ? html_unescape $1 : $1); |
185
|
39
|
|
|
|
|
123
|
_end($start, 0, \$current); |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# DOCTYPE |
190
|
12
|
|
|
|
|
45
|
elsif (defined $doctype) { _node($current, 'doctype', $doctype) } |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# Comment |
193
|
10
|
|
|
|
|
30
|
elsif (defined $comment) { _node($current, 'comment', $comment) } |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# CDATA |
196
|
7
|
|
|
|
|
22
|
elsif (defined $cdata) { _node($current, 'cdata', $cdata) } |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Processing instruction (try to detect XML) |
199
|
|
|
|
|
|
|
elsif (defined $pi) { |
200
|
18
|
100
|
100
|
|
|
171
|
$self->xml($xml = 1) if !exists $self->{xml} && $pi =~ /xml/i; |
201
|
18
|
|
|
|
|
55
|
_node($current, 'pi', $pi); |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
225
|
|
|
|
|
676
|
return $self->tree($tree); |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
152
|
|
|
152
|
0
|
374
|
sub render { _render($_[0]->tree, $_[0]->xml) } |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub _end { |
211
|
938
|
|
|
938
|
|
1904
|
my ($end, $xml, $current) = @_; |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Search stack for start tag |
214
|
938
|
|
|
|
|
1436
|
my $next = $$current; |
215
|
938
|
|
|
|
|
1224
|
do { |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# Ignore useless end tag |
218
|
1302
|
100
|
|
|
|
2717
|
return if $next->[0] eq 'root'; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# Right tag |
221
|
1145
|
100
|
|
|
|
4553
|
return $$current = $next->[3] if $next->[1] eq $end; |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
# Phrasing content can only cross phrasing content |
224
|
368
|
100
|
100
|
|
|
1576
|
return if !$xml && $PHRASING{$end} && !$PHRASING{$next->[1]}; |
|
|
|
66
|
|
|
|
|
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
} while $next = $next->[3]; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
sub _node { |
230
|
1183
|
|
|
1183
|
|
2338
|
my ($current, $type, $content) = @_; |
231
|
1183
|
|
|
|
|
3492
|
push @$current, my $new = [$type, $content, $current]; |
232
|
1183
|
|
|
|
|
3252
|
weaken $new->[2]; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub _render { |
236
|
981
|
|
|
981
|
|
1639
|
my ($tree, $xml) = @_; |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# Tag |
239
|
981
|
|
|
|
|
1589
|
my $type = $tree->[0]; |
240
|
981
|
100
|
|
|
|
1852
|
if ($type eq 'tag') { |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Start tag |
243
|
434
|
|
|
|
|
731
|
my $tag = $tree->[1]; |
244
|
434
|
|
|
|
|
809
|
my $result = "<$tag"; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# Attributes |
247
|
434
|
|
|
|
|
549
|
for my $key (sort keys %{$tree->[2]}) { |
|
434
|
|
|
|
|
1252
|
|
248
|
64
|
|
|
|
|
141
|
my $value = $tree->[2]{$key}; |
249
|
64
|
100
|
50
|
|
|
168
|
$result .= $xml ? qq{ $key="$key"} : " $key" and next |
|
|
100
|
|
|
|
|
|
250
|
|
|
|
|
|
|
unless defined $value; |
251
|
54
|
|
|
|
|
175
|
$result .= qq{ $key="} . html_escape($value) . '"'; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# No children |
255
|
434
|
100
|
|
|
|
1189
|
return $xml ? "$result />" : $EMPTY{$tag} ? "$result>" : "$result>$tag>" |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
256
|
|
|
|
|
|
|
unless $tree->[4]; |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# Children |
259
|
2
|
|
|
2
|
|
19
|
no warnings 'recursion'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
1202
|
|
260
|
394
|
|
|
|
|
821
|
$result .= '>' . join '', map { _render($_, $xml) } @$tree[4 .. $#$tree]; |
|
653
|
|
|
|
|
1533
|
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
# End tag |
263
|
394
|
|
|
|
|
2515
|
return "$result$tag>"; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Text (escaped) |
267
|
547
|
100
|
|
|
|
1346
|
return html_escape($tree->[1]) if $type eq 'text'; |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Raw text |
270
|
144
|
100
|
|
|
|
344
|
return $tree->[1] if $type eq 'raw'; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Root |
273
|
138
|
100
|
|
|
|
655
|
return join '', map { _render($_, $xml) } @$tree[1 .. $#$tree] |
|
175
|
|
|
|
|
361
|
|
274
|
|
|
|
|
|
|
if $type eq 'root'; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# DOCTYPE |
277
|
14
|
100
|
|
|
|
71
|
return '[1] . '>' if $type eq 'doctype'; |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# Comment |
280
|
10
|
100
|
|
|
|
55
|
return '' if $type eq 'comment'; |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# CDATA |
283
|
6
|
100
|
|
|
|
37
|
return '[1] . ']]>' if $type eq 'cdata'; |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# Processing instruction |
286
|
3
|
50
|
|
|
|
31
|
return '' . $tree->[1] . '?>' if $type eq 'pi'; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
# Everything else |
289
|
0
|
|
|
|
|
0
|
return ''; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub _start { |
293
|
863
|
|
|
863
|
|
1720
|
my ($start, $attrs, $xml, $current) = @_; |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# Autoclose optional HTML elements |
296
|
863
|
100
|
100
|
|
|
3159
|
if (!$xml && $$current->[0] ne 'root') { |
297
|
557
|
100
|
|
|
|
1735
|
if (my $end = $END{$start}) { _end($end, 0, $current) } |
|
148
|
100
|
|
|
|
320
|
|
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
elsif (my $close = $CLOSE{$start}) { |
300
|
146
|
|
|
|
|
297
|
my ($allowed, $scope) = @$close; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Close allowed parent elements in scope |
303
|
146
|
|
|
|
|
257
|
my $parent = $$current; |
304
|
146
|
|
66
|
|
|
657
|
while ($parent->[0] ne 'root' && !$scope->{$parent->[1]}) { |
305
|
147
|
100
|
|
|
|
416
|
_end($parent->[1], 0, $current) if $allowed->{$parent->[1]}; |
306
|
147
|
|
|
|
|
523
|
$parent = $parent->[3]; |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# New tag |
312
|
863
|
|
|
|
|
2842
|
push @$$current, my $new = ['tag', $start, $attrs, $$current]; |
313
|
863
|
|
|
|
|
2637
|
weaken $new->[3]; |
314
|
863
|
|
|
|
|
1578
|
$$current = $new; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
sub _tag { |
318
|
12
|
|
|
12
|
|
34
|
my $tree = ['tag', shift, undef, undef]; |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# Content |
321
|
12
|
100
|
|
|
|
60
|
push @$tree, ref $_[-1] eq 'CODE' ? ['raw', pop->()] : ['text', pop] |
|
|
100
|
|
|
|
|
|
322
|
|
|
|
|
|
|
if @_ % 2; |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
# Attributes |
325
|
12
|
|
|
|
|
45
|
my $attrs = $tree->[2] = {@_}; |
326
|
12
|
100
|
66
|
|
|
62
|
return $tree unless exists $attrs->{data} && ref $attrs->{data} eq 'HASH'; |
327
|
1
|
|
|
|
|
2
|
my $data = delete $attrs->{data}; |
328
|
1
|
|
|
|
|
6
|
@$attrs{map { y/_/-/; lc "data-$_" } keys %$data} = values %$data; |
|
2
|
|
|
|
|
7
|
|
|
2
|
|
|
|
|
9
|
|
329
|
|
|
|
|
|
|
|
330
|
1
|
|
|
|
|
5
|
return $tree; |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
1; |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=for Pod::Coverage *EVERYTHING* |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=cut |