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
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
58
|
|
8
|
2
|
|
|
2
|
|
12
|
use warnings; |
|
2
|
|
|
|
|
25
|
|
|
2
|
|
|
|
|
55
|
|
9
|
2
|
|
|
2
|
|
18
|
use Exporter 'import'; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
79
|
|
10
|
2
|
|
|
2
|
|
1073
|
use Mojo::DOM58::Entities qw(html_attr_unescape html_escape html_unescape); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
317
|
|
11
|
2
|
|
|
2
|
|
18
|
use Scalar::Util 'weaken'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
2938
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '2.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 elements without end tags |
78
|
|
|
|
|
|
|
my %EMPTY = map { $_ => 1 } ( |
79
|
|
|
|
|
|
|
qw(area base br col embed hr img input keygen link menuitem meta param), |
80
|
|
|
|
|
|
|
qw(source track wbr) |
81
|
|
|
|
|
|
|
); |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# HTML elements categorized as phrasing content (and obsolete inline elements) |
84
|
|
|
|
|
|
|
my @PHRASING = ( |
85
|
|
|
|
|
|
|
qw(a abbr area audio b bdi bdo br button canvas cite code data datalist), |
86
|
|
|
|
|
|
|
qw(del dfn em embed i iframe img input ins kbd keygen label link map mark), |
87
|
|
|
|
|
|
|
qw(math meta meter noscript object output picture progress q ruby s samp), |
88
|
|
|
|
|
|
|
qw(script select slot small span strong sub sup svg template textarea time u), |
89
|
|
|
|
|
|
|
qw(var video wbr) |
90
|
|
|
|
|
|
|
); |
91
|
|
|
|
|
|
|
my @OBSOLETE = qw(acronym applet basefont big font strike tt); |
92
|
|
|
|
|
|
|
my %PHRASING = map { $_ => 1 } @OBSOLETE, @PHRASING; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# HTML elements that don't get their self-closing flag acknowledged |
95
|
|
|
|
|
|
|
my %BLOCK = map { $_ => 1 } ( |
96
|
|
|
|
|
|
|
qw(a address applet article aside b big blockquote body button caption), |
97
|
|
|
|
|
|
|
qw(center code col colgroup dd details dialog dir div dl dt em fieldset), |
98
|
|
|
|
|
|
|
qw(figcaption figure font footer form frameset h1 h2 h3 h4 h5 h6 head), |
99
|
|
|
|
|
|
|
qw(header hgroup html i iframe li listing main marquee menu nav nobr), |
100
|
|
|
|
|
|
|
qw(noembed noframes noscript object ol optgroup option p plaintext pre rp), |
101
|
|
|
|
|
|
|
qw(rt s script section select small strike strong style summary table), |
102
|
|
|
|
|
|
|
qw(tbody td template textarea tfoot th thead title tr tt u ul xmp) |
103
|
|
|
|
|
|
|
); |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub new { |
106
|
2061
|
|
|
2061
|
0
|
3327
|
my $class = shift; |
107
|
2061
|
50
|
33
|
|
|
11573
|
bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class; |
|
0
|
100
|
|
|
|
0
|
|
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
11
|
|
|
11
|
0
|
23
|
sub tag { shift->tree(['root', _tag(@_)]) } |
111
|
|
|
|
|
|
|
|
112
|
1
|
|
|
1
|
0
|
3
|
sub tag_to_html { _render(_tag(@_), undef) } |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub tree { |
115
|
4742
|
|
|
4742
|
0
|
7118
|
my $self = shift; |
116
|
4742
|
100
|
|
|
|
16745
|
return exists $self->{tree} ? $self->{tree} : ($self->{tree} = ['root']) unless @_; |
|
|
100
|
|
|
|
|
|
117
|
2072
|
|
|
|
|
4253
|
$self->{tree} = shift; |
118
|
2072
|
|
|
|
|
8431
|
return $self; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub xml { |
122
|
3417
|
|
|
3417
|
0
|
4952
|
my $self = shift; |
123
|
3417
|
100
|
|
|
|
8081
|
return $self->{xml} unless @_; |
124
|
1860
|
|
|
|
|
3537
|
$self->{xml} = shift; |
125
|
1860
|
|
|
|
|
7827
|
return $self; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
sub parse { |
129
|
213
|
|
|
213
|
0
|
768
|
my ($self, $html) = (shift, "$_[0]"); |
130
|
|
|
|
|
|
|
|
131
|
213
|
|
|
|
|
490
|
my $xml = $self->xml; |
132
|
213
|
|
|
|
|
482
|
my $current = my $tree = ['root']; |
133
|
213
|
|
|
|
|
1609
|
while ($html =~ /\G$TOKEN_RE/gcso) { |
134
|
3936
|
|
|
|
|
11822
|
my ($text, $doctype, $comment, $cdata, $pi, $tag, $runaway) |
135
|
|
|
|
|
|
|
= ($1, $2, $3, $4, $5, $6, $11); |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Text (and runaway "<") |
138
|
3936
|
100
|
|
|
|
6711
|
$text .= '<' if defined $runaway; |
139
|
3936
|
100
|
|
|
|
7220
|
_node($current, 'text', html_unescape $text) if defined $text; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
# Tag |
142
|
3936
|
100
|
|
|
|
138955
|
if (defined $tag) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# End |
145
|
1329
|
100
|
|
|
|
4210
|
if ($tag =~ /^\/\s*(\S+)/) { _end($xml ? $1 : lc $1, $xml, \$current) } |
|
550
|
100
|
|
|
|
1720
|
|
|
|
50
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Start |
148
|
|
|
|
|
|
|
elsif ($tag =~ m!^([^\s/]+)([\s\S]*)!) { |
149
|
779
|
100
|
|
|
|
2549
|
my ($start, $attr) = ($xml ? $1 : lc $1, $2); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Attributes |
152
|
779
|
|
|
|
|
1809
|
my (%attrs, $closing); |
153
|
779
|
|
|
|
|
2282
|
while ($attr =~ /$ATTR_RE/go) { |
154
|
33091
|
100
|
|
|
|
59920
|
my $key = $xml ? $1 : lc $1; |
155
|
33091
|
100
|
|
|
|
57882
|
my $value = defined($2) ? $2 : defined($3) ? $3 : $4; |
|
|
100
|
|
|
|
|
|
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# Empty tag |
158
|
33091
|
100
|
50
|
|
|
48414
|
++$closing and next if $key eq '/'; |
159
|
|
|
|
|
|
|
|
160
|
33047
|
100
|
|
|
|
58965
|
$attrs{$key} = defined $value ? html_attr_unescape $value : $value; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# "image" is an alias for "img" |
164
|
779
|
100
|
100
|
|
|
2363
|
$start = 'img' if !$xml && $start eq 'image'; |
165
|
779
|
|
|
|
|
2095
|
_start($start, \%attrs, $xml, \$current); |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# Element without end tag (self-closing) |
168
|
|
|
|
|
|
|
_end($start, $xml, \$current) |
169
|
779
|
100
|
100
|
|
|
4481
|
if !$xml && $EMPTY{$start} || ($xml || !$BLOCK{$start}) && $closing; |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# Raw text elements |
172
|
779
|
100
|
100
|
|
|
5135
|
next if $xml || !$RAW{$start} && !$RCDATA{$start}; |
|
|
|
100
|
|
|
|
|
173
|
29
|
100
|
|
|
|
579
|
next unless $html =~ m!\G(.*?)<\s*/\s*\Q$start\E\s*>!gcsi; |
174
|
28
|
100
|
|
|
|
141
|
_node($current, 'raw', $RCDATA{$start} ? html_unescape $1 : $1); |
175
|
28
|
|
|
|
|
81
|
_end($start, 0, \$current); |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# DOCTYPE |
180
|
11
|
|
|
|
|
30
|
elsif (defined $doctype) { _node($current, 'doctype', $doctype) } |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# Comment |
183
|
10
|
|
|
|
|
27
|
elsif (defined $comment) { _node($current, 'comment', $comment) } |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# CDATA |
186
|
7
|
|
|
|
|
38
|
elsif (defined $cdata) { _node($current, 'cdata', $cdata) } |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Processing instruction (try to detect XML) |
189
|
|
|
|
|
|
|
elsif (defined $pi) { |
190
|
17
|
100
|
100
|
|
|
127
|
$self->xml($xml = 1) if !exists $self->{xml} && $pi =~ /xml/i; |
191
|
17
|
|
|
|
|
42
|
_node($current, 'pi', $pi); |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
213
|
|
|
|
|
603
|
return $self->tree($tree); |
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
|
198
|
152
|
|
|
152
|
0
|
332
|
sub render { _render($_[0]->tree, $_[0]->xml) } |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub _end { |
201
|
823
|
|
|
823
|
|
1522
|
my ($end, $xml, $current) = @_; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# Search stack for start tag |
204
|
823
|
|
|
|
|
1134
|
my $next = $$current; |
205
|
823
|
|
|
|
|
1050
|
do { |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Ignore useless end tag |
208
|
1119
|
100
|
|
|
|
2149
|
return if $next->[0] eq 'root'; |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Right tag |
211
|
998
|
100
|
|
|
|
3591
|
return $$current = $next->[3] if $next->[1] eq $end; |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Phrasing content can only cross phrasing content |
214
|
301
|
100
|
100
|
|
|
1204
|
return if !$xml && $PHRASING{$end} && !$PHRASING{$next->[1]}; |
|
|
|
66
|
|
|
|
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
} while $next = $next->[3]; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
sub _node { |
220
|
1041
|
|
|
1041
|
|
1790
|
my ($current, $type, $content) = @_; |
221
|
1041
|
|
|
|
|
3665
|
push @$current, my $new = [$type, $content, $current]; |
222
|
1041
|
|
|
|
|
2779
|
weaken $new->[2]; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub _render { |
226
|
981
|
|
|
981
|
|
1550
|
my ($tree, $xml) = @_; |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# Tag |
229
|
981
|
|
|
|
|
1444
|
my $type = $tree->[0]; |
230
|
981
|
100
|
|
|
|
1733
|
if ($type eq 'tag') { |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Start tag |
233
|
434
|
|
|
|
|
673
|
my $tag = $tree->[1]; |
234
|
434
|
|
|
|
|
694
|
my $result = "<$tag"; |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# Attributes |
237
|
434
|
|
|
|
|
499
|
for my $key (sort keys %{$tree->[2]}) { |
|
434
|
|
|
|
|
1072
|
|
238
|
64
|
|
|
|
|
121
|
my $value = $tree->[2]{$key}; |
239
|
64
|
100
|
50
|
|
|
157
|
$result .= $xml ? qq{ $key="$key"} : " $key" and next |
|
|
100
|
|
|
|
|
|
240
|
|
|
|
|
|
|
unless defined $value; |
241
|
54
|
|
|
|
|
168
|
$result .= qq{ $key="} . html_escape($value) . '"'; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# No children |
245
|
434
|
100
|
|
|
|
1105
|
return $xml ? "$result />" : $EMPTY{$tag} ? "$result>" : "$result>$tag>" |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
246
|
|
|
|
|
|
|
unless $tree->[4]; |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Children |
249
|
2
|
|
|
2
|
|
18
|
no warnings 'recursion'; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
1111
|
|
250
|
394
|
|
|
|
|
715
|
$result .= '>' . join '', map { _render($_, $xml) } @$tree[4 .. $#$tree]; |
|
653
|
|
|
|
|
1226
|
|
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# End tag |
253
|
394
|
|
|
|
|
2146
|
return "$result$tag>"; |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# Text (escaped) |
257
|
547
|
100
|
|
|
|
1276
|
return html_escape($tree->[1]) if $type eq 'text'; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# Raw text |
260
|
144
|
100
|
|
|
|
269
|
return $tree->[1] if $type eq 'raw'; |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
# Root |
263
|
138
|
100
|
|
|
|
496
|
return join '', map { _render($_, $xml) } @$tree[1 .. $#$tree] |
|
175
|
|
|
|
|
348
|
|
264
|
|
|
|
|
|
|
if $type eq 'root'; |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# DOCTYPE |
267
|
14
|
100
|
|
|
|
62
|
return '[1] . '>' if $type eq 'doctype'; |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Comment |
270
|
10
|
100
|
|
|
|
47
|
return '' if $type eq 'comment'; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# CDATA |
273
|
6
|
100
|
|
|
|
34
|
return '[1] . ']]>' if $type eq 'cdata'; |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# Processing instruction |
276
|
3
|
50
|
|
|
|
28
|
return '' . $tree->[1] . '?>' if $type eq 'pi'; |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
# Everything else |
279
|
0
|
|
|
|
|
0
|
return ''; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub _start { |
283
|
779
|
|
|
779
|
|
1387
|
my ($start, $attrs, $xml, $current) = @_; |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
# Autoclose optional HTML elements |
286
|
779
|
100
|
100
|
|
|
2300
|
if (!$xml && $$current->[0] ne 'root') { |
287
|
491
|
100
|
|
|
|
1299
|
if (my $end = $END{$start}) { _end($end, 0, $current) } |
|
126
|
100
|
|
|
|
234
|
|
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
elsif (my $close = $CLOSE{$start}) { |
290
|
123
|
|
|
|
|
261
|
my ($allowed, $scope) = @$close; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# Close allowed parent elements in scope |
293
|
123
|
|
|
|
|
182
|
my $parent = $$current; |
294
|
123
|
|
66
|
|
|
520
|
while ($parent->[0] ne 'root' && !$scope->{$parent->[1]}) { |
295
|
119
|
100
|
|
|
|
288
|
_end($parent->[1], 0, $current) if $allowed->{$parent->[1]}; |
296
|
119
|
|
|
|
|
373
|
$parent = $parent->[3]; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
# New tag |
302
|
779
|
|
|
|
|
2347
|
push @$$current, my $new = ['tag', $start, $attrs, $$current]; |
303
|
779
|
|
|
|
|
2211
|
weaken $new->[3]; |
304
|
779
|
|
|
|
|
1185
|
$$current = $new; |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
sub _tag { |
308
|
12
|
|
|
12
|
|
23
|
my $tree = ['tag', shift, undef, undef]; |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# Content |
311
|
12
|
100
|
|
|
|
44
|
push @$tree, ref $_[-1] eq 'CODE' ? ['raw', pop->()] : ['text', pop] |
|
|
100
|
|
|
|
|
|
312
|
|
|
|
|
|
|
if @_ % 2; |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# Attributes |
315
|
12
|
|
|
|
|
32
|
my $attrs = $tree->[2] = {@_}; |
316
|
12
|
100
|
66
|
|
|
45
|
return $tree unless exists $attrs->{data} && ref $attrs->{data} eq 'HASH'; |
317
|
1
|
|
|
|
|
3
|
my $data = delete $attrs->{data}; |
318
|
1
|
|
|
|
|
4
|
@$attrs{map { y/_/-/; lc "data-$_" } keys %$data} = values %$data; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
6
|
|
319
|
|
|
|
|
|
|
|
320
|
1
|
|
|
|
|
4
|
return $tree; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
1; |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=for Pod::Coverage *EVERYTHING* |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=cut |