line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::DOM::CSS; |
2
|
61
|
|
|
61
|
|
423
|
use Mojo::Base -base; |
|
61
|
|
|
|
|
161
|
|
|
61
|
|
|
|
|
419
|
|
3
|
|
|
|
|
|
|
|
4
|
61
|
|
|
61
|
|
430
|
use Carp qw(croak); |
|
61
|
|
|
|
|
156
|
|
|
61
|
|
|
|
|
3341
|
|
5
|
61
|
|
|
61
|
|
439
|
use Mojo::Util qw(dumper trim); |
|
61
|
|
|
|
|
215
|
|
|
61
|
|
|
|
|
4537
|
|
6
|
|
|
|
|
|
|
|
7
|
61
|
|
50
|
61
|
|
453
|
use constant DEBUG => $ENV{MOJO_DOM_CSS_DEBUG} || 0; |
|
61
|
|
|
|
|
174
|
|
|
61
|
|
|
|
|
255689
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
has 'tree'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
my $ESCAPE_RE = qr/\\[^0-9a-fA-F]|\\[0-9a-fA-F]{1,6}/; |
12
|
|
|
|
|
|
|
my $ATTR_RE = qr/ |
13
|
|
|
|
|
|
|
\[ |
14
|
|
|
|
|
|
|
((?:$ESCAPE_RE|[\w\-])+) # Key |
15
|
|
|
|
|
|
|
(?: |
16
|
|
|
|
|
|
|
(\W)?= # Operator |
17
|
|
|
|
|
|
|
(?:"((?:\\"|[^"])*)"|'((?:\\'|[^'])*)'|([^\]]+?)) # Value |
18
|
|
|
|
|
|
|
(?:\s+(?:(i|I)|s|S))? # Case-sensitivity |
19
|
|
|
|
|
|
|
)? |
20
|
|
|
|
|
|
|
\] |
21
|
|
|
|
|
|
|
/x; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
sub matches { |
24
|
44
|
|
|
44
|
1
|
112
|
my $tree = shift->tree; |
25
|
44
|
100
|
|
|
|
145
|
return $tree->[0] ne 'tag' ? undef : _match(_compile(@_), $tree, $tree, _root($tree)); |
26
|
|
|
|
|
|
|
} |
27
|
|
|
|
|
|
|
|
28
|
444
|
|
|
444
|
1
|
1185
|
sub select { _select(0, shift->tree, _compile(@_)) } |
29
|
762
|
|
|
762
|
1
|
2107
|
sub select_one { _select(1, shift->tree, _compile(@_)) } |
30
|
|
|
|
|
|
|
|
31
|
47
|
100
|
|
47
|
|
68
|
sub _absolutize { [map { _is_scoped($_) ? $_ : [[['pc', 'scope']], ' ', @$_] } @{shift()}] } |
|
50
|
|
|
|
|
82
|
|
|
47
|
|
|
|
|
81
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub _ancestor { |
34
|
1529
|
|
|
1529
|
|
2945
|
my ($selectors, $current, $tree, $scope, $one, $pos) = @_; |
35
|
|
|
|
|
|
|
|
36
|
1529
|
|
100
|
|
|
8024
|
while ($current ne $scope && $current->[0] ne 'root' && ($current = $current->[3])) { |
|
|
|
66
|
|
|
|
|
37
|
1625
|
100
|
|
|
|
3001
|
return 1 if _combinator($selectors, $current, $tree, $scope, $pos); |
38
|
393
|
100
|
|
|
|
1193
|
return undef if $current eq $scope; |
39
|
269
|
100
|
|
|
|
968
|
last if $one; |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
173
|
|
|
|
|
561
|
return undef; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub _attr { |
46
|
1458
|
|
|
1458
|
|
2358
|
my ($name_re, $value_re, $current) = @_; |
47
|
|
|
|
|
|
|
|
48
|
1458
|
|
|
|
|
2017
|
my $attrs = $current->[2]; |
49
|
1458
|
|
|
|
|
3522
|
for my $name (keys %$attrs) { |
50
|
1478
|
|
|
|
|
2744
|
my $value = $attrs->{$name}; |
51
|
1478
|
100
|
100
|
|
|
8406
|
next if $name !~ $name_re || (!defined $value && defined $value_re); |
|
|
|
100
|
|
|
|
|
52
|
761
|
100
|
100
|
|
|
6083
|
return 1 if !(defined $value && defined $value_re) || $value =~ $value_re; |
|
|
|
100
|
|
|
|
|
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
984
|
|
|
|
|
4344
|
return undef; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub _combinator { |
59
|
9124
|
|
|
9124
|
|
15015
|
my ($selectors, $current, $tree, $scope, $pos) = @_; |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# Selector |
62
|
9124
|
100
|
|
|
|
17789
|
return undef unless my $c = $selectors->[$pos]; |
63
|
9119
|
100
|
|
|
|
17286
|
if (ref $c) { |
64
|
9116
|
100
|
|
|
|
13657
|
return undef unless _selector($c, $current, $tree, $scope); |
65
|
3499
|
100
|
|
|
|
14108
|
return 1 unless $c = $selectors->[++$pos]; |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# ">" (parent only) |
69
|
1661
|
100
|
|
|
|
3966
|
return _ancestor($selectors, $current, $tree, $scope, 1, ++$pos) if $c eq '>'; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# "~" (preceding siblings) |
72
|
637
|
100
|
|
|
|
1205
|
return _sibling($selectors, $current, $tree, $scope, 0, ++$pos) if $c eq '~'; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# "+" (immediately preceding siblings) |
75
|
572
|
100
|
|
|
|
1142
|
return _sibling($selectors, $current, $tree, $scope, 1, ++$pos) if $c eq '+'; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# " " (ancestor) |
78
|
505
|
|
|
|
|
996
|
return _ancestor($selectors, $current, $tree, $scope, 0, ++$pos); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub _compile { |
82
|
1304
|
|
|
1304
|
|
5562
|
my ($css, %ns) = (trim('' . shift), @_); |
83
|
|
|
|
|
|
|
|
84
|
1304
|
|
|
|
|
3159
|
my $group = [[]]; |
85
|
1304
|
|
|
|
|
3329
|
while (my $selectors = $group->[-1]) { |
86
|
4504
|
100
|
100
|
|
|
16135
|
push @$selectors, [] unless @$selectors && ref $selectors->[-1]; |
87
|
4504
|
|
|
|
|
6568
|
my $last = $selectors->[-1]; |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# Separator |
90
|
4504
|
100
|
|
|
|
30892
|
if ($css =~ /\G\s*,\s*/gc) { push @$group, [] } |
|
14
|
100
|
|
|
|
44
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# Combinator |
93
|
|
|
|
|
|
|
elsif ($css =~ /\G\s*([ >+~])\s*/gc) { |
94
|
742
|
100
|
|
|
|
1776
|
push @$last, ['pc', 'scope'] unless @$last; |
95
|
742
|
|
|
|
|
2395
|
push @$selectors, $1; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# Class or ID |
99
|
|
|
|
|
|
|
elsif ($css =~ /\G([.#])((?:$ESCAPE_RE\s|\\.|[^,.#:[ >~+])+)/gco) { |
100
|
188
|
100
|
|
|
|
746
|
my ($name, $op) = $1 eq '.' ? ('class', '~') : ('id', ''); |
101
|
188
|
|
|
|
|
453
|
push @$last, ['attr', _name($name), _value($op, $2)]; |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# Attributes |
105
|
283
|
|
100
|
|
|
689
|
elsif ($css =~ /\G$ATTR_RE/gco) { push @$last, ['attr', _name($1), _value($2 // '', $3 // $4 // $5, $6)] } |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Pseudo-class |
108
|
|
|
|
|
|
|
elsif ($css =~ /\G:([\w\-]+)(?:\(((?:\([^)]+\)|[^)])+)\))?/gcs) { |
109
|
302
|
|
|
|
|
1187
|
my ($name, $args) = (lc $1, $2); |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# ":text" (raw text) |
112
|
302
|
100
|
|
|
|
983
|
$args = [$args =~ m!^/(.+)/$! ? qr/$1/ : qr/\Q$args\E/i] if $name eq 'text'; |
|
|
100
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
# ":is" and ":not" (contains more selectors) |
115
|
302
|
100
|
100
|
|
|
1534
|
$args = _compile($args, %ns) if $name eq 'has' || $name eq 'is' || $name eq 'not'; |
|
|
|
100
|
|
|
|
|
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# ":nth-*" (with An+B notation) |
118
|
302
|
100
|
|
|
|
954
|
$args = _equation($args) if $name =~ /^nth-/; |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# ":first-*", ":last-*" (rewrite to ":nth-(last-)*") |
121
|
302
|
100
|
|
|
|
901
|
($name, $args) = ("nth-$+", [0, 1]) if $name =~ /^(?:first-(.+)|(last-.+))$/; |
122
|
|
|
|
|
|
|
|
123
|
302
|
|
|
|
|
1116
|
push @$last, ['pc', $name, $args]; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Tag |
127
|
|
|
|
|
|
|
elsif ($css =~ /\G((?:$ESCAPE_RE\s|\\.|[^,.#:[ >~+])+)/gco) { |
128
|
1672
|
100
|
100
|
|
|
6887
|
my $alias = (my $name = $1) =~ s/^([^|]*)\|// && $1 ne '*' ? $1 : undef; |
129
|
1672
|
100
|
100
|
|
|
3494
|
my $ns = length $alias ? $ns{$alias} // return [['invalid']] : $alias; |
130
|
1671
|
100
|
|
|
|
4163
|
push @$last, ['tag', $name eq '*' ? undef : _name($name), _unescape($ns)]; |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
|
133
|
1303
|
100
|
|
|
|
3751
|
else { pos $css < length $css ? croak "Unknown CSS selector: $css" : last } |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
1301
|
|
|
|
|
1625
|
warn qq{-- CSS Selector ($css)\n@{[dumper $group]}} if DEBUG; |
137
|
1301
|
|
|
|
|
4275
|
return $group; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub _equation { |
141
|
117
|
100
|
|
117
|
|
311
|
return [0, 0] unless my $equation = shift; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# "even" |
144
|
115
|
100
|
|
|
|
309
|
return [2, 0] if $equation =~ /^\s*even\s*$/i; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# "odd" |
147
|
106
|
100
|
|
|
|
312
|
return [2, 1] if $equation =~ /^\s*odd\s*$/i; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# "4", "+4" or "-4" |
150
|
94
|
100
|
|
|
|
470
|
return [0, $1] if $equation =~ /^\s*((?:\+|-)?\d+)\s*$/; |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# "n", "4n", "+4n", "-4n", "n+1", "4n-1", "+4n-1" (and other variations) |
153
|
52
|
100
|
|
|
|
264
|
return [0, 0] unless $equation =~ /^\s*((?:\+|-)?(?:\d+)?)?n\s*((?:\+|-)\s*\d+)?\s*$/i; |
154
|
51
|
100
|
100
|
|
|
446
|
return [$1 eq '-' ? -1 : !length $1 ? 1 : $1, join('', split(' ', $2 // 0))]; |
|
|
100
|
|
|
|
|
|
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
sub _is_scoped { |
158
|
1362
|
|
|
1362
|
|
2026
|
my $selector = shift; |
159
|
|
|
|
|
|
|
|
160
|
1362
|
100
|
|
|
|
2203
|
for my $pc (grep { $_->[0] eq 'pc' } map { ref $_ ? @$_ : () } @$selector) { |
|
2552
|
|
|
|
|
6567
|
|
|
2970
|
|
|
|
|
6632
|
|
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# Selector with ":scope" |
163
|
371
|
100
|
|
|
|
1034
|
return 1 if $pc->[1] eq 'scope'; |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Argument of functional pseudo-class with ":scope" |
166
|
277
|
100
|
100
|
|
|
1355
|
return 1 if ($pc->[1] eq 'has' || $pc->[1] eq 'is' || $pc->[1] eq 'not') && grep { _is_scoped($_) } @{$pc->[2]}; |
|
69
|
|
100
|
|
|
140
|
|
|
65
|
|
|
|
|
137
|
|
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
1258
|
|
|
|
|
3434
|
return undef; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
sub _match { |
173
|
7231
|
|
|
7231
|
|
12547
|
my ($group, $current, $tree, $scope) = @_; |
174
|
7231
|
|
100
|
|
|
18990
|
_combinator([reverse @$_], $current, $tree, $scope, 0) and return 1 for @$group; |
175
|
5395
|
|
|
|
|
16564
|
return undef; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
2119
|
|
|
2119
|
|
3171
|
sub _name {qr/(?:^|:)\Q@{[_unescape(shift)]}\E$/} |
|
2119
|
|
|
|
|
4003
|
|
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
sub _namespace { |
181
|
77
|
|
|
77
|
|
149
|
my ($ns, $current) = @_; |
182
|
|
|
|
|
|
|
|
183
|
77
|
100
|
|
|
|
198
|
my $attr = $current->[1] =~ /^([^:]+):/ ? "xmlns:$1" : 'xmlns'; |
184
|
77
|
|
|
|
|
155
|
while ($current) { |
185
|
121
|
100
|
|
|
|
236
|
last if $current->[0] eq 'root'; |
186
|
117
|
100
|
|
|
|
502
|
return $current->[2]{$attr} eq $ns if exists $current->[2]{$attr}; |
187
|
|
|
|
|
|
|
|
188
|
44
|
|
|
|
|
89
|
$current = $current->[3]; |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Failing to match yields true if searching for no namespace, false otherwise |
192
|
4
|
|
|
|
|
25
|
return !length $ns; |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub _pc { |
196
|
1793
|
|
|
1793
|
|
3059
|
my ($class, $args, $current, $tree, $scope) = @_; |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# ":scope" (root can only be a :scope) |
199
|
1793
|
100
|
|
|
|
3680
|
return $current eq $scope if $class eq 'scope'; |
200
|
1671
|
100
|
|
|
|
2960
|
return undef if $current->[0] eq 'root'; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# ":checked" |
203
|
1666
|
100
|
100
|
|
|
3523
|
return exists $current->[2]{checked} || exists $current->[2]{selected} if $class eq 'checked'; |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
# ":not" |
206
|
1462
|
100
|
|
|
|
2554
|
return !_match($args, $current, $current, $scope) if $class eq 'not'; |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
# ":is" |
209
|
1283
|
100
|
|
|
|
2131
|
return !!_match($args, $current, $current, $scope) if $class eq 'is'; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# ":has" |
212
|
1270
|
100
|
|
|
|
2119
|
return !!_select(1, $current, $args) if $class eq 'has'; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# ":empty" |
215
|
1241
|
100
|
100
|
|
|
2160
|
return !grep { !($_->[0] eq 'comment' || $_->[0] eq 'pi') } @$current[4 .. $#$current] if $class eq 'empty'; |
|
58
|
|
|
|
|
211
|
|
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# ":root" |
218
|
1213
|
100
|
66
|
|
|
2237
|
return $current->[3] && $current->[3][0] eq 'root' if $class eq 'root'; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# ":text" |
221
|
1159
|
100
|
66
|
|
|
1985
|
return grep { ($_->[0] eq 'text' || $_->[0] eq 'raw') && $_->[1] =~ $args->[0] } @$current[4 .. $#$current] |
|
232
|
100
|
|
|
|
1309
|
|
222
|
|
|
|
|
|
|
if $class eq 'text'; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# ":any-link", ":link" and ":visited" |
225
|
1079
|
100
|
100
|
|
|
3910
|
if ($class eq 'any-link' || $class eq 'link' || $class eq 'visited') { |
|
|
|
100
|
|
|
|
|
226
|
39
|
100
|
66
|
|
|
202
|
return undef unless $current->[0] eq 'tag' && exists $current->[2]{href}; |
227
|
21
|
|
|
|
|
35
|
return !!grep { $current->[1] eq $_ } qw(a area link); |
|
63
|
|
|
|
|
171
|
|
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# ":only-child" or ":only-of-type" |
231
|
1040
|
100
|
100
|
|
|
2733
|
if ($class eq 'only-child' || $class eq 'only-of-type') { |
232
|
40
|
100
|
|
|
|
78
|
my $type = $class eq 'only-of-type' ? $current->[1] : undef; |
233
|
40
|
|
100
|
|
|
46
|
$_ ne $current and return undef for @{_siblings($current, $type)}; |
|
40
|
|
|
|
|
79
|
|
234
|
7
|
|
|
|
|
24
|
return 1; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# ":nth-child", ":nth-last-child", ":nth-of-type" or ":nth-last-of-type" |
238
|
1000
|
100
|
|
|
|
1917
|
if (ref $args) { |
239
|
992
|
100
|
100
|
|
|
2647
|
my $type = $class eq 'nth-of-type' || $class eq 'nth-last-of-type' ? $current->[1] : undef; |
240
|
992
|
|
|
|
|
1267
|
my @siblings = @{_siblings($current, $type)}; |
|
992
|
|
|
|
|
1505
|
|
241
|
992
|
|
|
|
|
1590
|
my $index; |
242
|
992
|
|
|
|
|
1990
|
for my $i (0 .. $#siblings) { |
243
|
3648
|
100
|
|
|
|
8122
|
$index = $i, last if $siblings[$i] eq $current; |
244
|
|
|
|
|
|
|
} |
245
|
992
|
100
|
100
|
|
|
2968
|
$index = $#siblings - $index if $class eq 'nth-last-child' || $class eq 'nth-last-of-type'; |
246
|
992
|
|
|
|
|
1340
|
$index++; |
247
|
|
|
|
|
|
|
|
248
|
992
|
|
|
|
|
1485
|
my $delta = $index - $args->[1]; |
249
|
992
|
100
|
|
|
|
2076
|
return 1 if $delta == 0; |
250
|
800
|
|
100
|
|
|
5563
|
return $args->[0] != 0 && ($delta < 0) == ($args->[0] < 0) && $delta % $args->[0] == 0; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# Everything else |
254
|
8
|
|
|
|
|
35
|
return undef; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
sub _root { |
258
|
90
|
|
|
90
|
|
147
|
my $tree = shift; |
259
|
90
|
|
|
|
|
373
|
$tree = $tree->[3] while $tree->[0] ne 'root'; |
260
|
90
|
|
|
|
|
226
|
return $tree; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sub _select { |
264
|
1233
|
|
|
1233
|
|
2580
|
my ($one, $scope, $group) = @_; |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Scoped selectors require the whole tree to be searched |
267
|
1233
|
|
|
|
|
1735
|
my $tree = $scope; |
268
|
1233
|
100
|
|
|
|
2472
|
($group, $tree) = (_absolutize($group), _root($scope)) if grep { _is_scoped($_) } @$group; |
|
1243
|
|
|
|
|
2406
|
|
269
|
|
|
|
|
|
|
|
270
|
1233
|
|
|
|
|
1849
|
my @results; |
271
|
1233
|
100
|
|
|
|
4707
|
my @queue = @$tree[($tree->[0] eq 'root' ? 1 : 4) .. $#$tree]; |
272
|
1233
|
|
|
|
|
3184
|
while (my $current = shift @queue) { |
273
|
17358
|
100
|
|
|
|
38677
|
next unless $current->[0] eq 'tag'; |
274
|
|
|
|
|
|
|
|
275
|
6996
|
|
|
|
|
15653
|
unshift @queue, @$current[4 .. $#$current]; |
276
|
6996
|
100
|
|
|
|
12236
|
next unless _match($group, $current, $tree, $scope); |
277
|
1757
|
100
|
|
|
|
6618
|
$one ? return $current : push @results, $current; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
530
|
100
|
|
|
|
3322
|
return $one ? undef : \@results; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub _selector { |
284
|
9116
|
|
|
9116
|
|
14261
|
my ($selector, $current, $tree, $scope) = @_; |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# The root might be the scope |
287
|
9116
|
|
|
|
|
13636
|
my $is_tag = $current->[0] eq 'tag'; |
288
|
9116
|
|
|
|
|
13005
|
for my $s (@$selector) { |
289
|
10355
|
|
|
|
|
13572
|
my $type = $s->[0]; |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
# Tag |
292
|
10355
|
100
|
100
|
|
|
32316
|
if ($is_tag && $type eq 'tag') { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
293
|
6989
|
100
|
100
|
|
|
47941
|
return undef if defined $s->[1] && $current->[1] !~ $s->[1]; |
294
|
3546
|
100
|
100
|
|
|
10309
|
return undef if defined $s->[2] && !_namespace($s->[2], $current); |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# Attribute |
298
|
1458
|
100
|
|
|
|
2631
|
elsif ($is_tag && $type eq 'attr') { return undef unless _attr(@$s[1, 2], $current) } |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# Pseudo-class |
301
|
1793
|
100
|
|
|
|
3375
|
elsif ($type eq 'pc') { return undef unless _pc(@$s[1, 2], $current, $tree, $scope) } |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# No match |
304
|
115
|
|
|
|
|
354
|
else { return undef } |
305
|
|
|
|
|
|
|
} |
306
|
|
|
|
|
|
|
|
307
|
3499
|
|
|
|
|
7303
|
return 1; |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
sub _sibling { |
311
|
132
|
|
|
132
|
|
267
|
my ($selectors, $current, $tree, $scope, $immediate, $pos) = @_; |
312
|
|
|
|
|
|
|
|
313
|
132
|
|
|
|
|
206
|
my $found; |
314
|
132
|
|
|
|
|
179
|
for my $sibling (@{_siblings($current)}) { |
|
132
|
|
|
|
|
234
|
|
315
|
326
|
100
|
|
|
|
1023
|
return $found if $sibling eq $current; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# "+" (immediately preceding sibling) |
318
|
224
|
100
|
|
|
|
425
|
if ($immediate) { $found = _combinator($selectors, $sibling, $tree, $scope, $pos) } |
|
125
|
|
|
|
|
226
|
|
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
# "~" (preceding sibling) |
321
|
99
|
100
|
|
|
|
169
|
else { return 1 if _combinator($selectors, $sibling, $tree, $scope, $pos) } |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
0
|
|
|
|
|
0
|
return undef; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub _siblings { |
328
|
1164
|
|
|
1164
|
|
1868
|
my ($current, $type) = @_; |
329
|
|
|
|
|
|
|
|
330
|
1164
|
|
|
|
|
1649
|
my $parent = $current->[3]; |
331
|
1164
|
100
|
|
|
|
3436
|
my @siblings = grep { $_->[0] eq 'tag' } @$parent[($parent->[0] eq 'root' ? 1 : 4) .. $#$parent]; |
|
15442
|
|
|
|
|
27537
|
|
332
|
1164
|
100
|
|
|
|
2831
|
@siblings = grep { $type eq $_->[1] } @siblings if defined $type; |
|
643
|
|
|
|
|
1136
|
|
333
|
|
|
|
|
|
|
|
334
|
1164
|
|
|
|
|
2886
|
return \@siblings; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub _unescape { |
338
|
4213
|
100
|
|
4213
|
|
14369
|
return undef unless defined(my $value = shift); |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# Remove escaped newlines |
341
|
2610
|
|
|
|
|
4383
|
$value =~ s/\\\n//g; |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# Unescape Unicode characters |
344
|
2610
|
|
|
|
|
3951
|
$value =~ s/\\([0-9a-fA-F]{1,6})\s?/pack 'U', hex $1/ge; |
|
35
|
|
|
|
|
207
|
|
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# Remove backslash |
347
|
2610
|
|
|
|
|
4243
|
$value =~ s/\\//g; |
348
|
|
|
|
|
|
|
|
349
|
2610
|
|
|
|
|
40197
|
return $value; |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
sub _value { |
353
|
471
|
|
|
471
|
|
1735
|
my ($op, $value, $insensitive) = @_; |
354
|
471
|
100
|
|
|
|
1216
|
return undef unless defined $value; |
355
|
423
|
100
|
|
|
|
938
|
$value = ($insensitive ? '(?i)' : '') . quotemeta _unescape($value); |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# "~=" (word) |
358
|
423
|
100
|
|
|
|
2352
|
return qr/(?:^|\s+)$value(?:\s+|$)/ if $op eq '~'; |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
# "|=" (hyphen-separated) |
361
|
324
|
100
|
|
|
|
770
|
return qr/^$value(?:-|$)/ if $op eq '|'; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# "*=" (contains) |
364
|
314
|
100
|
|
|
|
728
|
return qr/$value/ if $op eq '*'; |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# "^=" (begins with) |
367
|
302
|
100
|
|
|
|
870
|
return qr/^$value/ if $op eq '^'; |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# "$=" (ends with) |
370
|
270
|
100
|
|
|
|
693
|
return qr/$value$/ if $op eq '$'; |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
# Everything else |
373
|
239
|
|
|
|
|
2555
|
return qr/^$value$/; |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
1; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=encoding utf8 |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 NAME |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
Mojo::DOM::CSS - CSS selector engine |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=head1 SYNOPSIS |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
use Mojo::DOM::CSS; |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
# Select elements from DOM tree |
389
|
|
|
|
|
|
|
my $css = Mojo::DOM::CSS->new(tree => $tree); |
390
|
|
|
|
|
|
|
my $elements = $css->select('h1, h2, h3'); |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=head1 DESCRIPTION |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
L is the CSS selector engine used by L, based on the L
|
395
|
|
|
|
|
|
|
Standard|https://html.spec.whatwg.org> and L. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=head1 SELECTORS |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
All CSS selectors that make sense for a standalone parser are supported. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head2 * |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Any element. |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
my $all = $css->select('*'); |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=head2 E |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
An element of type C. |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
my $title = $css->select('title'); |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=head2 E[foo] |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
An C element with a C attribute. |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
my $links = $css->select('a[href]'); |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=head2 E[foo="bar"] |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
An C element whose C attribute value is exactly equal to C. |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
my $case_sensitive = $css->select('input[type="hidden"]'); |
424
|
|
|
|
|
|
|
my $case_sensitive = $css->select('input[type=hidden]'); |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=head2 E[foo="bar" i] |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
An C element whose C attribute value is exactly equal to any (ASCII-range) case-permutation of C. Note |
429
|
|
|
|
|
|
|
that this selector is B and might change without warning! |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
my $case_insensitive = $css->select('input[type="hidden" i]'); |
432
|
|
|
|
|
|
|
my $case_insensitive = $css->select('input[type=hidden i]'); |
433
|
|
|
|
|
|
|
my $case_insensitive = $css->select('input[class~="foo" i]'); |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
This selector is part of L, which is still a work in progress. |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=head2 E[foo="bar" s] |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
An C element whose C attribute value is exactly and case-sensitively equal to C. Note that this selector |
440
|
|
|
|
|
|
|
is B and might change without warning! |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
my $case_sensitive = $css->select('input[type="hidden" s]'); |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
This selector is part of L, which is still a work in progress. |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head2 E[foo~="bar"] |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
An C element whose C attribute value is a list of whitespace-separated values, one of which is exactly equal to |
449
|
|
|
|
|
|
|
C. |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
my $foo = $css->select('input[class~="foo"]'); |
452
|
|
|
|
|
|
|
my $foo = $css->select('input[class~=foo]'); |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=head2 E[foo^="bar"] |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
An C element whose C attribute value begins exactly with the string C. |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
my $begins_with = $css->select('input[name^="f"]'); |
459
|
|
|
|
|
|
|
my $begins_with = $css->select('input[name^=f]'); |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head2 E[foo$="bar"] |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
An C element whose C attribute value ends exactly with the string C. |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
my $ends_with = $css->select('input[name$="o"]'); |
466
|
|
|
|
|
|
|
my $ends_with = $css->select('input[name$=o]'); |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=head2 E[foo*="bar"] |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
An C element whose C attribute value contains the substring C. |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
my $contains = $css->select('input[name*="fo"]'); |
473
|
|
|
|
|
|
|
my $contains = $css->select('input[name*=fo]'); |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=head2 E[foo|="en"] |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
An C element whose C attribute has a hyphen-separated list of values beginning (from the left) with C. |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
my $english = $css->select('link[hreflang|=en]'); |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
=head2 E:root |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
An C element, root of the document. |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
my $root = $css->select(':root'); |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=head2 E:nth-child(n) |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
An C element, the C child of its parent. |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
my $third = $css->select('div:nth-child(3)'); |
492
|
|
|
|
|
|
|
my $odd = $css->select('div:nth-child(odd)'); |
493
|
|
|
|
|
|
|
my $even = $css->select('div:nth-child(even)'); |
494
|
|
|
|
|
|
|
my $top3 = $css->select('div:nth-child(-n+3)'); |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=head2 E:nth-last-child(n) |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
An C element, the C child of its parent, counting from the last one. |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
my $third = $css->select('div:nth-last-child(3)'); |
501
|
|
|
|
|
|
|
my $odd = $css->select('div:nth-last-child(odd)'); |
502
|
|
|
|
|
|
|
my $even = $css->select('div:nth-last-child(even)'); |
503
|
|
|
|
|
|
|
my $bottom3 = $css->select('div:nth-last-child(-n+3)'); |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head2 E:nth-of-type(n) |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
An C element, the C sibling of its type. |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
my $third = $css->select('div:nth-of-type(3)'); |
510
|
|
|
|
|
|
|
my $odd = $css->select('div:nth-of-type(odd)'); |
511
|
|
|
|
|
|
|
my $even = $css->select('div:nth-of-type(even)'); |
512
|
|
|
|
|
|
|
my $top3 = $css->select('div:nth-of-type(-n+3)'); |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=head2 E:nth-last-of-type(n) |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
An C element, the C sibling of its type, counting from the last one. |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
my $third = $css->select('div:nth-last-of-type(3)'); |
519
|
|
|
|
|
|
|
my $odd = $css->select('div:nth-last-of-type(odd)'); |
520
|
|
|
|
|
|
|
my $even = $css->select('div:nth-last-of-type(even)'); |
521
|
|
|
|
|
|
|
my $bottom3 = $css->select('div:nth-last-of-type(-n+3)'); |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
=head2 E:first-child |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
An C element, first child of its parent. |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
my $first = $css->select('div p:first-child'); |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=head2 E:last-child |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
An C element, last child of its parent. |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
my $last = $css->select('div p:last-child'); |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=head2 E:first-of-type |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
An C element, first sibling of its type. |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
my $first = $css->select('div p:first-of-type'); |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
=head2 E:last-of-type |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
An C element, last sibling of its type. |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
my $last = $css->select('div p:last-of-type'); |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
=head2 E:only-child |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
An C element, only child of its parent. |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
my $lonely = $css->select('div p:only-child'); |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
=head2 E:only-of-type |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
An C element, only sibling of its type. |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
my $lonely = $css->select('div p:only-of-type'); |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=head2 E:empty |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
An C element that has no children (including text nodes). |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
my $empty = $css->select(':empty'); |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=head2 E:any-link |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
Alias for L"E:link">. Note that this selector is B and might change without warning! This selector is |
568
|
|
|
|
|
|
|
part of L, which is still a work in progress. |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
=head2 E:link |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
An C element being the source anchor of a hyperlink of which the target is not yet visited (C<:link>) or already |
573
|
|
|
|
|
|
|
visited (C<:visited>). Note that L is not stateful, therefore C<:any-link>, C<:link> and C<:visited> |
574
|
|
|
|
|
|
|
yield exactly the same results. |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
my $links = $css->select(':any-link'); |
577
|
|
|
|
|
|
|
my $links = $css->select(':link'); |
578
|
|
|
|
|
|
|
my $links = $css->select(':visited'); |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=head2 E:visited |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
Alias for L"E:link">. |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
=head2 E:scope |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
An C element being a designated reference element. Note that this selector is B and might change |
587
|
|
|
|
|
|
|
without warning! |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
my $scoped = $css->select('a:not(:scope > a)'); |
590
|
|
|
|
|
|
|
my $scoped = $css->select('div :scope p'); |
591
|
|
|
|
|
|
|
my $scoped = $css->select('~ p'); |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
This selector is part of L, which is still a work in progress. |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=head2 E:checked |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
A user interface element C which is checked (for instance a radio-button or checkbox). |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
my $input = $css->select(':checked'); |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
=head2 E.warning |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
An C element whose class is "warning". |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
my $warning = $css->select('div.warning'); |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
=head2 E#myid |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
An C element with C equal to "myid". |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
my $foo = $css->select('div#foo'); |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
=head2 E:not(s1, s2) |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
An C element that does not match either compound selector C or compound selector C. Note that support for |
616
|
|
|
|
|
|
|
compound selectors is B and might change without warning! |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
my $others = $css->select('div p:not(:first-child, :last-child)'); |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
Support for compound selectors was added as part of L, which is |
621
|
|
|
|
|
|
|
still a work in progress. |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
=head2 E:is(s1, s2) |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
An C element that matches compound selector C and/or compound selector C. Note that this selector is |
626
|
|
|
|
|
|
|
B and might change without warning! |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
my $headers = $css->select(':is(section, article, aside, nav) h1'); |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
This selector is part of L, which is still a work in progress. |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=head2 E:has(rs1, rs2) |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
An C element, if either of the relative selectors C or C, when evaluated with C as the :scope elements, |
635
|
|
|
|
|
|
|
match an element. Note that this selector is B and might change without warning! |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
my $link = $css->select('a:has(> img)'); |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
This selector is part of L, which is still a work in progress. |
640
|
|
|
|
|
|
|
Also be aware that this feature is currently marked C, so there is a high chance that it will get removed |
641
|
|
|
|
|
|
|
completely. |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
=head2 E:text(string_or_regex) |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
An C element containing text content that substring matches C case-insensitively or that regex |
646
|
|
|
|
|
|
|
matches C. For regular expressions use the format C<:text(/.../)>. Note that this selector is |
647
|
|
|
|
|
|
|
B and might change without warning! |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
# Substring match |
650
|
|
|
|
|
|
|
my $login = $css->select(':text(Log in)'); |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
# Regex match |
653
|
|
|
|
|
|
|
my $login = $css->select(':text(/Log ?in/)'); |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
# Regex match (case-insensitive) |
656
|
|
|
|
|
|
|
my $login = $css->select(':text(/(?i:Log ?in)/)'); |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
This is a custom selector for L and not part of any spec. |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=head2 A|E |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
An C element that belongs to the namespace alias C from L
|
663
|
|
|
|
|
|
|
3|https://www.w3.org/TR/css-namespaces-3/>. Key/value pairs passed to selector methods are used to declare namespace |
664
|
|
|
|
|
|
|
aliases. |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
my $elem = $css->select('lq|elem', lq => 'http://example.com/q-markup'); |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
Using an empty alias searches for an element that belongs to no namespace. |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
my $div = $c->select('|div'); |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
=head2 E F |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
An C element descendant of an C element. |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
my $headlines = $css->select('div h1'); |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=head2 E E F |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
An C element child of an C element. |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
my $headlines = $css->select('html > body > div > h1'); |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
=head2 E + F |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
An C element immediately preceded by an C element. |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
my $second = $css->select('h1 + h2'); |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
=head2 E ~ F |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
An C element preceded by an C element. |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
my $second = $css->select('h1 ~ h2'); |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
=head2 E, F, G |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
Elements of type C, C and C. |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
my $headlines = $css->select('h1, h2, h3'); |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
=head2 E[foo=bar][bar=baz] |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
An C element whose attributes match all following attribute selectors. |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
my $links = $css->select('a[foo^=b][foo$=ar]'); |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
L implements the following attributes. |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
=head2 tree |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
my $tree = $css->tree; |
715
|
|
|
|
|
|
|
$css = $css->tree(['root']); |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
Document Object Model. Note that this structure should only be used very carefully since it is very dynamic. |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=head1 METHODS |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
L inherits all methods from L and implements the following new ones. |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
=head2 matches |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
my $bool = $css->matches('head > title'); |
726
|
|
|
|
|
|
|
my $bool = $css->matches('svg|line', svg => 'http://www.w3.org/2000/svg'); |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
Check if first node in L"tree"> matches the CSS selector. Trailing key/value pairs can be used to declare xml |
729
|
|
|
|
|
|
|
namespace aliases. |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=head2 select |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
my $results = $css->select('head > title'); |
734
|
|
|
|
|
|
|
my $results = $css->select('svg|line', svg => 'http://www.w3.org/2000/svg'); |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
Run CSS selector against L"tree">. Trailing key/value pairs can be used to declare xml namespace aliases. |
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
=head2 select_one |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
my $result = $css->select_one('head > title'); |
741
|
|
|
|
|
|
|
my $result = |
742
|
|
|
|
|
|
|
$css->select_one('svg|line', svg => 'http://www.w3.org/2000/svg'); |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
Run CSS selector against L"tree"> and stop as soon as the first node matched. Trailing key/value pairs can be used to |
745
|
|
|
|
|
|
|
declare xml namespace aliases. |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=head1 DEBUGGING |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
You can set the C environment variable to get some advanced diagnostics information printed to |
750
|
|
|
|
|
|
|
C. |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
MOJO_DOM_CSS_DEBUG=1 |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=head1 SEE ALSO |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
L, L, L. |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=cut |