Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
improved css3 support in Mojo::DOM
  • Loading branch information
kraih committed Feb 8, 2011
1 parent 1515d2a commit 30c8e6b
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 10 deletions.
1 change: 1 addition & 0 deletions Changes
Expand Up @@ -22,6 +22,7 @@ This file documents the revision history for Perl extension Mojolicious.
over Mojolicious.
- Renamed after and before in Mojo::DOM to add_after and add_before.
- Improved documentation browser slightly.
- Improved CSS3 support in Mojo::DOM.
- Changed Mojolicious::Plugin::EplRenderer to not render 404 errors
for missing templates.
- Changed exception template to use pre instead of h1 tags for error
Expand Down
25 changes: 16 additions & 9 deletions lib/Mojo/DOM.pm
Expand Up @@ -15,11 +15,11 @@ has tree => sub { ['root'] };
my $CSS_ESCAPE_RE = qr/\\[^0-9a-fA-F]|\\[0-9a-fA-F]{1,6}/;
my $CSS_ATTR_RE = qr/
\[
((?:$CSS_ESCAPE_RE|\w)+) # Key
((?:$CSS_ESCAPE_RE|\w)+) # Key
(?:
(\W)? # Operator
(\W)? # Operator
=
"((?:\\"|[^"])+)" # Value
(?:"((?:\\"|[^"])+)"|(\S+)) # Value
)?
\]
/x;
Expand All @@ -28,13 +28,18 @@ my $CSS_ELEMENT_RE = qr/^((?:\\\.|\\\#|[^\.\#])+)/;
my $CSS_ID_RE = qr/\#((?:\\\#|[^\#])+)/;
my $CSS_PSEUDO_CLASS_RE = qr/(?:\:([\w\-]+)(?:\(((?:\([^\)]+\)|[^\)])+)\))?)/;
my $CSS_TOKEN_RE = qr/
(\s*,\s*)? # Separator
((?:[^\[\\\:\s\,]|$CSS_ESCAPE_RE\s?)+)? # Element
((?:\:[\w\-]+(?:\((?:\([^\)]+\)|[^\)])+\))?)*)? # Pseudoclass
((?:\[(?:$CSS_ESCAPE_RE|\w)+(?:\W?="(?:\\"|[^"])+")?\])*)? # Attributes
(\s*,\s*)? # Separator
((?:[^\[\\\:\s\,]|$CSS_ESCAPE_RE\s?)+)? # Element
((?:\:[\w\-]+(?:\((?:\([^\)]+\)|[^\)])+\))?)*)? # Pseudoclass
((?:\[
(?:$CSS_ESCAPE_RE|\w)+ # Key
(?:\W?= # Operator
(?:"(?:\\"|[^"])+"|\S+) # Value
)?
\])*)?
(?:
\s*
([\>\+\~]) # Combinator
([\>\+\~]) # Combinator
)?
/x;
my $XML_ATTR_RE = qr/
Expand Down Expand Up @@ -428,10 +433,11 @@ sub _css_equation {
elsif ($equation eq 'odd') { $num = [2, 1] }

# Equation
elsif ($equation =~ /(?:(\-?(?:\d+)?)?n)?\+?(\-?\d+)?$/) {
elsif ($equation =~ /(?:(\-?(?:\d+)?)?n)?\s*\+?\s*(\-?\s*\d+)?\s*$/) {
$num->[0] = $1 || 0;
$num->[0] = -1 if $num->[0] eq '-';
$num->[1] = $2 || 0;
$num->[1] =~ s/\s+//g;
}

return $num;
Expand Down Expand Up @@ -864,6 +870,7 @@ sub _parse_css {
my $key = $self->_css_unescape($1);
my $op = $2 || '';
my $value = $3;
$value = $4 unless defined $3;

push @$selector, ['attribute', $key, $self->_css_regex($op, $value)];
}
Expand Down
39 changes: 38 additions & 1 deletion t/mojo/dom.t
Expand Up @@ -5,7 +5,7 @@ use warnings;

use utf8;

use Test::More tests => 317;
use Test::More tests => 344;

# "Homer gave me a kidney: it wasn't his, I didn't need it,
# and it came postage due- but I appreciated the gesture!"
Expand Down Expand Up @@ -120,6 +120,8 @@ is $simple->to_xml, '<simple class="working">easy</simple>',
is $dom->at('test#test')->type, 'test', 'right type';
is $dom->at('[class$="ing"]')->type, 'simple', 'right type';
is $dom->at('[class="working"]')->type, 'simple', 'right type';
is $dom->at('[class$=ing]')->type, 'simple', 'right type';
is $dom->at('[class=working]')->type, 'simple', 'right type';

# Deep nesting (parent combinator)
$dom->parse(<<EOF);
Expand Down Expand Up @@ -168,6 +170,8 @@ is $dom->at('#test')->text, 'works', 'right text';
is $dom->at('div')->text, 'works', 'right text';
is $dom->at('[foo="bar"]')->text, 'works', 'right text';
is $dom->at('[foo="ba"]'), undef, 'no result';
is $dom->at('[foo=bar]')->text, 'works', 'right text';
is $dom->at('[foo=ba]'), undef, 'no result';
is $dom->at('.tset')->text, 'works', 'right text';

# HTML1 (single quotes, uppercase tags and whitespace in attributes)
Expand All @@ -176,6 +180,8 @@ is $dom->at('#test')->text, 'works', 'right text';
is $dom->at('div')->text, 'works', 'right text';
is $dom->at('[foo="bar"]')->text, 'works', 'right text';
is $dom->at('[foo="ba"]'), undef, 'no result';
is $dom->at('[foo=bar]')->text, 'works', 'right text';
is $dom->at('[foo=ba]'), undef, 'no result';
is $dom->at('.tset')->text, 'works', 'right text';

# Already decoded unicode snowman and quotes in selector
Expand Down Expand Up @@ -204,6 +210,10 @@ is $dom->at('[id^="☃"]')->text, 'Snowman', 'right text';
is $dom->at('div[id^="☃"]')->text, 'Snowman', 'right text';
is $dom->at('p div[id^="☃"]')->text, 'Snowman', 'right text';
is $dom->at('p > div[id^="☃"]')->text, 'Snowman', 'right text';
is $dom->at('[id^=☃]')->text, 'Snowman', 'right text';
is $dom->at('div[id^=☃]')->text, 'Snowman', 'right text';
is $dom->at('p div[id^=☃]')->text, 'Snowman', 'right text';
is $dom->at('p > div[id^=☃]')->text, 'Snowman', 'right text';
is $dom->at(".\\\n\\002665")->text, 'Heart', 'right text';
is $dom->at('.\\2665')->text, 'Heart', 'right text';
is $dom->at("p .\\\n\\002665")->text, 'Heart', 'right text';
Expand All @@ -222,14 +232,26 @@ is $dom->at('[class$="♥"]')->text, 'Heart', 'right text';
is $dom->at('div[class$="♥"]')->text, 'Heart', 'right text';
is $dom->at('p div[class$="♥"]')->text, 'Heart', 'right text';
is $dom->at('p > div[class$="♥"]')->text, 'Heart', 'right text';
is $dom->at('[class$=♥]')->text, 'Heart', 'right text';
is $dom->at('div[class$=♥]')->text, 'Heart', 'right text';
is $dom->at('p div[class$=♥]')->text, 'Heart', 'right text';
is $dom->at('p > div[class$=♥]')->text, 'Heart', 'right text';
is $dom->at('[class~="♥"]')->text, 'Heart', 'right text';
is $dom->at('div[class~="♥"]')->text, 'Heart', 'right text';
is $dom->at('p div[class~="♥"]')->text, 'Heart', 'right text';
is $dom->at('p > div[class~="♥"]')->text, 'Heart', 'right text';
is $dom->at('[class~=♥]')->text, 'Heart', 'right text';
is $dom->at('div[class~=♥]')->text, 'Heart', 'right text';
is $dom->at('p div[class~=♥]')->text, 'Heart', 'right text';
is $dom->at('p > div[class~=♥]')->text, 'Heart', 'right text';
is $dom->at('[class~="x"]')->text, 'Heart', 'right text';
is $dom->at('div[class~="x"]')->text, 'Heart', 'right text';
is $dom->at('p div[class~="x"]')->text, 'Heart', 'right text';
is $dom->at('p > div[class~="x"]')->text, 'Heart', 'right text';
is $dom->at('[class~=x]')->text, 'Heart', 'right text';
is $dom->at('div[class~=x]')->text, 'Heart', 'right text';
is $dom->at('p div[class~=x]')->text, 'Heart', 'right text';
is $dom->at('p > div[class~=x]')->text, 'Heart', 'right text';

# Looks remotely like HTML
$dom->parse('<!DOCTYPE H "-/W/D HT 4/E">☃<title class=test>♥</title>☃');
Expand Down Expand Up @@ -566,6 +588,9 @@ is($dom->find(':nth-last-child(1)')->[1]->text, 'H', 'right text');
$dom->find('li:nth-child(2n+1)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A C E G/], 'found all odd li elements';
@li = ();
$dom->find('li:nth-child(2n + 1)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A C E G/], 'found all odd li elements';
@li = ();
$dom->find('li:nth-last-child(2n+1)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/B D F H/], 'found all odd li elements';
@li = ();
Expand All @@ -578,6 +603,9 @@ is_deeply \@li, [qw/A C E G/], 'found all even li elements';
$dom->find('li:nth-child(2n+2)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/B D F H/], 'found all even li elements';
@li = ();
$dom->find('li:nth-child( 2n + 2 )')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/B D F H/], 'found all even li elements';
@li = ();
$dom->find('li:nth-last-child(2n+2)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A C E G/], 'found all even li elements';
@li = ();
Expand All @@ -596,18 +624,27 @@ is_deeply \@li, [qw/A E/], 'found the right li element';
$dom->find('li:nth-child(4n)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/D H/], 'found the right li element';
@li = ();
$dom->find('li:nth-child( 4n )')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/D H/], 'found the right li element';
@li = ();
$dom->find('li:nth-last-child(4n)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A E/], 'found the right li element';
@li = ();
$dom->find('li:nth-child(5n-2)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/C H/], 'found the right li element';
@li = ();
$dom->find('li:nth-child( 5n - 2 )')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/C H/], 'found the right li element';
@li = ();
$dom->find('li:nth-last-child(5n-2)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A F/], 'found the right li element';
@li = ();
$dom->find('li:nth-child(-n+3)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A B C/], 'found first three li elements';
@li = ();
$dom->find('li:nth-child( -n + 3 )')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/A B C/], 'found first three li elements';
@li = ();
$dom->find('li:nth-last-child(-n+3)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw/F G H/], 'found last three li elements';
@li = ();
Expand Down

0 comments on commit 30c8e6b

Please sign in to comment.