NAME

HTML::Query - jQuery-like selection queries for HTML::Element

SYNOPSIS

Creating an HTML::Query object using the Query() constructor subroutine:

use HTML::Query 'Query';

# using named parameters
$q = Query( text  => $text  );          # HTML text
$q = Query( file  => $file  );          # HTML file
$q = Query( tree  => $tree  );          # HTML::Element object
$q = Query( query => $query );          # HTML::Query object
$q = Query(
    text  => $text1,                    # or any combination
    text  => $text2,                    # of the above
    file  => $file1,
    file  => $file2,
    tree  => $tree,
    query => $query,
);

# passing elements as positional arguments
$q = Query( $tree );                    # HTML::Element object(s)
$q = Query( $tree1, $tree2, $tree3, ... );

# or from one or more existing queries
$q = Query( $query1 );                  # HTML::Query object(s)
$q = Query( $query1, $query2, $query3, ... );

# or a mixture
$q = Query( $tree1, $query1, $tree2, $query2 );

# the final argument (in all cases) can be a selector
my $spec = 'ul.menu li a';              # <ul class="menu">..<li>..<a>

$q = Query( $tree, $spec );
$q = Query( $query, $spec );
$q = Query( $tree1, $tree2, $query1, $query2, $spec );
$q = Query( text  => $text,  $spec );
$q = Query( file  => $file,  $spec );
$q = Query( tree  => $tree,  $spec );
$q = Query( query => $query, $spec );
$q = Query(
    text => $text,
    file => $file,
    # ...etc...
    $spec
);

Or using the OO new() constructor method (which the Query() subroutine maps onto):

use HTML::Query;

$q = HTML::Query->new(
    # accepts the same arguments as Query()
)

Or by monkey-patching a query() method into HTML::Element.

use HTML::Query 'query';                # note lower case 'q'
use HTML::TreeBuilder;

# build a tree
my $tree = HTML::TreeBuilder->new;
$tree->parse_file($filename);

# call the query() method on any element
my $query = $tree->query($spec);

Once you have a query, you can start selecting elements:

@r = $q->query('a')->get_elements();            # all <a>...</a> elements
@r = $q->query('a#menu')->get_elements();       # all <a> with "menu" id
@r = $q->query('#menu')->get_elements();        # all elements with "menu" id
@r = $q->query('a.menu')->get_elements();       # all <a> with "menu" class
@r = $q->query('.menu')->get_elements();        # all elements with "menu" class
@r = $q->query('a[href]')->get_elements();      # all <a> with 'href' attr
@r = $q->query('a[href=foo]')->get_elements();  # all <a> with 'href="foo"' attr

# you can specify elements within elements...
@r = $q->query('ul.menu li a')->get_elements(); # <ul class="menu">...<li>...<a>

# and use commas to delimit multiple path specs for different elements
@r = $q->query('table tr td a, form input[type=submit]')->get_elements();

# query() in scalar context returns a new query
$r = $q->query('table')->get_elements();;       # find all tables
$s = $r->query('tr')->get_elements();           # find all rows in all those tables
$t = $s->query('td')->get_elements();           # and all cells in those rows...

Inspecting query elements:

# get number of elements in query
my $size  = $q->size

# get first/last element in query
my $first = $q->first;
my $last  = $q->last;

# convert query to list or list ref of HTML::Element objects
my $list = $q->list;            # list ref in scalar context
my @list = $q->list;            # list in list context

All other methods are mapped onto the HTML::Element objects in the query:

print $query->as_trimmed_text;  # print trimmed text for each element
print $query->as_HTML;          # print each element as HTML
$query->delete;                 # call delete() on each element

DESCRIPTION

The HTML::Query module is an add-on for the HTML::Tree module set. It provides a simple way to select one or more elements from a tree using a query syntax inspired by jQuery. This selector syntax will be reassuringly familiar to anyone who has ever written a CSS selector.

HTML::Query is not an attempt to provide a complete (or even near-complete) implementation of jQuery in Perl (see Ingy's pQuery module for a more ambitious attempt at that). Rather, it borrows some of the tried and tested selector syntax from jQuery (and CSS) that can easily be mapped onto the look_down() method provided by the HTML::Element module.

Creating a Query

The easiest way to create a query is using the exportable Query() subroutine.

use HTML::Query 'Query';        # note capital 'Q'

It accepts a text or file named parameter and will create an HTML::Query object from the HTML source text or file, respectively.

my $query = Query( text => $text );
my $query = Query( file => $file );

This delegates to HTML::TreeBuilder to parse the HTML into a tree of HTML::Element objects. The root element returned is then wrapped in an HTML::Query object.

If you already have one or more HTML::Element objects that you want to query then you can pass them to the Query() subroutine as arguments. For example, you can explicitly use HTML::TreeBuilder to parse an HTML document into a tree:

use HTML::TreeBuilder;
my $tree = HTML::TreeBuilder->new;
$tree->parse_file($filename);

And then create an HTML::Query object for the tree either using an explicit tree named parameter:

my $query = Query( tree => $tree );

Or implicitly using positional arguments.

my $query = Query( $tree );

If you want to query across multiple elements, then pass each one as a positional argument.

my $query = Query( $tree1, $tree2, $tree3 );

You can also create a new query from one or more existing queries,

my $query = Query( query => $query );   # named parameter
my $query = Query( $query1, $query2 );  # positional arguments.

You can mix and match these different parameters and positional arguments to create a query across several different sources.

$q = Query(
    text  => $text1,
    text  => $text2,
    file  => $file1,
    file  => $file2,
    tree  => $tree,
    query => $query,
);

The Query() subroutine is a simple wrapper around the new() constructor method. You can instantiate your objects manually if you prefer. The new() method accepts the same arguments as for the Query() subroutine (in fact, the Query() subroutine simply forwards all arguments to the new() method).

use HTML::Query;

my $query = HTML::Query->new(
    # same argument format as for Query()
);

A final way to use HTML::Query is to have it add a query() method to HTML::Element. The query import hook (all lower case) can be specified to make this so.

use HTML::Query 'query';                # note lower case 'q'
use HTML::TreeBuilder;

my $tree = HTML::TreeBuilder->new;
$tree->parse_file($filename);

# now all HTML::Elements have a query() method
my @items = $tree->query('ul li')->get_elements();  # find all list items

This approach, often referred to as monkey-patching, should be used carefully and sparingly. It involves a violation of HTML::Element's namespace that could have unpredictable results with a future version of the module (e.g. one which defines its own query() method that does something different). Treat it as something that is great to get a quick job done right now, but probably not something to be used in production code without careful consideration of the implications.

Selecting Elements

Having created an HTML::Query object by one of the methods outlined above, you can now fetch descendant elements in the tree using a simple query syntax. For example, to fetch all the <a> elements in the tree, you can write:

@links = $query->query('a')->get_elements();

Or, if you want the elements that have a specific class attribute defined with a value of, say menu, you can write:

@links = $query->query('a.menu')->get_elements();

More generally, you can look for the existence of any attribute and optionally provide a specific value for it.

@links = $query->query('a[href]')->get_elements();            # any href attribute
@links = $query->query('a[href=index.html]')->get_elements(); # specific value

You can also find an element (or elements) by specifying an id.

@links = $query->query('#menu')->get_elements();         # any element with id="menu"
@links = $query->query('ul#menu')->get_elements();       # ul element with id="menu"

You can provide multiple selection criteria to find elements within elements within elements, and so on. For example, to find all links in a menu, you can write:

# matches: <ul class="menu"> <li> <a>
@links = $query->query('ul.menu li a')->get_elements();

You can separate different criteria using commas. For example, to fetch all table rows and span elements with a foo class:

@elems = $query->('table tr, span.foo')->get_elements();

Query Results

When called in list context, as shown in the examples above, the query() method returns a list of HTML::Element objects matching the search criteria. In scalar context, the query() method returns a new HTML::Query object containing the HTML::Element objects found. You can then call the query() method against that object to further refine the query. The query() method applies the selection to all elements stored in the query.

my $tables = $query->query('table');             # query for tables
my $rows   = $tables->query('tr');               # requery for all rows in those tables
my $cells  = $rows->query('td')->get_elements(); # return back all the cells in those rows

Inspection Methods

The size() method returns the number of elements in the query. The first() and last() methods return the first and last items in the query, respectively.

if ($query->size) {
    print "from ", $query->first->as_trimmed_text, " to ", $query->last->as_trimmed_text;
}

If you want to extract the HTML::Element objects from the query you can call the list() method. This returns a list of HTML::Element objects in list context, or a reference to a list in scalar context.

@elems = $query->list;
$elems = $query->list;

Element Methods

Any other methods are automatically applied to each element in the list. For example, to call the as_trimmed_text() method on all the HTML::Element objects in the query, you can write:

print $query->as_trimmed_text;

In list context, this method returns a list of the return values from calling the method on each element. In scalar context it returns a reference to a list of return values.

@text_blocks = $query->as_trimmed_text;
$text_blocks = $query->as_trimmed_text;

See HTML::Element for further information on the methods it provides.

QUERY SYNTAX

Basic Selectors

element

Matches all elements of a particular type.

@elems = $query->query('table')->get_elements();     # <table>

#id

Matches all elements with a specific id attribute.

@elems = $query->query('#menu')->get_elements()     # <ANY id="menu">

This can be combined with an element type:

@elems = $query->query('ul#menu')->get_elements();  # <ul id="menu">

.class

Matches all elements with a specific class attribute.

@elems = $query->query('.info')->get_elements();     # <ANY class="info">

This can be combined with an element type and/or element id:

@elems = $query->query('p.info')->get_elements();     # <p class="info">
@elems = $query->query('p#foo.info')->get_elements(); # <p id="foo" class="info">
@elems = $query->query('#foo.info')->get_elements();  # <ANY id="foo" class="info">

The selectors listed above can be combined in a whitespace delimited sequence to select down through a hierarchy of elements. Consider the following table:

<table class="search">
  <tr class="result">
    <td class="value">WE WANT THIS ELEMENT</td>
  </tr>
  <tr class="result">
    <td class="value">AND THIS ONE</td>
  </tr>
  ...etc..
</table>

To locate the cells that we're interested in, we can write:

@elems = $query->query('table.search tr.result td.value')->get_elements();

Attribute Selectors

W3C CSS 2 specification defines new constructs through which to select based on specific attributes within elements. See the following link for the spec: http://www.w3.org/TR/css3-selectors/#attribute-selectors

[attr]

Matches elements that have the specified attribute, including any where the attribute has no value.

@elems = $query->query('[href]')->get_elements();        # <ANY href="...">

This can be combined with any of the above selectors. For example:

@elems = $query->query('a[href]')->get_elements();       # <a href="...">
@elems = $query->query('a.menu[href]')->get_elements();  # <a class="menu" href="...">

You can specify multiple attribute selectors. Only those elements that match all of them will be selected.

@elems = $query->query('a[href][rel]')->get_elements();  # <a href="..." rel="...">

[attr=value]

Matches elements that have an attribute set to a specific value. The value can be quoted in either single or double quotes, or left unquoted.

@elems = $query->query('[href=index.html]')->get_elements();
@elems = $query->query('[href="index.html"]')->get_elements();
@elems = $query->query("[href='index.html']")->get_elements();

You can specify multiple attribute selectors. Only those elements that match all of them will be selected.

@elems = $query->query('a[href=index.html][rel=home]')->get_elements();

[attr|=value]

Matches any element X whose foo attribute has a hyphen-separated list of values beginning (from the left) with bar. The value can be quoted in either single or double quotes, or left unquoted.

@elems = $query->query('[lang|=en]')->get_elements();
@elems = $query->query('p[class|="example"]')->get_elements();
@elems = $query->query("img[alt|='fig']")->get_elements();

You can specify multiple attribute selectors. Only those elements that match all of them will be selected.

@elems = $query->query('p[class|="external"][lang|="en"]')->get_elements();

[attr~=value]

Matches any element X whose foo attribute value is a list of space-separated values, one of which is exactly equal to bar. The value can be quoted in either single or double quotes, or left unquoted.

@elems = $query->query('[lang~=en]')->get_elements();
@elems = $query->query('p[class~="example"]')->get_elements();
@elems = $query->query("img[alt~='fig']")->get_elements();

You can specify multiple attribute selectors. Only those elements that match all of them will be selected.

@elems = $query->query('p[class~="external"][lang~="en"]')->get_elements();

KNOWN BUG: you can't have a ] character in the attribute value because it confuses the query parser. Fixing this is TODO.

Universal Selector

W3C CSS 2 specification defines a new construct through which to select any element within the document below a given hierarchy.

http://www.w3.org/TR/css3-selectors/#universal-selector

@elems = $query->query('*')->get_elements();

Combinator Selectors

W3C CSS 2 specification defines new constructs through which to select based on heirarchy with the DOM. See the following link for the spec: http://www.w3.org/TR/css3-selectors/#combinators

Immediate Descendents (children)

When you combine selectors with whitespace elements are selected if they are descended from the parent in some way. But if you just want to select the children (and not the grandchildren, great-grandchildren, etc) then you can combine the selectors with the > character.

@elems = $query->query('a > img')->get_elements();

Non-Immediate Descendents

If you just want any descendents that aren't children then you can combine selectors with the * character.

@elems = $query->query('div * a')->get_elements();

Immediate Siblings

If you want to use a sibling relationship then you can can join selectors with the + character.

@elems = $query->query('img + span')->get_elements();

Pseudo-classes

W3C CSS 2 and CSS 3 specifications define new concepts of pseudo-classes to permit formatting based on information that lies outside the document tree. See the following link for the most recent spec: http://www.w3.org/TR/css3-selectors/#pseudo-classes

HTML::Query currently has limited support for CSS 2, and no support for CSS 3.

Patches are *highly* encouraged to help add support here.

-child pseudo-classes

If you want to return child elements within a certain position then -child pseudo-classes (:first-child, :last-child) are what you're looking for.

@elems = $query->query('table td:first-child')->get_elements;

Unsupported.

The :link pseudo-class is to be implemented, currently unsupported.

It is not possible to locate :visited outside of a browser context due to it's dynamic nature.

Dynamic pseudo-classes

Unsupported.

It is not possible to locate these classes(:hover, :active, :focus) outside of a browser context due to their dynamic nature.

Language pseudo-class

Unsupported.

Functionality for the :lang pseudo-class is largely replicated by using an attribute selector for lang combined with a universal selector query.

If this is insufficient I'd love to see a patch adding support for it.

Other pseudo-classes

W3C CSS 3 added a number of new behaviors that need support. At this time there is no support for them, but we should work on adding support.

Patches are very welcome.

Pseudo-elements

W3C CSS 2 and CSS 3 specification defines new concepts of pseudo-elements to permit formatting based on information that lies outside the document tree. See the following link for the most recent spec: http://www.w3.org/TR/css3-selectors/#pseudo-elements

At this time there is no support for pseudo-elements, but we are working on adding support.

Patches are very welcome.

Combining Selectors

You can combine basic and hierarchical selectors into a single query by separating each part with a comma. The query will select all matching elements for each of the comma-delimited selectors. For example, to find all a, b and i elements in a tree:

@elems = $query->query('a, b, i')->get_elements();

Each of these selectors can be arbitrarily complex.

@elems = $query->query(
    'table.search[width=100%] tr.result[valign=top] td.value,
     form.search input[type=submit],
     a[href=index.html]'
)->get_elements();

EXPORT HOOKS

Query

The Query() constructor subroutine (note the capital letter) can be exported as a convenient way to create HTML::Query objects. It simply forwards all arguments to the new() constructor method.

use HTML::Query 'Query';

my $query = Query( file => $file, 'ul.menu li a' );

query

The query() export hook can be called to monkey-patch a query() method into the HTML::Element module.

This is considered questionable behaviour in polite society which regards it as a violation of the inner sanctity of the HTML::Element.

But if you're the kind of person that doesn't mind a bit of occasional namespace abuse for the sake of getting the job done, then go right ahead. Just don't blame me if it all blows up later.

use HTML::Query 'query';                # note lower case 'q'
use HTML::TreeBuilder;

# build a tree
my $tree = HTML::TreeBuilder->new;
$tree->parse_file($filename);

# call the query() method on any element
my $query = $tree->query('ul li a');

METHODS

The HTML::Query object is a subclass of Badger::Base and inherits all of its method.

new(@elements,$selector)

This constructor method is used to create a new HTML::Query object. It expects a list of any number (including zero) of HTML::Element or HTML::Query objects.

# single HTML::Element object
my $query = HTML::Query->new($elem);

# multiple element object
my $query = HTML::Query->new($elem1, $elem2, $elem3, ...);

# copy elements from an existing query
my $query = HTML::Query->new($another_query);

# copy elements from several queries
my $query = HTML::Query->new($query1, $query2, $query3);

# or a mixture
my $query = HTML::Query->new($elem1, $query1, $elem2, $query3);

You can also use named parameters to specify an alternate source for a element.

$query = HTML::Query->new( file => $file );
$query = HTML::Query->new( text => $text );

In this case, the HTML::TreeBuilder module is used to parse the source file or text into a tree of HTML::Element objects.

For the sake of completeness, you can also specify element trees and queries using named parameters:

$query = HTML::Query->new( tree  => $tree );
$query = HTML::Query->new( query => $query );

You can freely mix and match elements, queries and named sources. The query will be constructed as an aggregate across them all.

$q = HTML::Query->new(
    text  => $text1,
    text  => $text2,
    file  => $file1,
    file  => $file2,
    tree  => $tree,
    query => $query1,
);

The final, optional argument can be a selector specification. This is immediately passed to the query() method which will return a new query with only those elements selected.

my $spec = 'ul.menu li a';              # <ul class="menu">..<li>..<a>

my $query = HTML::Query->new( $tree, $spec );
my $query = HTML::Query->new( text => $text, $spec );
my $query = HTML::Query->new(
    text => $text,
    file => $file,
    $spec
);

The list of arguments can also be passed by reference to a list.

my $query = HTML::Query->new(\@args);

query($spec)

This method locates the descendant elements identified by the $spec argument for each element in the query. It then interally stores the results for requerying or return. See get_elements().

my $query = HTML::Query->new(\@args);
my $results = $query->query($spec);

See "QUERY SYNTAX" for the permitted syntax of the $spec argument.

get_elements()

This method returns the stored results from a query. In list context it returns a list of matching HTML::Element objects. In scalar context it returns a reference to the results array.

my $query = HTML::Query->new(\@args);
my $results = $query->query($spec);

my @elements  = $results->query($spec)->get_elements();
my $elements  = $results->query($spec)->get_elements();

get_specificity()

Calculate the specificity for any given passed selector, a critical factor in determining how best to apply the cascade

A selector's specificity is calculated as follows:

* count the number of ID attributes in the selector (= a) * count the number of other attributes and pseudo-classes in the selector (= b) * count the number of element names in the selector (= c) * ignore pseudo-elements.

The specificity is based only on the form of the selector. In particular, a selector of the form "[id=p33]" is counted as an attribute selector (a=0, b=0, c=1, d=0), even if the id attribute is defined as an "ID" in the source document's DTD.

See the following spec for additional details: http://www.w3.org/TR/CSS21/cascade.html#specificity

size()

Returns the number of elements in the query.

first()

Returns the first element in the query.

my $elem = $query->first;

If the query is empty then an exception will be thrown. If you would rather have an undefined value returned then you can use the try method inherited from Badger::Base. This effectively wraps the call to first() in an eval block to catch any exceptions thrown.

my $elem = $query->try('first') || warn "no first element\n";

last()

Similar to first(), but returning the last element in the query.

my $elem = $query->last;

list()

Returns a list of the HTML::Element object in the query in list context, or a reference to a list in scalar context.

my @elems = $query->list;
my $elems = $query->list;

AUTOLOAD

The AUTOLOAD method maps any other method calls to the HTML::Element objects in the list. When called in list context it returns a list of the values returned from calling the method on each element. In scalar context it returns a reference to a list of return values.

my @text_blocks = $query->as_trimmed_text;
my $text_blocks = $query->as_trimmed_text;

KNOWN BUGS

Attribute Values

It is not possible to use ] in an attribute value. This is due to a limitation in the parser which will be fixed RSN.

AUTHOR

Andy Wardley http://wardley.org

MAINTAINER

Kevin Kamel <kamelkev@mailermailer.com>

CONTRIBUTORS

Vivek Khera <vivek@khera.org> Michael Peters <wonko@cpan.org> David Gray <cpan@doesntsuck.com>

COPYRIGHT

Copyright (C) 2010 Andy Wardley. All Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

HTML::Tree, HTML::Element, HTML::TreeBuilder, pQuery, http://jQuery.com/