Skip to content

Commit

Permalink
added match method to Mojo::DOM
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Sep 11, 2013
1 parent 0177187 commit 04e4d24
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 27 deletions.
4 changes: 4 additions & 0 deletions Changes
@@ -1,5 +1,9 @@

4.36 2013-09-11
- Added match method to Mojo::DOM.
- Added match method to Mojo::DOM::CSS.
- Improved ancestors and children methods in Mojo::DOM to support all CSS
selectors.
- Improved compatibility with different object systems.

4.35 2013-09-10
Expand Down
47 changes: 30 additions & 17 deletions lib/Mojo/DOM.pm
Expand Up @@ -38,7 +38,7 @@ sub new {

sub all_text { shift->_content(1, @_) }

sub ancestors { $_[0]->_collection(_ancestors($_[0]->tree)) }
sub ancestors { _select($_[1], $_[0]->_collection(_ancestors($_[0]->tree))) }

sub append { shift->_add(1, @_) }

Expand Down Expand Up @@ -75,18 +75,9 @@ sub attrs {
}

sub children {
my ($self, $type) = @_;

my @children;
my $xml = $self->xml;
for my $n (@{_nodes($self->tree)}) {

# Make sure child is the right type
next if $n->[0] ne 'tag' || (defined $type && $n->[1] ne $type);
push @children, $self->new->tree($n)->xml($xml);
}

return Mojo::Collection->new(@children);
my $self = shift;
return _select(shift,
$self->_collection(grep { $_->[0] eq 'tag' } @{_nodes($self->tree)}));
}

sub content_xml {
Expand All @@ -101,6 +92,12 @@ sub find {
return $self->_collection(@$results);
}

sub match {
my $self = shift;
return undef unless Mojo::DOM::CSS->new(tree => $self->tree)->match(@_);
return $self;
}

sub namespace {
my $self = shift;

Expand Down Expand Up @@ -306,6 +303,11 @@ sub _replace {
return $self->parent;
}

sub _select {
my $selector = shift;
return defined $selector ? shift->grep(sub { $_->match($selector) }) : shift;
}

sub _sibling {
my ($self, $next) = @_;

Expand Down Expand Up @@ -466,9 +468,11 @@ enabled by default.
=head2 ancestors
my $collection = $dom->ancestors;
my $collection = $dom->ancestors('div');
Return a L<Mojo::Collection> object containing the ancestors of this element
as L<Mojo::DOM> objects, similar to C<children>.
Find all ancestors of this element matching the CSS selector and return a
L<Mojo::Collection> object containing these elements as L<Mojo::DOM> objects.
All selectors from L<Mojo::DOM::CSS> are supported.
# List types of ancestor elements
say $dom->ancestors->type;
Expand Down Expand Up @@ -519,8 +523,9 @@ Element attributes.
my $collection = $dom->children;
my $collection = $dom->children('div');
Return a L<Mojo::Collection> object containing the children of this element as
L<Mojo::DOM> objects, similar to C<find>.
Find all children of this element matching the CSS selector and return a
L<Mojo::Collection> object containing these elements as L<Mojo::DOM> objects.
All selectors from L<Mojo::DOM::CSS> are supported.
# Show type of random child element
say $dom->children->shuffle->first->type;
Expand Down Expand Up @@ -549,6 +554,14 @@ L<Mojo::DOM::CSS> are supported.
my @headers = $dom->find('h1, h2, h3')->text->each;
my @links = $dom->find('a[href]')->attr('href')->each;
=head2 match
my $result = $dom->match('html title');
Match the CSS selector against this element and return it as a L<Mojo::DOM>
object or return C<undef> if it didn't match. All selectors from
L<Mojo::DOM::CSS> are supported.
=head2 namespace
my $namespace = $dom->namespace;
Expand Down
27 changes: 21 additions & 6 deletions lib/Mojo/DOM/CSS.pm
Expand Up @@ -33,6 +33,13 @@ my $TOKEN_RE = qr/
)?
/x;

sub match {
my $self = shift;
my $tree = $self->tree;
return undef if $tree->[0] eq 'root';
return $self->_match($self->_compile(shift), $tree, $tree);
}

sub select {
my $self = shift;

Expand All @@ -49,12 +56,7 @@ sub select {
# Tag
elsif ($type eq 'tag') {
unshift @queue, @$current[4 .. $#$current];

# Try all selectors with element
for my $part (@$pattern) {
push @results, $current and last
if $self->_combinator([reverse @$part], $current, $tree);
}
push @results, $current if $self->_match($pattern, $current, $tree);
}
}

Expand Down Expand Up @@ -203,6 +205,13 @@ sub _equation {
return $num;
}

sub _match {
my ($self, $pattern, $current, $tree) = @_;
$self->_combinator([reverse @$_], $current, $tree) and return 1
for @$pattern;
return undef;
}

sub _parent {
my ($self, $selectors, $current, $tree) = @_;
return undef unless my $parent = $current->[3];
Expand Down Expand Up @@ -608,6 +617,12 @@ carefully since it is very dynamic.
L<Mojo::DOM::CSS> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 match
my $success = $css->match('head > title');
Match CSS selector against first node in C<tree>.
=head2 select
my $results = $css->select('head > title');
Expand Down
12 changes: 8 additions & 4 deletions t/mojo/dom.t
Expand Up @@ -465,11 +465,11 @@ is $dom->at('book nons section')->namespace, '', 'no namespace';
is $dom->at('book nons section')->text, 'Nothing', 'right text';
is $dom->at('book meta number')->namespace, 'uri:isbn-ns', 'right namespace';
is $dom->at('book meta number')->text, '978-0596000271', 'right text';
is $dom->children('bk:book')->first->{xmlns}, 'uri:default-ns',
is $dom->children('bk\:book')->first->{xmlns}, 'uri:default-ns',
'right attribute';
is $dom->children('k:book')->first, undef, 'no result';
is $dom->children('book')->first, undef, 'no result';
is $dom->children('ook')->first, undef, 'no result';
is $dom->children('book')->first->{xmlns}, 'uri:default-ns', 'right attribute';
is $dom->children('k\:book')->first, undef, 'no result';
is $dom->children('ook')->first, undef, 'no result';
is $dom->at('k\:book'), undef, 'no result';
is $dom->at('ook'), undef, 'no result';
is $dom->at('[xmlns\:bk]')->{'xmlns:bk'}, 'uri:book-ns', 'right attribute';
Expand All @@ -480,6 +480,10 @@ is $dom->at('[bk]')->attr('bk'), '', 'no attribute';
is $dom->at('[bk]')->attr('k'), '', 'no attribute';
is $dom->at('[s\:bk]'), undef, 'no result';
is $dom->at('[k]'), undef, 'no result';
is $dom->at('number')->ancestors('meta')->first->{xmlns}, 'uri:meta-ns',
'right attribute';
ok !!$dom->at('nons')->match('book > nons'), 'element did match';
ok !$dom->at('title')->match('book > nons > section'), 'element did not match';

# Namespace with dot
$dom = Mojo::DOM->new(<<EOF);
Expand Down

0 comments on commit 04e4d24

Please sign in to comment.