{{$NEXT}}
+ - Merge several CSS bugfixes and improvements from Mojolicious 6.31 and 6.32.
0.002 2015-11-09 19:28:42 EST
- Support perl 5.8 (mst)
my $dom = DOM::Tiny->new('<P ID="greeting">Hi!</P>');
say $dom->at('p[id]')->text;
-If XML processing instructions are found, the parser will automatically switch
-into XML mode and everything becomes case-sensitive.
+If an XML declaration is found, the parser will automatically switch into XML
+mode and everything becomes case-sensitive.
# XML semantics
my $dom = DOM::Tiny->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
my $result = $dom->at('div ~ p');
Find first descendant element of this element matching the CSS selector and
-return it as a L<DOM::Tiny> object or return C<undef> if none could be found.
-All selectors listed in L</"SELECTORS"> are supported.
+return it as a L<DOM::Tiny> object, or C<undef> if none could be found. All
+selectors listed in L</"SELECTORS"> are supported.
# Find first element with "svg" namespace definition
my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};
my $namespace = $dom->namespace;
-Find this element's namespace or return C<undef> if none could be found.
+Find this element's namespace, or return C<undef> if none could be found.
# Find namespace for an element with namespace prefix
my $namespace = $dom->at('svg > svg\:circle')->namespace;
my $sibling = $dom->next;
-Return L<DOM::Tiny> object for next sibling element or C<undef> if there are no
-more siblings.
+Return L<DOM::Tiny> object for next sibling element, or C<undef> if there are
+no more siblings.
# "<h2>123</h2>"
$dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;
my $sibling = $dom->next_node;
-Return L<DOM::Tiny> object for next sibling node or C<undef> if there are no
+Return L<DOM::Tiny> object for next sibling node, or C<undef> if there are no
more siblings.
# "456"
my $parent = $dom->parent;
-Return L<DOM::Tiny> object for parent of this node or C<undef> if this node has
-no parent.
+Return L<DOM::Tiny> object for parent of this node, or C<undef> if this node
+has no parent.
+
+ # "<b><i>Test</i></b>"
+ $dom->parse('<p><b><i>Test</i></b></p>')->at('i')->parent;
=head2 parse
Parse HTML/XML fragment.
# Parse XML
- my $dom = DOM::Tiny->new->xml(1)->parse($xml);
+ my $dom = DOM::Tiny->new->xml(1)->parse('<foo>I ♥ DOM::Tiny!</foo>');
=head2 preceding
my $sibling = $dom->previous;
-Return L<DOM::Tiny> object for previous sibling element or C<undef> if there
+Return L<DOM::Tiny> object for previous sibling element, or C<undef> if there
are no more siblings.
# "<h1>Test</h1>"
my $sibling = $dom->previous_node;
-Return L<DOM::Tiny> object for previous sibling node or C<undef> if there are
+Return L<DOM::Tiny> object for previous sibling node, or C<undef> if there are
no more siblings.
# "123"
my $value = $dom->val;
Extract value from form element (such as C<button>, C<input>, C<option>,
-C<select> and C<textarea>) or return C<undef> if this element has no value. In
+C<select> and C<textarea>), or return C<undef> if this element has no value. In
the case of C<select> with C<multiple> attribute, find C<option> elements with
-C<selected> attribute and return an array reference with all values or C<undef>
-if none could be found.
+C<selected> attribute and return an array reference with all values, or
+C<undef> if none could be found.
# "a"
$dom->parse('<input name="test" value="a">')->at('input')->val;
$dom = $dom->xml($bool);
Disable HTML semantics in parser and activate case-sensitivity, defaults to
-auto detection based on processing instructions.
+auto detection based on XML declarations.
=head1 COLLECTION METHODS
my $dom = DOM::Tiny->new('<P ID="greeting">Hi!</P>');
say $dom->at('p[id]')->text;
-If XML processing instructions are found, the parser will automatically switch
-into XML mode and everything becomes case-sensitive.
+If an XML declaration is found, the parser will automatically switch into XML
+mode and everything becomes case-sensitive.
# XML semantics
my $dom = DOM::Tiny->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
my $result = $dom->at('div ~ p');
Find first descendant element of this element matching the CSS selector and
-return it as a L<DOM::Tiny> object or return C<undef> if none could be found.
-All selectors listed in L</"SELECTORS"> are supported.
+return it as a L<DOM::Tiny> object, or C<undef> if none could be found. All
+selectors listed in L</"SELECTORS"> are supported.
# Find first element with "svg" namespace definition
my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};
my $namespace = $dom->namespace;
-Find this element's namespace or return C<undef> if none could be found.
+Find this element's namespace, or return C<undef> if none could be found.
# Find namespace for an element with namespace prefix
my $namespace = $dom->at('svg > svg\:circle')->namespace;
my $sibling = $dom->next;
-Return L<DOM::Tiny> object for next sibling element or C<undef> if there are no
-more siblings.
+Return L<DOM::Tiny> object for next sibling element, or C<undef> if there are
+no more siblings.
# "<h2>123</h2>"
$dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;
my $sibling = $dom->next_node;
-Return L<DOM::Tiny> object for next sibling node or C<undef> if there are no
+Return L<DOM::Tiny> object for next sibling node, or C<undef> if there are no
more siblings.
# "456"
my $parent = $dom->parent;
-Return L<DOM::Tiny> object for parent of this node or C<undef> if this node has
-no parent.
+Return L<DOM::Tiny> object for parent of this node, or C<undef> if this node
+has no parent.
+
+ # "<b><i>Test</i></b>"
+ $dom->parse('<p><b><i>Test</i></b></p>')->at('i')->parent;
=head2 parse
Parse HTML/XML fragment.
# Parse XML
- my $dom = DOM::Tiny->new->xml(1)->parse($xml);
+ my $dom = DOM::Tiny->new->xml(1)->parse('<foo>I ♥ DOM::Tiny!</foo>');
=head2 preceding
my $sibling = $dom->previous;
-Return L<DOM::Tiny> object for previous sibling element or C<undef> if there
+Return L<DOM::Tiny> object for previous sibling element, or C<undef> if there
are no more siblings.
# "<h1>Test</h1>"
my $sibling = $dom->previous_node;
-Return L<DOM::Tiny> object for previous sibling node or C<undef> if there are
+Return L<DOM::Tiny> object for previous sibling node, or C<undef> if there are
no more siblings.
# "123"
my $value = $dom->val;
Extract value from form element (such as C<button>, C<input>, C<option>,
-C<select> and C<textarea>) or return C<undef> if this element has no value. In
+C<select> and C<textarea>), or return C<undef> if this element has no value. In
the case of C<select> with C<multiple> attribute, find C<option> elements with
-C<selected> attribute and return an array reference with all values or C<undef>
-if none could be found.
+C<selected> attribute and return an array reference with all values, or
+C<undef> if none could be found.
# "a"
$dom->parse('<input name="test" value="a">')->at('input')->val;
$dom = $dom->xml($bool);
Disable HTML semantics in parser and activate case-sensitivity, defaults to
-auto detection based on processing instructions.
+auto detection based on XML declarations.
=head1 COLLECTION METHODS
];
}
- # Pseudo-class (":not" contains more selectors)
+ # Pseudo-class
elsif ($css =~ /\G:([\w\-]+)(?:\(((?:\([^)]+\)|[^)])+)\))?/gcs) {
- push @$last, ['pc', lc $1, $1 eq 'not' ? _compile($2) : _equation($2)];
+ my ($name, $args) = (lc $1, $2);
+
+ # ":not" (contains more selectors)
+ $args = _compile($args) if $name eq 'not';
+
+ # ":nth-*" (with An+B notation)
+ $args = _equation($args) if $name =~ /^nth-/;
+
+ # ":first-*" (rewrite to ":nth-*")
+ ($name, $args) = ("nth-$1", [0, 1]) if $name =~ /^first-(.+)$/;
+
+ # ":last-*" (rewrite to ":nth-*")
+ ($name, $args) = ("nth-$name", [-1, 1]) if $name =~ /^last-/;
+
+ push @$last, ['pc', $name, $args];
}
# Tag
sub _empty { $_[0][0] eq 'comment' || $_[0][0] eq 'pi' }
sub _equation {
- return [] unless my $equation = shift;
+ return [0, 0] unless my $equation = shift;
# "even"
return [2, 2] if $equation =~ /^\s*even\s*$/i;
# "odd"
return [2, 1] if $equation =~ /^\s*odd\s*$/i;
- # Equation
- my $num = [1, 1];
- return $num if $equation !~ /(?:(-?(?:\d+)?)?(n))?\s*\+?\s*(-?\s*\d+)?\s*$/i;
- $num->[0] = defined($1) && $1 ne '' ? $1 : $2 ? 1 : 0;
- $num->[0] = -1 if $num->[0] eq '-';
- $num->[1] = defined($3) ? $3 : 0;
- $num->[1] =~ s/\s+//g;
- return $num;
+ # "4", "+4" or "-4"
+ return [0, $1] if $equation =~ /^\s*((?:\+|-)?\d+)\s*$/;
+
+ # "n", "4n", "+4n", "-4n", "n+1", "4n-1", "+4n-1" (and other variations)
+ return [0, 0]
+ unless $equation =~ /^\s*((?:\+|-)?(?:\d+)?)?n\s*((?:\+|-)\s*\d+)?\s*$/i;
+ return [$1 eq '-' ? -1 : $1 eq '' ? 1 : $1, join('', split(' ', $2 || 0))];
}
sub _match {
sub _pc {
my ($class, $args, $current) = @_;
- # ":empty"
- return !grep { !_empty($_) } @$current[4 .. $#$current] if $class eq 'empty';
-
- # ":root"
- return $current->[3] && $current->[3][0] eq 'root' if $class eq 'root';
+ # ":checked"
+ return exists $current->[2]{checked} || exists $current->[2]{selected}
+ if $class eq 'checked';
# ":not"
return !_match($args, $current, $current) if $class eq 'not';
- # ":checked"
- return exists $current->[2]{checked} || exists $current->[2]{selected}
- if $class eq 'checked';
+ # ":empty"
+ return !grep { !_empty($_) } @$current[4 .. $#$current] if $class eq 'empty';
- # ":first-*" or ":last-*" (rewrite with equation)
- ($class, $args) = $1 ? ("nth-$class", [0, 1]) : ("nth-last-$class", [-1, 1])
- if $class =~ s/^(?:(first)|last)-//;
+ # ":root"
+ return $current->[3] && $current->[3][0] eq 'root' if $class eq 'root';
- # ":nth-*"
- if ($class =~ /^nth-/) {
+ # ":nth-child", ":nth-last-child", ":nth-of-type" or ":nth-last-of-type"
+ if (ref $args) {
my $type = $class =~ /of-type$/ ? $current->[1] : undef;
my @siblings = @{_siblings($current, $type)};
-
- # ":nth-last-*"
@siblings = reverse @siblings if $class =~ /^nth-last/;
for my $i (0 .. $#siblings) {
}
}
- # ":only-*"
- elsif ($class =~ /^only-(?:child|(of-type))$/) {
- $_ ne $current and return undef
- for @{_siblings($current, $1 ? $current->[1] : undef)};
+ # ":only-child" or ":only-of-type"
+ elsif ($class eq 'only-child' || $class eq 'only-of-type') {
+ my $type = $class eq 'only-of-type' ? $current->[1] : undef;
+ $_ ne $current and return undef for @{_siblings($current, $type)};
return 1;
}
is_deeply \@li, [qw(D H)], 'found the right li elements';
@li = ();
$dom->find('li:nth-child(4n+4)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(D H)], 'found the right li element';
+is_deeply \@li, [qw(D H)], 'found the right li elements';
@li = ();
$dom->find('li:nth-last-child(4n+4)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(A E)], 'found the right li element';
+is_deeply \@li, [qw(A E)], 'found the right li elements';
@li = ();
$dom->find('li:nth-child(4n)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(D H)], 'found the right li element';
+is_deeply \@li, [qw(D H)], 'found the right li elements';
@li = ();
$dom->find('li:nth-child( 4n )')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(D H)], 'found the right li element';
+is_deeply \@li, [qw(D H)], 'found the right li elements';
@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';
+is_deeply \@li, [qw(A E)], 'found the right li elements';
@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';
+is_deeply \@li, [qw(C H)], 'found the right li elements';
@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';
+is_deeply \@li, [qw(C H)], 'found the right li elements';
@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';
+is_deeply \@li, [qw(A F)], 'found the right 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';
$dom->find('li:Nth-Last-Child(3N)')->each(sub { push @li, shift->text });
is_deeply \@li, [qw(C F)], 'found every third li elements';
@li = ();
-$dom->find('li:nth-child(3)')->each(sub { push @li, shift->text });
+$dom->find('li:nth-child( 3 )')->each(sub { push @li, shift->text });
is_deeply \@li, ['C'], 'found third li element';
@li = ();
-$dom->find('li:nth-last-child(3)')->each(sub { push @li, shift->text });
+$dom->find('li:nth-last-child( +3 )')->each(sub { push @li, shift->text });
is_deeply \@li, ['F'], 'found third last li element';
@li = ();
$dom->find('li:nth-child(1n+0)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(A B C D E F G)], 'found first three li elements';
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
+@li = ();
+$dom->find('li:nth-child(1n-0)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
@li = ();
$dom->find('li:nth-child(n+0)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(A B C D E F G)], 'found first three li elements';
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
+@li = ();
+$dom->find('li:nth-child(n)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
+@li = ();
+$dom->find('li:nth-child(n+0)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
@li = ();
$dom->find('li:NTH-CHILD(N+0)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(A B C D E F G)], 'found first three li elements';
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
@li = ();
$dom->find('li:Nth-Child(N+0)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(A B C D E F G)], 'found first three li elements';
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
@li = ();
$dom->find('li:nth-child(n)')->each(sub { push @li, shift->text });
-is_deeply \@li, [qw(A B C D E F G)], 'found first three li elements';
+is_deeply \@li, [qw(A B C D E F G)], 'found all li elements';
+@li = ();
+$dom->find('li:nth-child(0n+1)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(A)], 'found first li element';
+is $dom->find('li:nth-child(0n+0)')->size, 0, 'no results';
+is $dom->find('li:nth-child(0)')->size, 0, 'no results';
+is $dom->find('li:nth-child()')->size, 0, 'no results';
+is $dom->find('li:nth-child(whatever)')->size, 0, 'no results';
+is $dom->find('li:whatever(whatever)')->size, 0, 'no results';
# Even more pseudo-classes
$dom = DOM::Tiny->new(<<EOF);
$dom->find('ul :nth-child(-n+3):not(li)')->each(sub { push @e, shift->text });
is_deeply \@e, ['B'], 'found first p element';
@e = ();
+$dom->find('ul :nth-child(-n+3):NOT(li)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['B'], 'found first p element';
+@e = ();
$dom->find('ul :nth-child(-n+3):not(:first-child)')
->each(sub { push @e, shift->text });
is_deeply \@e, [qw(B C)], 'found second and third element';