Skip to content

Commit

Permalink
added experimental support for case-insensitive attribute selectors l…
Browse files Browse the repository at this point in the history
…ike [foo="bar" i] to Mojo::DOM::CSS
  • Loading branch information
kraih committed Jan 10, 2015
1 parent 5bfba56 commit 1a2c5de
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 10 deletions.
2 changes: 2 additions & 0 deletions Changes
@@ -1,5 +1,7 @@

5.72 2015-01-10
- Added EXPERIMENTAL support for case-insensitive attribute selectors like
[foo="bar" i] to Mojo::DOM::CSS.
- Improved Mojo::Reactor::EV to update the current time before starting a
timer.

Expand Down
33 changes: 23 additions & 10 deletions lib/Mojo/DOM/CSS.pm
Expand Up @@ -8,9 +8,9 @@ my $ATTR_RE = qr/
\[
((?:$ESCAPE_RE|[\w\-])+) # Key
(?:
(\W)? # Operator
=
(?:"((?:\\"|[^"])*)"|([^\]]+)) # Value
(\W)?= # Operator
(?:"((?:\\"|[^"])*)"|([^\]]+?)) # Value
(?:\s+(i))? # Case-sensitivity
)?
\]
/x;
Expand Down Expand Up @@ -82,7 +82,7 @@ sub _compile {
my $pattern = [[]];
while ($css =~ /$TOKEN_RE/go) {
my ($separator, $element, $pc, $attrs, $combinator)
= ($1, $2 // '', $3, $6, $11);
= ($1, $2 // '', $3, $6, $12);

next unless $separator || $element || $pc || $attrs || $combinator;

Expand All @@ -109,7 +109,7 @@ sub _compile {
while $pc =~ /$PSEUDO_CLASS_RE/go;

# Attributes
push @$selector, ['attr', _name($1), _value($2 // '', $3 // $4)]
push @$selector, ['attr', _name($1), _value($2 // '', $3 // $4, $5)]
while $attrs =~ /$ATTR_RE/go;

# Combinator
Expand Down Expand Up @@ -279,24 +279,25 @@ sub _unescape {
}

sub _value {
my ($op, $value) = @_;
my ($op, $value, $ci) = @_;
return undef unless defined $value;
$value = quotemeta _unescape($value);

# "~=" (word)
return qr/(?:^|.*\s+)$value(?:\s+.*|$)/i if $op eq '~' && $ci;
return qr/(?:^|.*\s+)$value(?:\s+.*|$)/ if $op eq '~';

# "*=" (contains)
return qr/$value/ if $op eq '*';
return $ci ? qr/$value/i : qr/$value/ if $op eq '*';

# "^=" (begins with)
return qr/^$value/ if $op eq '^';
return $ci ? qr/^$value/i : qr/^$value/ if $op eq '^';

# "$=" (ends with)
return qr/$value$/ if $op eq '$';
return $ci ? qr/$value$/i : qr/$value$/ if $op eq '$';

# Everything else
return qr/^$value$/;
return $ci ? qr/^$value$/i : qr/^$value$/;
}

1;
Expand Down Expand Up @@ -348,6 +349,18 @@ An C<E> element whose C<foo> attribute value is exactly equal to C<bar>.
my $fields = $css->select('input[name="foo"]');
=head2 E[foo="bar" i]
An C<E> element whose C<foo> attribute value is exactly equal to any
(ASCII-range) case-permutation of C<bar>. Note that this selector is
EXPERIMENTAL and might change without warning!
my $fields = $css->select('input[type="hidden" i]');
This selector is part of
L<Selectors Level 4|http://dev.w3.org/csswg/selectors-4>, which is still a
work in progress.
=head2 E[foo~="bar"]
An C<E> element whose C<foo> attribute value is a list of
Expand Down
25 changes: 25 additions & 0 deletions t/mojo/dom.t
Expand Up @@ -2279,6 +2279,31 @@ is "$dom", <<EOF, 'right result';
<bar>after</bar>
EOF

# Case-insensitive attribute values
$dom = Mojo::DOM->new(<<EOF);
<p class="foo">A</p>
<p class="foo bAr">B</p>
<p class="FOO">C</p>
EOF
is $dom->find('.foo')->map('text')->join(','), 'A,B', 'right result';
is $dom->find('.FOO')->map('text')->join(','), 'C', 'right result';
is $dom->find('[class=foo]')->map('text')->join(','), 'A', 'right result';
is $dom->find('[class=foo i]')->map('text')->join(','), 'A,C', 'right result';
is $dom->find('[class="foo" i]')->map('text')->join(','), 'A,C',
'right result';
is $dom->find('[class="foo bar"]')->size, 0, 'no results';
is $dom->find('[class="foo bar" i]')->map('text')->join(','), 'B',
'right result';
is $dom->find('[class~=foo]')->map('text')->join(','), 'A,B', 'right result';
is $dom->find('[class~=foo i]')->map('text')->join(','), 'A,B,C',
'right result';
is $dom->find('[class*=f]')->map('text')->join(','), 'A,B', 'right result';
is $dom->find('[class*=f i]')->map('text')->join(','), 'A,B,C', 'right result';
is $dom->find('[class^=F]')->map('text')->join(','), 'C', 'right result';
is $dom->find('[class^=F i]')->map('text')->join(','), 'A,B,C', 'right result';
is $dom->find('[class$=O]')->map('text')->join(','), 'C', 'right result';
is $dom->find('[class$=O i]')->map('text')->join(','), 'A,C', 'right result';

# Nested description lists
$dom = Mojo::DOM->new(<<EOF);
<dl>
Expand Down

0 comments on commit 1a2c5de

Please sign in to comment.