| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Text::HTML::Turndown::Node 0.12; |
|
2
|
6
|
|
|
6
|
|
117
|
use 5.020; |
|
|
6
|
|
|
|
|
23
|
|
|
3
|
6
|
|
|
6
|
|
34
|
use Moo; |
|
|
6
|
|
|
|
|
25
|
|
|
|
6
|
|
|
|
|
62
|
|
|
4
|
6
|
|
|
6
|
|
2714
|
use experimental 'signatures'; |
|
|
6
|
|
|
|
|
15
|
|
|
|
6
|
|
|
|
|
50
|
|
|
5
|
6
|
|
|
6
|
|
1266
|
use stable 'postderef'; |
|
|
6
|
|
|
|
|
14
|
|
|
|
6
|
|
|
|
|
58
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our @blockElements = ( |
|
8
|
|
|
|
|
|
|
'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS', |
|
9
|
|
|
|
|
|
|
'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', |
|
10
|
|
|
|
|
|
|
'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', |
|
11
|
|
|
|
|
|
|
'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES', |
|
12
|
|
|
|
|
|
|
'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD', |
|
13
|
|
|
|
|
|
|
'TFOOT', 'TH', 'THEAD', 'TR', 'UL' |
|
14
|
|
|
|
|
|
|
); |
|
15
|
|
|
|
|
|
|
our %blockElements = map { $_ => 1, lc $_ => 1 } @blockElements; |
|
16
|
|
|
|
|
|
|
|
|
17
|
3246
|
|
|
3246
|
|
4307
|
sub _isBlock ($self) { |
|
|
3246
|
|
|
|
|
4655
|
|
|
|
3246
|
|
|
|
|
3982
|
|
|
18
|
3246
|
|
|
|
|
27503
|
$blockElements{ $self->nodeName } |
|
19
|
|
|
|
|
|
|
}; |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
our @voidElements = ( |
|
22
|
|
|
|
|
|
|
'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', |
|
23
|
|
|
|
|
|
|
'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR' |
|
24
|
|
|
|
|
|
|
); |
|
25
|
|
|
|
|
|
|
our %voidElements = map { $_ => 1, lc $_ => 1 } @voidElements; |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
has '_node' => ( |
|
29
|
|
|
|
|
|
|
is => 'ro', |
|
30
|
|
|
|
|
|
|
required => 1, |
|
31
|
|
|
|
|
|
|
handles => [qw[ |
|
32
|
|
|
|
|
|
|
parentNode |
|
33
|
|
|
|
|
|
|
firstChild |
|
34
|
|
|
|
|
|
|
previousSibling |
|
35
|
|
|
|
|
|
|
nextSibling |
|
36
|
|
|
|
|
|
|
childNodes |
|
37
|
|
|
|
|
|
|
lastChild |
|
38
|
|
|
|
|
|
|
nodeName |
|
39
|
|
|
|
|
|
|
nodeValue |
|
40
|
|
|
|
|
|
|
nodeType |
|
41
|
|
|
|
|
|
|
textContent |
|
42
|
|
|
|
|
|
|
getAttribute |
|
43
|
|
|
|
|
|
|
isEqual |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
toString |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
find |
|
48
|
|
|
|
|
|
|
]], |
|
49
|
|
|
|
|
|
|
); |
|
50
|
|
|
|
|
|
|
|
|
51
|
1221
|
|
|
1221
|
|
1667
|
sub _isVoid( $self ) { |
|
|
1221
|
|
|
|
|
1810
|
|
|
|
1221
|
|
|
|
|
1518
|
|
|
52
|
1221
|
|
|
|
|
7701
|
$voidElements{ $self->nodeName } |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
|
|
55
|
1078
|
|
|
1078
|
|
1485
|
sub _hasVoid( $self ) { |
|
|
1078
|
|
|
|
|
1471
|
|
|
|
1078
|
|
|
|
|
1263
|
|
|
56
|
1078
|
|
|
|
|
3681
|
return _has($self, \%voidElements) |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
has ['isVoid', 'hasVoid', 'isBlock', 'isMeaningfulWhenBlank', 'hasMeaningfulWhenBlank', |
|
60
|
|
|
|
|
|
|
'isCode'] => ( |
|
61
|
|
|
|
|
|
|
is => 'ro', |
|
62
|
|
|
|
|
|
|
required => 1, |
|
63
|
|
|
|
|
|
|
); |
|
64
|
|
|
|
|
|
|
|
|
65
|
4
|
|
|
4
|
0
|
64
|
sub className( $self ) { |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
7
|
|
|
66
|
4
|
|
|
|
|
85
|
$self->getAttribute('class'); |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
|
|
69
|
4715
|
|
|
4715
|
|
6211
|
sub _isCode( $self ) { |
|
|
4715
|
|
|
|
|
6320
|
|
|
|
4715
|
|
|
|
|
5927
|
|
|
70
|
4715
|
100
|
|
|
|
17390
|
return 1 if uc $self->nodeName eq 'CODE'; |
|
71
|
4644
|
|
|
|
|
13743
|
my $p = $self->parentNode; |
|
72
|
4644
|
100
|
66
|
|
|
17693
|
if( $p and $p->can('nodeName')) { |
|
73
|
3637
|
|
|
|
|
32087
|
return _isCode($self->parentNode) |
|
74
|
|
|
|
|
|
|
}; |
|
75
|
|
|
|
|
|
|
} |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
our @meaningfulWhenBlankElements = ( |
|
78
|
|
|
|
|
|
|
'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT', |
|
79
|
|
|
|
|
|
|
'AUDIO', 'VIDEO' |
|
80
|
|
|
|
|
|
|
); |
|
81
|
|
|
|
|
|
|
our %meaningfulWhenBlankElements = map { $_ => 1, lc $_ => 1 } @meaningfulWhenBlankElements; |
|
82
|
|
|
|
|
|
|
|
|
83
|
1078
|
|
|
1078
|
|
1525
|
sub _isMeaningfulWhenBlank( $self ) { |
|
|
1078
|
|
|
|
|
1524
|
|
|
|
1078
|
|
|
|
|
1363
|
|
|
84
|
1078
|
|
|
|
|
5439
|
$meaningfulWhenBlankElements{ $self->nodeName } |
|
85
|
|
|
|
|
|
|
} |
|
86
|
|
|
|
|
|
|
|
|
87
|
1078
|
|
|
1078
|
|
1445
|
sub _hasMeaningfulWhenBlank( $self ) { |
|
|
1078
|
|
|
|
|
1378
|
|
|
|
1078
|
|
|
|
|
1422
|
|
|
88
|
1078
|
|
|
|
|
2170
|
_has( $self, \%meaningfulWhenBlankElements ) |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
2156
|
|
|
2156
|
|
3067
|
sub _has( $self, $nodeNames ) { |
|
|
2156
|
|
|
|
|
2784
|
|
|
|
2156
|
|
|
|
|
2931
|
|
|
|
2156
|
|
|
|
|
2817
|
|
|
92
|
2156
|
100
|
|
|
|
10710
|
return if ! $self->can('getElementsByTagName'); |
|
93
|
1514
|
|
|
|
|
21917
|
for my $tag (sort keys $nodeNames->%*) { |
|
94
|
39451
|
100
|
|
|
|
1564272
|
return 1 if $self->getElementsByTagName($tag) |
|
95
|
|
|
|
|
|
|
} |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
|
|
98
|
54
|
|
|
54
|
0
|
1096
|
sub firstNonBlankChild( $self ) { |
|
|
54
|
|
|
|
|
78
|
|
|
|
54
|
|
|
|
|
69
|
|
|
99
|
54
|
|
|
|
|
223
|
return ([$self->_node->nonBlankChildNodes]->[0]); |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
|
|
102
|
757
|
|
|
757
|
0
|
1132
|
sub isBlank( $self ) { |
|
|
757
|
|
|
|
|
1044
|
|
|
|
757
|
|
|
|
|
983
|
|
|
103
|
757
|
100
|
100
|
|
|
14951
|
!$self->isVoid |
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
104
|
|
|
|
|
|
|
&& !$self->isMeaningfulWhenBlank |
|
105
|
|
|
|
|
|
|
&& $self->textContent =~ /^\s*$/ |
|
106
|
|
|
|
|
|
|
&& !$self->hasVoid |
|
107
|
|
|
|
|
|
|
&& !$self->hasMeaningfulWhenBlank() |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
around BUILDARGS => sub( $orig, $class, %args ) { |
|
111
|
|
|
|
|
|
|
my $node = $args{ _node }; |
|
112
|
|
|
|
|
|
|
if( ! exists $args{ isVoid } ) { |
|
113
|
|
|
|
|
|
|
$args{ isVoid } = _isVoid( $node ); |
|
114
|
|
|
|
|
|
|
}; |
|
115
|
|
|
|
|
|
|
if( ! exists $args{ hasVoid } ) { |
|
116
|
|
|
|
|
|
|
$args{ hasVoid } = _hasVoid( $node ); |
|
117
|
|
|
|
|
|
|
}; |
|
118
|
|
|
|
|
|
|
if( ! exists $args{ isMeaningfulWhenBlank } ) { |
|
119
|
|
|
|
|
|
|
$args{ isMeaningfulWhenBlank } = _isMeaningfulWhenBlank( $node ); |
|
120
|
|
|
|
|
|
|
}; |
|
121
|
|
|
|
|
|
|
if( ! exists $args{ hasMeaningfulWhenBlank } ) { |
|
122
|
|
|
|
|
|
|
$args{ hasMeaningfulWhenBlank } = _hasMeaningfulWhenBlank( $node ); |
|
123
|
|
|
|
|
|
|
}; |
|
124
|
|
|
|
|
|
|
if( ! exists $args{ isCode } ) { |
|
125
|
|
|
|
|
|
|
$args{ isCode } = _isCode( $node ); |
|
126
|
|
|
|
|
|
|
}; |
|
127
|
|
|
|
|
|
|
if( ! exists $args{ isBlock } ) { |
|
128
|
|
|
|
|
|
|
$args{ isBlock } = _isBlock( $node ); |
|
129
|
|
|
|
|
|
|
}; |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
return $class->$orig(\%args); |
|
132
|
|
|
|
|
|
|
}; |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
|
|
135
|
757
|
|
|
757
|
0
|
5917
|
sub flankingWhitespace( $node, $options ) { |
|
|
757
|
|
|
|
|
1203
|
|
|
|
757
|
|
|
|
|
997
|
|
|
|
757
|
|
|
|
|
993
|
|
|
136
|
757
|
100
|
100
|
|
|
1646
|
if( _isBlock($node) || ($options->{ preformattedCode } && isCode($node) )) { |
|
|
|
|
100
|
|
|
|
|
|
137
|
651
|
|
|
|
|
22572
|
return { leading => '', trailing => '' }; |
|
138
|
|
|
|
|
|
|
} |
|
139
|
|
|
|
|
|
|
|
|
140
|
106
|
|
|
|
|
5264
|
my $edges = edgeWhitespace( $node->textContent ); |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# abandon leading ASCII WS if left-flanked by ASCII WS |
|
143
|
106
|
50
|
66
|
|
|
403
|
if ($edges->{leadingAscii} && isFlankedByWhitespace('left', $node, $options)) { |
|
144
|
0
|
|
|
|
|
0
|
$edges->{leading} = $edges->{leadingNonAscii}; |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# abandon trailing ASCII WS if right-flanked by ASCII WS |
|
148
|
106
|
50
|
66
|
|
|
350
|
if ($edges->{trailingAscii} && isFlankedByWhitespace('right', $node, $options)) { |
|
149
|
|
|
|
|
|
|
$edges->{trailing} = $edges->{trailingNonAscii} |
|
150
|
0
|
|
|
|
|
0
|
} |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
return { leading => $edges->{leading}, trailing => $edges->{trailing}, } |
|
153
|
106
|
|
|
|
|
668
|
} |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
|
|
156
|
106
|
|
|
106
|
0
|
2869
|
sub edgeWhitespace ($string) { |
|
|
106
|
|
|
|
|
217
|
|
|
|
106
|
|
|
|
|
161
|
|
|
157
|
106
|
|
|
|
|
766
|
$string =~ /^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/; |
|
158
|
|
|
|
|
|
|
return { |
|
159
|
106
|
|
|
|
|
1250
|
leading => $1, # whole string for whitespace-only strings |
|
160
|
|
|
|
|
|
|
leadingAscii => $2, |
|
161
|
|
|
|
|
|
|
leadingNonAscii => $3, |
|
162
|
|
|
|
|
|
|
trailing => $4, # empty for whitespace-only strings |
|
163
|
|
|
|
|
|
|
trailingNonAscii => $5, |
|
164
|
|
|
|
|
|
|
trailingAscii => $6, |
|
165
|
|
|
|
|
|
|
} |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
16
|
|
|
16
|
0
|
27
|
sub isFlankedByWhitespace( $side, $node, $options) { |
|
|
16
|
|
|
|
|
23
|
|
|
|
16
|
|
|
|
|
30
|
|
|
|
16
|
|
|
|
|
22
|
|
|
|
16
|
|
|
|
|
22
|
|
|
169
|
16
|
|
|
|
|
40
|
my $sibling; |
|
170
|
|
|
|
|
|
|
my $regExp; |
|
171
|
16
|
|
|
|
|
0
|
my $isFlanked; |
|
172
|
|
|
|
|
|
|
|
|
173
|
16
|
100
|
|
|
|
36
|
if ($side eq 'left') { |
|
174
|
7
|
|
|
|
|
123
|
$sibling = $node->previousSibling; |
|
175
|
7
|
|
|
|
|
188
|
$regExp = qr/ $/; |
|
176
|
|
|
|
|
|
|
} else { |
|
177
|
9
|
|
|
|
|
133
|
$sibling = $node->nextSibling; |
|
178
|
9
|
|
|
|
|
237
|
$regExp = qr/^ /; |
|
179
|
|
|
|
|
|
|
} |
|
180
|
|
|
|
|
|
|
|
|
181
|
16
|
100
|
|
|
|
56
|
if ($sibling) { |
|
182
|
10
|
100
|
66
|
|
|
94
|
if ($sibling->nodeType == 3) { |
|
|
|
50
|
33
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
183
|
6
|
|
|
|
|
43
|
$isFlanked = $sibling->nodeValue =~ /$regExp/; |
|
184
|
|
|
|
|
|
|
} elsif ($options->{preformattedCode} && $sibling->nodeName eq 'CODE') { |
|
185
|
0
|
|
|
|
|
0
|
$isFlanked = undef; |
|
186
|
|
|
|
|
|
|
} elsif ($sibling->nodeType == 1 && !_isBlock($sibling)) { |
|
187
|
4
|
|
|
|
|
40
|
$isFlanked = $sibling->textContent =~ /$regExp/; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
} |
|
190
|
16
|
|
|
|
|
79
|
return $isFlanked |
|
191
|
|
|
|
|
|
|
} |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
1; |
|
194
|
|
|
|
|
|
|
=head1 REPOSITORY |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
The public repository of this module is |
|
197
|
|
|
|
|
|
|
L. |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head1 SUPPORT |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
The public support forum of this module is L. |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=head1 BUG TRACKER |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
Please report bugs in this module via the Github bug queue at |
|
206
|
|
|
|
|
|
|
L |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head1 AUTHOR |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Max Maischein C |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head1 COPYRIGHT (c) |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Copyright 2025- by Max Maischein C. |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=head1 LICENSE |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
This module is released under the Artistic License 2.0. |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=cut |