| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Mojo::DOM::CSS; | 
| 2 | 61 |  |  | 61 |  | 470 | use Mojo::Base -base; | 
|  | 61 |  |  |  |  | 173 |  | 
|  | 61 |  |  |  |  | 450 |  | 
| 3 |  |  |  |  |  |  |  | 
| 4 | 61 |  |  | 61 |  | 455 | use Carp       qw(croak); | 
|  | 61 |  |  |  |  | 180 |  | 
|  | 61 |  |  |  |  | 3438 |  | 
| 5 | 61 |  |  | 61 |  | 457 | use Mojo::Util qw(dumper trim); | 
|  | 61 |  |  |  |  | 173 |  | 
|  | 61 |  |  |  |  | 4891 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 61 |  | 50 | 61 |  | 464 | use constant DEBUG => $ENV{MOJO_DOM_CSS_DEBUG} || 0; | 
|  | 61 |  |  |  |  | 158 |  | 
|  | 61 |  |  |  |  | 264018 |  | 
| 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 | 127 | my $tree = shift->tree; | 
| 25 | 44 | 100 |  |  |  | 154 | return $tree->[0] ne 'tag' ? undef : _match(_compile(@_), $tree, $tree, _root($tree)); | 
| 26 |  |  |  |  |  |  | } | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 444 |  |  | 444 | 1 | 1187 | sub select     { _select(0, shift->tree, _compile(@_)) } | 
| 29 | 762 |  |  | 762 | 1 | 2148 | sub select_one { _select(1, shift->tree, _compile(@_)) } | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 47 | 100 |  | 47 |  | 68 | sub _absolutize { [map { _is_scoped($_) ? $_ : [[['pc', 'scope']], ' ', @$_] } @{shift()}] } | 
|  | 50 |  |  |  |  | 85 |  | 
|  | 47 |  |  |  |  | 81 |  | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | sub _ancestor { | 
| 34 | 1529 |  |  | 1529 |  | 2842 | my ($selectors, $current, $tree, $scope, $one, $pos) = @_; | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 1529 |  | 100 |  |  | 6999 | while ($current ne $scope && $current->[0] ne 'root' && ($current = $current->[3])) { | 
|  |  |  | 66 |  |  |  |  | 
| 37 | 1625 | 100 |  |  |  | 3026 | return 1     if _combinator($selectors, $current, $tree, $scope, $pos); | 
| 38 | 393 | 100 |  |  |  | 1261 | return undef if $current eq $scope; | 
| 39 | 269 | 100 |  |  |  | 929 | last         if $one; | 
| 40 |  |  |  |  |  |  | } | 
| 41 |  |  |  |  |  |  |  | 
| 42 | 173 |  |  |  |  | 608 | return undef; | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | sub _attr { | 
| 46 | 1458 |  |  | 1458 |  | 2417 | my ($name_re, $value_re, $current) = @_; | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 1458 |  |  |  |  | 2044 | my $attrs = $current->[2]; | 
| 49 | 1458 |  |  |  |  | 3456 | for my $name (keys %$attrs) { | 
| 50 | 1470 |  |  |  |  | 2743 | my $value = $attrs->{$name}; | 
| 51 | 1470 | 100 | 100 |  |  | 8339 | next if $name !~ $name_re || (!defined $value && defined $value_re); | 
|  |  |  | 100 |  |  |  |  | 
| 52 | 761 | 100 | 100 |  |  | 6107 | return 1 if !(defined $value && defined $value_re) || $value =~ $value_re; | 
|  |  |  | 100 |  |  |  |  | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 984 |  |  |  |  | 4356 | return undef; | 
| 56 |  |  |  |  |  |  | } | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub _combinator { | 
| 59 | 9124 |  |  | 9124 |  | 14809 | my ($selectors, $current, $tree, $scope, $pos) = @_; | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | # Selector | 
| 62 | 9124 | 100 |  |  |  | 17676 | return undef unless my $c = $selectors->[$pos]; | 
| 63 | 9119 | 100 |  |  |  | 16780 | if (ref $c) { | 
| 64 | 9116 | 100 |  |  |  | 14630 | return undef unless _selector($c, $current, $tree, $scope); | 
| 65 | 3499 | 100 |  |  |  | 13739 | return 1 unless $c = $selectors->[++$pos]; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | # ">" (parent only) | 
| 69 | 1661 | 100 |  |  |  | 3959 | return _ancestor($selectors, $current, $tree, $scope, 1, ++$pos) if $c eq '>'; | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | # "~" (preceding siblings) | 
| 72 | 637 | 100 |  |  |  | 1228 | return _sibling($selectors, $current, $tree, $scope, 0, ++$pos) if $c eq '~'; | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | # "+" (immediately preceding siblings) | 
| 75 | 572 | 100 |  |  |  | 1136 | return _sibling($selectors, $current, $tree, $scope, 1, ++$pos) if $c eq '+'; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # " " (ancestor) | 
| 78 | 505 |  |  |  |  | 1031 | return _ancestor($selectors, $current, $tree, $scope, 0, ++$pos); | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | sub _compile { | 
| 82 | 1304 |  |  | 1304 |  | 6293 | my ($css, %ns) = (trim('' . shift), @_); | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 1304 |  |  |  |  | 3086 | my $group = [[]]; | 
| 85 | 1304 |  |  |  |  | 3700 | while (my $selectors = $group->[-1]) { | 
| 86 | 4504 | 100 | 100 |  |  | 15850 | push @$selectors, [] unless @$selectors && ref $selectors->[-1]; | 
| 87 | 4504 |  |  |  |  | 6803 | my $last = $selectors->[-1]; | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | # Separator | 
| 90 | 4504 | 100 |  |  |  | 31110 | if ($css =~ /\G\s*,\s*/gc) { push @$group, [] } | 
|  | 14 | 100 |  |  |  | 50 |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | # Combinator | 
| 93 |  |  |  |  |  |  | elsif ($css =~ /\G\s*([ >+~])\s*/gc) { | 
| 94 | 742 | 100 |  |  |  | 1647 | push @$last,      ['pc', 'scope'] unless @$last; | 
| 95 | 742 |  |  |  |  | 2487 | push @$selectors, $1; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | # Class or ID | 
| 99 |  |  |  |  |  |  | elsif ($css =~ /\G([.#])((?:$ESCAPE_RE\s|\\.|[^,.#:[ >~+])+)/gco) { | 
| 100 | 188 | 100 |  |  |  | 802 | my ($name, $op) = $1 eq '.' ? ('class', '~') : ('id', ''); | 
| 101 | 188 |  |  |  |  | 487 | push @$last, ['attr', _name($name), _value($op, $2)]; | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | # Attributes | 
| 105 | 283 |  | 100 |  |  | 847 | 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 |  |  |  |  | 1239 | my ($name, $args) = (lc $1, $2); | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | # ":text" (raw text) | 
| 112 | 302 | 100 |  |  |  | 1061 | $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 |  |  | 1430 | $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 |  |  |  | 912 | $args = _equation($args) if $name =~ /^nth-/; | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | # ":first-*", ":last-*" (rewrite to ":nth-(last-)*") | 
| 121 | 302 | 100 |  |  |  | 924 | ($name, $args) = ("nth-$+", [0, 1]) if $name =~ /^(?:first-(.+)|(last-.+))$/; | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 302 |  |  |  |  | 1203 | push @$last, ['pc', $name, $args]; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | # Tag | 
| 127 |  |  |  |  |  |  | elsif ($css =~ /\G((?:$ESCAPE_RE\s|\\.|[^,.#:[ >~+])+)/gco) { | 
| 128 | 1672 | 100 | 100 |  |  | 6571 | my $alias = (my $name = $1) =~ s/^([^|]*)\|// && $1 ne '*' ? $1                                  : undef; | 
| 129 | 1672 | 100 | 100 |  |  | 3330 | my $ns    = length $alias                                  ? $ns{$alias} // return [['invalid']] : $alias; | 
| 130 | 1671 | 100 |  |  |  | 4500 | push @$last, ['tag', $name eq '*' ? undef : _name($name), _unescape($ns)]; | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 1303 | 100 |  |  |  | 4026 | else { pos $css < length $css ? croak "Unknown CSS selector: $css" : last } | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 1301 |  |  |  |  | 1679 | warn qq{-- CSS Selector ($css)\n@{[dumper $group]}} if DEBUG; | 
| 137 | 1301 |  |  |  |  | 4165 | return $group; | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | sub _equation { | 
| 141 | 117 | 100 |  | 117 |  | 298 | return [0, 0] unless my $equation = shift; | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | # "even" | 
| 144 | 115 | 100 |  |  |  | 286 | return [2, 0] if $equation =~ /^\s*even\s*$/i; | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | # "odd" | 
| 147 | 106 | 100 |  |  |  | 282 | return [2, 1] if $equation =~ /^\s*odd\s*$/i; | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | # "4", "+4" or "-4" | 
| 150 | 94 | 100 |  |  |  | 463 | 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 |  |  |  | 266 | return [0, 0] unless $equation =~ /^\s*((?:\+|-)?(?:\d+)?)?n\s*((?:\+|-)\s*\d+)?\s*$/i; | 
| 154 | 51 | 100 | 100 |  |  | 405 | return [$1 eq '-' ? -1 : !length $1 ? 1 : $1, join('', split(' ', $2 // 0))]; | 
|  |  | 100 |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub _is_scoped { | 
| 158 | 1362 |  |  | 1362 |  | 1889 | my $selector = shift; | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 1362 | 100 |  |  |  | 2310 | for my $pc (grep { $_->[0] eq 'pc' } map { ref $_ ? @$_ : () } @$selector) { | 
|  | 2552 |  |  |  |  | 6311 |  | 
|  | 2970 |  |  |  |  | 6677 |  | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | # Selector with ":scope" | 
| 163 | 371 | 100 |  |  |  | 993 | return 1 if $pc->[1] eq 'scope'; | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | # Argument of functional pseudo-class with ":scope" | 
| 166 | 277 | 100 | 100 |  |  | 1335 | return 1 if ($pc->[1] eq 'has' || $pc->[1] eq 'is' || $pc->[1] eq 'not') && grep { _is_scoped($_) } @{$pc->[2]}; | 
|  | 69 |  | 100 |  |  | 151 |  | 
|  | 65 |  |  |  |  | 144 |  | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  |  | 
| 169 | 1258 |  |  |  |  | 3582 | return undef; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | sub _match { | 
| 173 | 7231 |  |  | 7231 |  | 12998 | my ($group, $current, $tree, $scope) = @_; | 
| 174 | 7231 |  | 100 |  |  | 19069 | _combinator([reverse @$_], $current, $tree, $scope, 0) and return 1 for @$group; | 
| 175 | 5395 |  |  |  |  | 18771 | return undef; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  |  | 
| 178 | 2119 |  |  | 2119 |  | 2996 | sub _name {qr/(?:^|:)\Q@{[_unescape(shift)]}\E$/} | 
|  | 2119 |  |  |  |  | 3854 |  | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | sub _namespace { | 
| 181 | 77 |  |  | 77 |  | 142 | my ($ns, $current) = @_; | 
| 182 |  |  |  |  |  |  |  | 
| 183 | 77 | 100 |  |  |  | 181 | my $attr = $current->[1] =~ /^([^:]+):/ ? "xmlns:$1" : 'xmlns'; | 
| 184 | 77 |  |  |  |  | 169 | while ($current) { | 
| 185 | 121 | 100 |  |  |  | 214 | last                               if $current->[0] eq 'root'; | 
| 186 | 117 | 100 |  |  |  | 475 | return $current->[2]{$attr} eq $ns if exists $current->[2]{$attr}; | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 44 |  |  |  |  | 83 | $current = $current->[3]; | 
| 189 |  |  |  |  |  |  | } | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | # Failing to match yields true if searching for no namespace, false otherwise | 
| 192 | 4 |  |  |  |  | 23 | return !length $ns; | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | sub _pc { | 
| 196 | 1793 |  |  | 1793 |  | 3216 | my ($class, $args, $current, $tree, $scope) = @_; | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | # ":scope" (root can only be a :scope) | 
| 199 | 1793 | 100 |  |  |  | 3402 | return $current eq $scope if $class eq 'scope'; | 
| 200 | 1671 | 100 |  |  |  | 2966 | return undef              if $current->[0] eq 'root'; | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # ":checked" | 
| 203 | 1666 | 100 | 100 |  |  | 3670 | return exists $current->[2]{checked} || exists $current->[2]{selected} if $class eq 'checked'; | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | # ":not" | 
| 206 | 1462 | 100 |  |  |  | 2323 | return !_match($args, $current, $current, $scope) if $class eq 'not'; | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | # ":is" | 
| 209 | 1283 | 100 |  |  |  | 2340 | return !!_match($args, $current, $current, $scope) if $class eq 'is'; | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # ":has" | 
| 212 | 1270 | 100 |  |  |  | 1982 | return !!_select(1, $current, $args) if $class eq 'has'; | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | # ":empty" | 
| 215 | 1241 | 100 | 100 |  |  | 2021 | return !grep { !($_->[0] eq 'comment' || $_->[0] eq 'pi') } @$current[4 .. $#$current] if $class eq 'empty'; | 
|  | 58 |  |  |  |  | 210 |  | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | # ":root" | 
| 218 | 1213 | 100 | 66 |  |  | 2287 | return $current->[3] && $current->[3][0] eq 'root' if $class eq 'root'; | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | # ":text" | 
| 221 | 1159 | 100 | 66 |  |  | 1913 | return grep { ($_->[0] eq 'text' || $_->[0] eq 'raw') && $_->[1] =~ $args->[0] } @$current[4 .. $#$current] | 
|  | 232 | 100 |  |  |  | 1688 |  | 
| 222 |  |  |  |  |  |  | if $class eq 'text'; | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | # ":any-link", ":link" and ":visited" | 
| 225 | 1079 | 100 | 100 |  |  | 3874 | if ($class eq 'any-link' || $class eq 'link' || $class eq 'visited') { | 
|  |  |  | 100 |  |  |  |  | 
| 226 | 39 | 100 | 66 |  |  | 194 | return undef unless $current->[0] eq 'tag' && exists $current->[2]{href}; | 
| 227 | 21 |  |  |  |  | 34 | return !!grep { $current->[1] eq $_ } qw(a area link); | 
|  | 63 |  |  |  |  | 168 |  | 
| 228 |  |  |  |  |  |  | } | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # ":only-child" or ":only-of-type" | 
| 231 | 1040 | 100 | 100 |  |  | 2673 | if ($class eq 'only-child' || $class eq 'only-of-type') { | 
| 232 | 40 | 100 |  |  |  | 79 | my $type = $class eq 'only-of-type' ? $current->[1] : undef; | 
| 233 | 40 |  | 100 |  |  | 58 | $_ ne $current and return undef for @{_siblings($current, $type)}; | 
|  | 40 |  |  |  |  | 60 |  | 
| 234 | 7 |  |  |  |  | 24 | return 1; | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | # ":nth-child", ":nth-last-child", ":nth-of-type" or ":nth-last-of-type" | 
| 238 | 1000 | 100 |  |  |  | 1768 | if (ref $args) { | 
| 239 | 992 | 100 | 100 |  |  | 2689 | my $type     = $class eq 'nth-of-type' || $class eq 'nth-last-of-type' ? $current->[1] : undef; | 
| 240 | 992 |  |  |  |  | 1243 | my @siblings = @{_siblings($current, $type)}; | 
|  | 992 |  |  |  |  | 1465 |  | 
| 241 | 992 |  |  |  |  | 1608 | my $index; | 
| 242 | 992 |  |  |  |  | 1943 | for my $i (0 .. $#siblings) { | 
| 243 | 3648 | 100 |  |  |  | 7859 | $index = $i, last if $siblings[$i] eq $current; | 
| 244 |  |  |  |  |  |  | } | 
| 245 | 992 | 100 | 100 |  |  | 2788 | $index = $#siblings - $index if $class eq 'nth-last-child' || $class eq 'nth-last-of-type'; | 
| 246 | 992 |  |  |  |  | 1292 | $index++; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 992 |  |  |  |  | 1593 | my $delta = $index - $args->[1]; | 
| 249 | 992 | 100 |  |  |  | 2011 | return 1 if $delta == 0; | 
| 250 | 800 |  | 100 |  |  | 5254 | return $args->[0] != 0 && ($delta < 0) == ($args->[0] < 0) && $delta % $args->[0] == 0; | 
| 251 |  |  |  |  |  |  | } | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | # Everything else | 
| 254 | 8 |  |  |  |  | 38 | return undef; | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | sub _root { | 
| 258 | 90 |  |  | 90 |  | 165 | my $tree = shift; | 
| 259 | 90 |  |  |  |  | 337 | $tree = $tree->[3] while $tree->[0] ne 'root'; | 
| 260 | 90 |  |  |  |  | 201 | return $tree; | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | sub _select { | 
| 264 | 1233 |  |  | 1233 |  | 2543 | my ($one, $scope, $group) = @_; | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | # Scoped selectors require the whole tree to be searched | 
| 267 | 1233 |  |  |  |  | 1889 | my $tree = $scope; | 
| 268 | 1233 | 100 |  |  |  | 2233 | ($group, $tree) = (_absolutize($group), _root($scope)) if grep { _is_scoped($_) } @$group; | 
|  | 1243 |  |  |  |  | 2318 |  | 
| 269 |  |  |  |  |  |  |  | 
| 270 | 1233 |  |  |  |  | 2000 | my @results; | 
| 271 | 1233 | 100 |  |  |  | 5046 | my @queue = @$tree[($tree->[0] eq 'root' ? 1 : 4) .. $#$tree]; | 
| 272 | 1233 |  |  |  |  | 3130 | while (my $current = shift @queue) { | 
| 273 | 17358 | 100 |  |  |  | 37327 | next unless $current->[0] eq 'tag'; | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 6996 |  |  |  |  | 15029 | unshift @queue, @$current[4 .. $#$current]; | 
| 276 | 6996 | 100 |  |  |  | 12359 | next unless _match($group, $current, $tree, $scope); | 
| 277 | 1757 | 100 |  |  |  | 6823 | $one ? return $current : push @results, $current; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  |  | 
| 280 | 530 | 100 |  |  |  | 3648 | return $one ? undef : \@results; | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | sub _selector { | 
| 284 | 9116 |  |  | 9116 |  | 13759 | my ($selector, $current, $tree, $scope) = @_; | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | # The root might be the scope | 
| 287 | 9116 |  |  |  |  | 13649 | my $is_tag = $current->[0] eq 'tag'; | 
| 288 | 9116 |  |  |  |  | 13914 | for my $s (@$selector) { | 
| 289 | 10355 |  |  |  |  | 13544 | my $type = $s->[0]; | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | # Tag | 
| 292 | 10355 | 100 | 100 |  |  | 33194 | if ($is_tag && $type eq 'tag') { | 
|  |  | 100 | 100 |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 293 | 6989 | 100 | 100 |  |  | 47199 | return undef if defined $s->[1] && $current->[1] !~ $s->[1]; | 
| 294 | 3546 | 100 | 100 |  |  | 9392 | return undef if defined $s->[2] && !_namespace($s->[2], $current); | 
| 295 |  |  |  |  |  |  | } | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | # Attribute | 
| 298 | 1458 | 100 |  |  |  | 2651 | elsif ($is_tag && $type eq 'attr') { return undef unless _attr(@$s[1, 2], $current) } | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | # Pseudo-class | 
| 301 | 1793 | 100 |  |  |  | 3457 | elsif ($type eq 'pc') { return undef unless _pc(@$s[1, 2], $current, $tree, $scope) } | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | # No match | 
| 304 | 115 |  |  |  |  | 391 | else { return undef } | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  |  | 
| 307 | 3499 |  |  |  |  | 7060 | return 1; | 
| 308 |  |  |  |  |  |  | } | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | sub _sibling { | 
| 311 | 132 |  |  | 132 |  | 280 | my ($selectors, $current, $tree, $scope, $immediate, $pos) = @_; | 
| 312 |  |  |  |  |  |  |  | 
| 313 | 132 |  |  |  |  | 167 | my $found; | 
| 314 | 132 |  |  |  |  | 175 | for my $sibling (@{_siblings($current)}) { | 
|  | 132 |  |  |  |  | 225 |  | 
| 315 | 326 | 100 |  |  |  | 1028 | return $found if $sibling eq $current; | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | # "+" (immediately preceding sibling) | 
| 318 | 224 | 100 |  |  |  | 390 | if ($immediate) { $found = _combinator($selectors, $sibling, $tree, $scope, $pos) } | 
|  | 125 |  |  |  |  | 235 |  | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | # "~" (preceding sibling) | 
| 321 | 99 | 100 |  |  |  | 186 | 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 |  | 1883 | my ($current, $type) = @_; | 
| 329 |  |  |  |  |  |  |  | 
| 330 | 1164 |  |  |  |  | 1729 | my $parent   = $current->[3]; | 
| 331 | 1164 | 100 |  |  |  | 3390 | my @siblings = grep { $_->[0] eq 'tag' } @$parent[($parent->[0] eq 'root' ? 1 : 4) .. $#$parent]; | 
|  | 15442 |  |  |  |  | 26152 |  | 
| 332 | 1164 | 100 |  |  |  | 2913 | @siblings = grep { $type eq $_->[1] } @siblings if defined $type; | 
|  | 643 |  |  |  |  | 1099 |  | 
| 333 |  |  |  |  |  |  |  | 
| 334 | 1164 |  |  |  |  | 2842 | return \@siblings; | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | sub _unescape { | 
| 338 | 4213 | 100 |  | 4213 |  | 13886 | return undef unless defined(my $value = shift); | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | # Remove escaped newlines | 
| 341 | 2610 |  |  |  |  | 4306 | $value =~ s/\\\n//g; | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | # Unescape Unicode characters | 
| 344 | 2610 |  |  |  |  | 3661 | $value =~ s/\\([0-9a-fA-F]{1,6})\s?/pack 'U', hex $1/ge; | 
|  | 35 |  |  |  |  | 217 |  | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | # Remove backslash | 
| 347 | 2610 |  |  |  |  | 3563 | $value =~ s/\\//g; | 
| 348 |  |  |  |  |  |  |  | 
| 349 | 2610 |  |  |  |  | 39651 | return $value; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | sub _value { | 
| 353 | 471 |  |  | 471 |  | 1735 | my ($op, $value, $insensitive) = @_; | 
| 354 | 471 | 100 |  |  |  | 1269 | return undef unless defined $value; | 
| 355 | 423 | 100 |  |  |  | 927 | $value = ($insensitive ? '(?i)' : '') . quotemeta _unescape($value); | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | # "~=" (word) | 
| 358 | 423 | 100 |  |  |  | 2375 | return qr/(?:^|\s+)$value(?:\s+|$)/ if $op eq '~'; | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | # "|=" (hyphen-separated) | 
| 361 | 324 | 100 |  |  |  | 795 | return qr/^$value(?:-|$)/ if $op eq '|'; | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | # "*=" (contains) | 
| 364 | 314 | 100 |  |  |  | 736 | return qr/$value/ if $op eq '*'; | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | # "^=" (begins with) | 
| 367 | 302 | 100 |  |  |  | 788 | return qr/^$value/ if $op eq '^'; | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | # "$=" (ends with) | 
| 370 | 270 | 100 |  |  |  | 726 | return qr/$value$/ if $op eq '$'; | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | # Everything else | 
| 373 | 239 |  |  |  |  | 2658 | 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 |