NAME

   Mojo::DOM58 - Minimalistic HTML/XML DOM parser with CSS selectors

SYNOPSIS

     use Mojo::DOM58;

     # Parse
     my $dom = Mojo::DOM58->new('<div><p id="a">Test</p><p id="b">123</p></div>');

     # Find
     say $dom->at('#b')->text;
     say $dom->find('p')->map('text')->join("\n");
     say $dom->find('[id]')->map(attr => 'id')->join("\n");

     # Iterate
     $dom->find('p[id]')->reverse->each(sub { say $_->{id} });

     # Loop
     for my $e ($dom->find('p[id]')->each) {
       say $e->{id}, ':', $e->text;
     }

     # Modify
     $dom->find('div p')->last->append('<p id="c">456</p>');
     $dom->find(':not(p)')->map('strip');

     # Render
     say "$dom";

DESCRIPTION

   Mojo::DOM58 is a minimalistic and relaxed pure-perl HTML/XML DOM parser
   based on Mojo::DOM. It supports the HTML Living Standard
   <https://html.spec.whatwg.org/> and Extensible Markup Language (XML)
   1.0 <http://www.w3.org/TR/xml/>, and matching based on CSS3 selectors
   <http://www.w3.org/TR/selectors/>. It will even try to interpret broken
   HTML and XML, so you should not use it for validation.

FORK INFO

   Mojo::DOM58 is a fork of Mojo::DOM and tracks features and fixes to
   stay closely compatible with upstream. It differs only in the
   standalone format and compatibility with Perl 5.8. Any bugs or patches
   not related to these changes should be reported directly to the
   Mojolicious issue tracker.

   This release of Mojo::DOM58 is up to date with version 7.36 of
   Mojolicious.

NODES AND ELEMENTS

   When we parse an HTML/XML fragment, it gets turned into a tree of
   nodes.

     <!DOCTYPE html>
     <html>
       <head><title>Hello</title></head>
       <body>World!</body>
     </html>

   There are currently eight different kinds of nodes, cdata, comment,
   doctype, pi, raw, root, tag and text. Elements are nodes of the type
   tag.

     root
     |- doctype (html)
     +- tag (html)
        |- tag (head)
        |  +- tag (title)
        |     +- raw (Hello)
        +- tag (body)
           +- text (World!)

   While all node types are represented as Mojo::DOM58 objects, some
   methods like "attr" and "namespace" only apply to elements.

CASE-SENSITIVITY

   Mojo::DOM58 defaults to HTML semantics, that means all tags and
   attribute names are lowercased and selectors need to be lowercase as
   well.

     # HTML semantics
     my $dom = Mojo::DOM58->new('<P ID="greeting">Hi!</P>');
     say $dom->at('p[id]')->text;

   If an XML declaration is found, the parser will automatically switch
   into XML mode and everything becomes case-sensitive.

     # XML semantics
     my $dom = Mojo::DOM58->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
     say $dom->at('P[ID]')->text;

   HTML or XML semantics can also be forced with the "xml" method.

     # Force HTML semantics
     my $dom = Mojo::DOM58->new->xml(0)->parse('<P ID="greeting">Hi!</P>');
     say $dom->at('p[id]')->text;

     # Force XML semantics
     my $dom = Mojo::DOM58->new->xml(1)->parse('<P ID="greeting">Hi!</P>');
     say $dom->at('P[ID]')->text;

SELECTORS

   Mojo::DOM58 uses a CSS selector engine based on Mojo::DOM::CSS. All CSS
   selectors that make sense for a standalone parser are supported.

   *

     Any element.

       my $all = $dom->find('*');

   E

     An element of type E.

       my $title = $dom->at('title');

   E[foo]

     An E element with a foo attribute.

       my $links = $dom->find('a[href]');

   E[foo="bar"]

     An E element whose foo attribute value is exactly equal to bar.

       my $case_sensitive = $dom->find('input[type="hidden"]');
       my $case_sensitive = $dom->find('input[type=hidden]');

   E[foo="bar" i]

     An E element whose foo attribute value is exactly equal to any
     (ASCII-range) case-permutation of bar. Note that this selector is
     EXPERIMENTAL and might change without warning!

       my $case_insensitive = $dom->find('input[type="hidden" i]');
       my $case_insensitive = $dom->find('input[type=hidden i]');
       my $case_insensitive = $dom->find('input[class~="foo" i]');

     This selector is part of Selectors Level 4
     <http://dev.w3.org/csswg/selectors-4>, which is still a work in
     progress.

   E[foo~="bar"]

     An E element whose foo attribute value is a list of
     whitespace-separated values, one of which is exactly equal to bar.

       my $foo = $dom->find('input[class~="foo"]');
       my $foo = $dom->find('input[class~=foo]');

   E[foo^="bar"]

     An E element whose foo attribute value begins exactly with the string
     bar.

       my $begins_with = $dom->find('input[name^="f"]');
       my $begins_with = $dom->find('input[name^=f]');

   E[foo$="bar"]

     An E element whose foo attribute value ends exactly with the string
     bar.

       my $ends_with = $dom->find('input[name$="o"]');
       my $ends_with = $dom->find('input[name$=o]');

   E[foo*="bar"]

     An E element whose foo attribute value contains the substring bar.

       my $contains = $dom->find('input[name*="fo"]');
       my $contains = $dom->find('input[name*=fo]');

   E:root

     An E element, root of the document.

       my $root = $dom->at(':root');

   E:nth-child(n)

     An E element, the n-th child of its parent.

       my $third = $dom->find('div:nth-child(3)');
       my $odd   = $dom->find('div:nth-child(odd)');
       my $even  = $dom->find('div:nth-child(even)');
       my $top3  = $dom->find('div:nth-child(-n+3)');

   E:nth-last-child(n)

     An E element, the n-th child of its parent, counting from the last
     one.

       my $third    = $dom->find('div:nth-last-child(3)');
       my $odd      = $dom->find('div:nth-last-child(odd)');
       my $even     = $dom->find('div:nth-last-child(even)');
       my $bottom3  = $dom->find('div:nth-last-child(-n+3)');

   E:nth-of-type(n)

     An E element, the n-th sibling of its type.

       my $third = $dom->find('div:nth-of-type(3)');
       my $odd   = $dom->find('div:nth-of-type(odd)');
       my $even  = $dom->find('div:nth-of-type(even)');
       my $top3  = $dom->find('div:nth-of-type(-n+3)');

   E:nth-last-of-type(n)

     An E element, the n-th sibling of its type, counting from the last
     one.

       my $third    = $dom->find('div:nth-last-of-type(3)');
       my $odd      = $dom->find('div:nth-last-of-type(odd)');
       my $even     = $dom->find('div:nth-last-of-type(even)');
       my $bottom3  = $dom->find('div:nth-last-of-type(-n+3)');

   E:first-child

     An E element, first child of its parent.

       my $first = $dom->find('div p:first-child');

   E:last-child

     An E element, last child of its parent.

       my $last = $dom->find('div p:last-child');

   E:first-of-type

     An E element, first sibling of its type.

       my $first = $dom->find('div p:first-of-type');

   E:last-of-type

     An E element, last sibling of its type.

       my $last = $dom->find('div p:last-of-type');

   E:only-child

     An E element, only child of its parent.

       my $lonely = $dom->find('div p:only-child');

   E:only-of-type

     An E element, only sibling of its type.

       my $lonely = $dom->find('div p:only-of-type');

   E:empty

     An E element that has no children (including text nodes).

       my $empty = $dom->find(':empty');

   E:checked

     A user interface element E which is checked (for instance a
     radio-button or checkbox).

       my $input = $dom->find(':checked');

   E.warning

     An E element whose class is "warning".

       my $warning = $dom->find('div.warning');

   E#myid

     An E element with ID equal to "myid".

       my $foo = $dom->at('div#foo');

   E:not(s1, s2)

     An E element that does not match either compound selector s1 or
     compound selector s2. Note that support for compound selectors is
     EXPERIMENTAL and might change without warning!

       my $others = $dom->find('div p:not(:first-child, :last-child)');

     Support for compound selectors was added as part of Selectors Level 4
     <http://dev.w3.org/csswg/selectors-4>, which is still a work in
     progress.

   E:matches(s1, s2)

     An E element that matches compound selector s1 and/or compound
     selector s2. Note that this selector is EXPERIMENTAL and might change
     without warning!

       my $headers = $dom->find(':matches(section, article, aside, nav) h1');

     This selector is part of Selectors Level 4
     <http://dev.w3.org/csswg/selectors-4>, which is still a work in
     progress.

   E F

     An F element descendant of an E element.

       my $headlines = $dom->find('div h1');

   E > F

     An F element child of an E element.

       my $headlines = $dom->find('html > body > div > h1');

   E + F

     An F element immediately preceded by an E element.

       my $second = $dom->find('h1 + h2');

   E ~ F

     An F element preceded by an E element.

       my $second = $dom->find('h1 ~ h2');

   E, F, G

     Elements of type E, F and G.

       my $headlines = $dom->find('h1, h2, h3');

   E[foo=bar][bar=baz]

     An E element whose attributes match all following attribute
     selectors.

       my $links = $dom->find('a[foo^=b][foo$=ar]');

OPERATORS

   Mojo::DOM58 overloads the following operators.

array

     my @nodes = @$dom;

   Alias for "child_nodes".

     # "<!-- Test -->"
     $dom->parse('<!-- Test --><b>123</b>')->[0];

bool

     my $bool = !!$dom;

   Always true.

hash

     my %attrs = %$dom;

   Alias for "attr".

     # "test"
     $dom->parse('<div id="test">Test</div>')->at('div')->{id};

stringify

     my $str = "$dom";

   Alias for "to_string".

METHODS

   Mojo::DOM58 implements the following methods.

new

     my $dom = Mojo::DOM58->new;
     my $dom = Mojo::DOM58->new('<foo bar="baz">I ♥ Mojo::DOM58!</foo>');

   Construct a new scalar-based Mojo::DOM58 object and "parse" HTML/XML
   fragment if necessary.

all_text

     my $text = $dom->all_text;

   Extract text content from all descendant nodes of this element.

     # "foo\nbarbaz\n"
     $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text;

ancestors

     my $collection = $dom->ancestors;
     my $collection = $dom->ancestors('div ~ p');

   Find all ancestor elements of this node matching the CSS selector and
   return a collection containing these elements as Mojo::DOM58 objects.
   All selectors listed in "SELECTORS" are supported.

     # List tag names of ancestor elements
     say $dom->ancestors->map('tag')->join("\n");

append

     $dom = $dom->append('<p>I ♥ Mojo::DOM58!</p>');

   Append HTML/XML fragment to this node (for all node types other than
   root).

     # "<div><h1>Test</h1><h2>123</h2></div>"
     $dom->parse('<div><h1>Test</h1></div>')
       ->at('h1')->append('<h2>123</h2>')->root;

     # "<p>Test 123</p>"
     $dom->parse('<p>Test</p>')->at('p')
       ->child_nodes->first->append(' 123')->root;

append_content

     $dom = $dom->append_content('<p>I ♥ Mojo::DOM58!</p>');

   Append HTML/XML fragment (for root and tag nodes) or raw content to
   this node's content.

     # "<div><h1>Test123</h1></div>"
     $dom->parse('<div><h1>Test</h1></div>')
       ->at('h1')->append_content('123')->root;

     # "<!-- Test 123 --><br>"
     $dom->parse('<!-- Test --><br>')
       ->child_nodes->first->append_content('123 ')->root;

     # "<p>Test<i>123</i></p>"
     $dom->parse('<p>Test</p>')->at('p')->append_content('<i>123</i>')->root;

at

     my $result = $dom->at('div ~ p');

   Find first descendant element of this element matching the CSS selector
   and return it as a Mojo::DOM58 object, or undef if none could be found.
   All selectors listed in "SELECTORS" are supported.

     # Find first element with "svg" namespace definition
     my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};

attr

     my $hash = $dom->attr;
     my $foo  = $dom->attr('foo');
     $dom     = $dom->attr({foo => 'bar'});
     $dom     = $dom->attr(foo => 'bar');

   This element's attributes.

     # Remove an attribute
     delete $dom->attr->{id};

     # Attribute without value
     $dom->attr(selected => undef);

     # List id attributes
     say $dom->find('*')->map(attr => 'id')->compact->join("\n");

child_nodes

     my $collection = $dom->child_nodes;

   Return a collection containing all child nodes of this element as
   Mojo::DOM58 objects.

     # "<p><b>123</b></p>"
     $dom->parse('<p>Test<b>123</b></p>')->at('p')->child_nodes->first->remove;

     # "<!DOCTYPE html>"
     $dom->parse('<!DOCTYPE html><b>123</b>')->child_nodes->first;

     # " Test "
     $dom->parse('<b>123</b><!-- Test -->')->child_nodes->last->content;

children

     my $collection = $dom->children;
     my $collection = $dom->children('div ~ p');

   Find all child elements of this element matching the CSS selector and
   return a collection containing these elements as Mojo::DOM58 objects.
   All selectors listed in "SELECTORS" are supported.

     # Show tag name of random child element
     say $dom->children->shuffle->first->tag;

content

     my $str = $dom->content;
     $dom    = $dom->content('<p>I ♥ Mojo::DOM58!</p>');

   Return this node's content or replace it with HTML/XML fragment (for
   root and tag nodes) or raw content.

     # "<b>Test</b>"
     $dom->parse('<div><b>Test</b></div>')->at('div')->content;

     # "<div><h1>123</h1></div>"
     $dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('123')->root;

     # "<p><i>123</i></p>"
     $dom->parse('<p>Test</p>')->at('p')->content('<i>123</i>')->root;

     # "<div><h1></h1></div>"
     $dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('')->root;

     # " Test "
     $dom->parse('<!-- Test --><br>')->child_nodes->first->content;

     # "<div><!-- 123 -->456</div>"
     $dom->parse('<div><!-- Test -->456</div>')
       ->at('div')->child_nodes->first->content(' 123 ')->root;

descendant_nodes

     my $collection = $dom->descendant_nodes;

   Return a collection containing all descendant nodes of this element as
   Mojo::DOM58 objects.

     # "<p><b>123</b></p>"
     $dom->parse('<p><!-- Test --><b>123<!-- 456 --></b></p>')
       ->descendant_nodes->grep(sub { $_->type eq 'comment' })
       ->map('remove')->first;

     # "<p><b>test</b>test</p>"
     $dom->parse('<p><b>123</b>456</p>')
       ->at('p')->descendant_nodes->grep(sub { $_->type eq 'text' })
       ->map(content => 'test')->first->root;

find

     my $collection = $dom->find('div ~ p');

   Find all descendant elements of this element matching the CSS selector
   and return a collection containing these elements as Mojo::DOM58
   objects. All selectors listed in "SELECTORS" are supported.

     # Find a specific element and extract information
     my $id = $dom->find('div')->[23]{id};

     # Extract information from multiple elements
     my @headers = $dom->find('h1, h2, h3')->map('text')->each;

     # Count all the different tags
     my $hash = $dom->find('*')->reduce(sub { $a->{$b->tag}++; $a }, {});

     # Find elements with a class that contains dots
     my @divs = $dom->find('div.foo\.bar')->each;

following

     my $collection = $dom->following;
     my $collection = $dom->following('div ~ p');

   Find all sibling elements after this node matching the CSS selector and
   return a collection containing these elements as Mojo::DOM58 objects.
   All selectors listed in "SELECTORS" are supported.

     # List tags of sibling elements after this node
     say $dom->following->map('tag')->join("\n");

following_nodes

     my $collection = $dom->following_nodes;

   Return a collection containing all sibling nodes after this node as
   Mojo::DOM58 objects.

     # "C"
     $dom->parse('<p>A</p><!-- B -->C')->at('p')->following_nodes->last->content;

matches

     my $bool = $dom->matches('div ~ p');

   Check if this element matches the CSS selector. All selectors listed in
   "SELECTORS" are supported.

     # True
     $dom->parse('<p class="a">A</p>')->at('p')->matches('.a');
     $dom->parse('<p class="a">A</p>')->at('p')->matches('p[class]');

     # False
     $dom->parse('<p class="a">A</p>')->at('p')->matches('.b');
     $dom->parse('<p class="a">A</p>')->at('p')->matches('p[id]');

namespace

     my $namespace = $dom->namespace;

   Find this element's namespace, or return undef if none could be found.

     # Find namespace for an element with namespace prefix
     my $namespace = $dom->at('svg > svg\:circle')->namespace;

     # Find namespace for an element that may or may not have a namespace prefix
     my $namespace = $dom->at('svg > circle')->namespace;

next

     my $sibling = $dom->next;

   Return Mojo::DOM58 object for next sibling element, or undef if there
   are no more siblings.

     # "<h2>123</h2>"
     $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;

next_node

     my $sibling = $dom->next_node;

   Return Mojo::DOM58 object for next sibling node, or undef if there are
   no more siblings.

     # "456"
     $dom->parse('<p><b>123</b><!-- Test -->456</p>')
       ->at('b')->next_node->next_node;

     # " Test "
     $dom->parse('<p><b>123</b><!-- Test -->456</p>')
       ->at('b')->next_node->content;

parent

     my $parent = $dom->parent;

   Return Mojo::DOM58 object for parent of this node, or undef if this
   node has no parent.

     # "<b><i>Test</i></b>"
     $dom->parse('<p><b><i>Test</i></b></p>')->at('i')->parent;

parse

     $dom = $dom->parse('<foo bar="baz">I ♥ Mojo::DOM58!</foo>');

   Parse HTML/XML fragment.

     # Parse XML
     my $dom = Mojo::DOM58->new->xml(1)->parse('<foo>I ♥ Mojo::DOM58!</foo>');

preceding

     my $collection = $dom->preceding;
     my $collection = $dom->preceding('div ~ p');

   Find all sibling elements before this node matching the CSS selector
   and return a collection containing these elements as Mojo::DOM58
   objects. All selectors listed in "SELECTORS" are supported.

     # List tags of sibling elements before this node
     say $dom->preceding->map('tag')->join("\n");

preceding_nodes

     my $collection = $dom->preceding_nodes;

   Return a collection containing all sibling nodes before this node as
   Mojo::DOM58 objects.

     # "A"
     $dom->parse('A<!-- B --><p>C</p>')->at('p')->preceding_nodes->first->content;

prepend

     $dom = $dom->prepend('<p>I ♥ Mojo::DOM58!</p>');

   Prepend HTML/XML fragment to this node (for all node types other than
   root).

     # "<div><h1>Test</h1><h2>123</h2></div>"
     $dom->parse('<div><h2>123</h2></div>')
       ->at('h2')->prepend('<h1>Test</h1>')->root;

     # "<p>Test 123</p>"
     $dom->parse('<p>123</p>')
       ->at('p')->child_nodes->first->prepend('Test ')->root;

prepend_content

     $dom = $dom->prepend_content('<p>I ♥ Mojo::DOM58!</p>');

   Prepend HTML/XML fragment (for root and tag nodes) or raw content to
   this node's content.

     # "<div><h2>Test123</h2></div>"
     $dom->parse('<div><h2>123</h2></div>')
       ->at('h2')->prepend_content('Test')->root;

     # "<!-- Test 123 --><br>"
     $dom->parse('<!-- 123 --><br>')
       ->child_nodes->first->prepend_content(' Test')->root;

     # "<p><i>123</i>Test</p>"
     $dom->parse('<p>Test</p>')->at('p')->prepend_content('<i>123</i>')->root;

previous

     my $sibling = $dom->previous;

   Return Mojo::DOM58 object for previous sibling element, or undef if
   there are no more siblings.

     # "<h1>Test</h1>"
     $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h2')->previous;

previous_node

     my $sibling = $dom->previous_node;

   Return Mojo::DOM58 object for previous sibling node, or undef if there
   are no more siblings.

     # "123"
     $dom->parse('<p>123<!-- Test --><b>456</b></p>')
       ->at('b')->previous_node->previous_node;

     # " Test "
     $dom->parse('<p>123<!-- Test --><b>456</b></p>')
       ->at('b')->previous_node->content;

remove

     my $parent = $dom->remove;

   Remove this node and return "root" (for root nodes) or "parent".

     # "<div></div>"
     $dom->parse('<div><h1>Test</h1></div>')->at('h1')->remove;

     # "<p><b>456</b></p>"
     $dom->parse('<p>123<b>456</b></p>')
       ->at('p')->child_nodes->first->remove->root;

replace

     my $parent = $dom->replace('<div>I ♥ Mojo::DOM58!</div>');

   Replace this node with HTML/XML fragment and return "root" (for root
   nodes) or "parent".

     # "<div><h2>123</h2></div>"
     $dom->parse('<div><h1>Test</h1></div>')->at('h1')->replace('<h2>123</h2>');

     # "<p><b>123</b></p>"
     $dom->parse('<p>Test</p>')
       ->at('p')->child_nodes->[0]->replace('<b>123</b>')->root;

root

     my $root = $dom->root;

   Return Mojo::DOM58 object for root node.

strip

     my $parent = $dom->strip;

   Remove this element while preserving its content and return "parent".

     # "<div>Test</div>"
     $dom->parse('<div><h1>Test</h1></div>')->at('h1')->strip;

tag

     my $tag = $dom->tag;
     $dom    = $dom->tag('div');

   This element's tag name.

     # List tag names of child elements
     say $dom->children->map('tag')->join("\n");

tap

     $dom = $dom->tap(sub {...});

   Equivalent to "tap" in Mojo::Base.

text

     my $text = $dom->text;

   Extract text content from this element only (not including child
   elements).

     # "bar"
     $dom->parse("<div>foo<p>bar</p>baz</div>")->at('p')->text;

     # "foo\nbaz\n"
     $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text;

to_string

     my $str = $dom->to_string;

   Render this node and its content to HTML/XML.

     # "<b>Test</b>"
     $dom->parse('<div><b>Test</b></div>')->at('div b')->to_string;

tree

     my $tree = $dom->tree;
     $dom     = $dom->tree(['root']);

   Document Object Model. Note that this structure should only be used
   very carefully since it is very dynamic.

type

     my $type = $dom->type;

   This node's type, usually cdata, comment, doctype, pi, raw, root, tag
   or text.

     # "cdata"
     $dom->parse('<![CDATA[Test]]>')->child_nodes->first->type;

     # "comment"
     $dom->parse('<!-- Test -->')->child_nodes->first->type;

     # "doctype"
     $dom->parse('<!DOCTYPE html>')->child_nodes->first->type;

     # "pi"
     $dom->parse('<?xml version="1.0"?>')->child_nodes->first->type;

     # "raw"
     $dom->parse('<title>Test</title>')->at('title')->child_nodes->first->type;

     # "root"
     $dom->parse('<p>Test</p>')->type;

     # "tag"
     $dom->parse('<p>Test</p>')->at('p')->type;

     # "text"
     $dom->parse('<p>Test</p>')->at('p')->child_nodes->first->type;

val

     my $value = $dom->val;

   Extract value from form element (such as button, input, option, select
   and textarea), or return undef if this element has no value. In the
   case of select with multiple attribute, find option elements with
   selected attribute and return an array reference with all values, or
   undef if none could be found.

     # "a"
     $dom->parse('<input name=test value=a>')->at('input')->val;

     # "b"
     $dom->parse('<textarea>b</textarea>')->at('textarea')->val;

     # "c"
     $dom->parse('<option value="c">Test</option>')->at('option')->val;

     # "d"
     $dom->parse('<select><option selected>d</option></select>')
       ->at('select')->val;

     # "e"
     $dom->parse('<select multiple><option selected>e</option></select>')
       ->at('select')->val->[0];

     # "on"
     $dom->parse('<input name=test type=checkbox>')->at('input')->val;

wrap

     $dom = $dom->wrap('<div></div>');

   Wrap HTML/XML fragment around this node (for all node types other than
   root), placing it as the last child of the first innermost element.

     # "<p>123<b>Test</b></p>"
     $dom->parse('<b>Test</b>')->at('b')->wrap('<p>123</p>')->root;

     # "<div><p><b>Test</b></p>123</div>"
     $dom->parse('<b>Test</b>')->at('b')->wrap('<div><p></p>123</div>')->root;

     # "<p><b>Test</b></p><p>123</p>"
     $dom->parse('<b>Test</b>')->at('b')->wrap('<p></p><p>123</p>')->root;

     # "<p><b>Test</b></p>"
     $dom->parse('<p>Test</p>')->at('p')->child_nodes->first->wrap('<b>')->root;

wrap_content

     $dom = $dom->wrap_content('<div></div>');

   Wrap HTML/XML fragment around this node's content (for root and tag
   nodes), placing it as the last children of the first innermost element.

     # "<p><b>123Test</b></p>"
     $dom->parse('<p>Test<p>')->at('p')->wrap_content('<b>123</b>')->root;

     # "<p><b>Test</b></p><p>123</p>"
     $dom->parse('<b>Test</b>')->wrap_content('<p></p><p>123</p>');

xml

     my $bool = $dom->xml;
     $dom     = $dom->xml($bool);

   Disable HTML semantics in parser and activate case-sensitivity,
   defaults to auto detection based on XML declarations.

COLLECTION METHODS

   Some Mojo::DOM58 methods return an array-based collection object based
   on Mojo::Collection, which can either be accessed directly as an array
   reference, or with the following methods.

     # Chain methods
     $collection->map(sub { ucfirst })->shuffle->each(sub {
       my ($word, $num) = @_;
       say "$num: $word";
     });

     # Access array directly to manipulate collection
     $collection->[23] += 100;
     say for @$collection;

compact

     my $new = $collection->compact;

   Create a new collection with all elements that are defined and not an
   empty string.

     # $collection contains (0, 1, undef, 2, '', 3)
     $collection->compact->join(', '); # "0, 1, 2, 3"

each

     my @elements = $collection->each;
     $collection  = $collection->each(sub {...});

   Evaluate callback for each element in collection or return all elements
   as a list if none has been provided. The element will be the first
   argument passed to the callback and is also available as $_.

     # Make a numbered list
     $collection->each(sub {
       my ($e, $num) = @_;
       say "$num: $e";
     });

first

     my $first = $collection->first;
     my $first = $collection->first(qr/foo/);
     my $first = $collection->first(sub {...});
     my $first = $collection->first($method);
     my $first = $collection->first($method, @args);

   Evaluate regular expression/callback for, or call method on, each
   element in collection and return the first one that matched the regular
   expression, or for which the callback/method returned true. The element
   will be the first argument passed to the callback and is also available
   as $_.

     # Longer version
     my $first = $collection->first(sub { $_->$method(@args) });

     # Find first value that contains the word "mojo"
     my $interesting = $collection->first(qr/mojo/i);

     # Find first value that is greater than 5
     my $greater = $collection->first(sub { $_ > 5 });

flatten

     my $new = $collection->flatten;

   Flatten nested collections/arrays recursively and create a new
   collection with all elements.

     # $collection contains (1, [2, [3, 4], 5, [6]], 7)
     $collection->flatten->join(', '); # "1, 2, 3, 4, 5, 6, 7"

grep

     my $new = $collection->grep(qr/foo/);
     my $new = $collection->grep(sub {...});
     my $new = $collection->grep($method);
     my $new = $collection->grep($method, @args);

   Evaluate regular expression/callback for, or call method on, each
   element in collection and create a new collection with all elements
   that matched the regular expression, or for which the callback/method
   returned true. The element will be the first argument passed to the
   callback and is also available as $_.

     # Longer version
     my $new = $collection->grep(sub { $_->$method(@args) });

     # Find all values that contain the word "mojo"
     my $interesting = $collection->grep(qr/mojo/i);

     # Find all values that are greater than 5
     my $greater = $collection->grep(sub { $_ > 5 });

join

     my $stream = $collection->join;
     my $stream = $collection->join("\n");

   Turn collection into string.

     # Join all values with commas
     $collection->join(', ');

last

     my $last = $collection->last;

   Return the last element in collection.

map

     my $new = $collection->map(sub {...});
     my $new = $collection->map($method);
     my $new = $collection->map($method, @args);

   Evaluate callback for, or call method on, each element in collection
   and create a new collection from the results. The element will be the
   first argument passed to the callback and is also available as $_.

     # Longer version
     my $new = $collection->map(sub { $_->$method(@args) });

     # Append the word "mojo" to all values
     my $domified = $collection->map(sub { $_ . 'mojo' });

reduce

     my $result = $collection->reduce(sub {...});
     my $result = $collection->reduce(sub {...}, $initial);

   Reduce elements in collection with callback, the first element will be
   used as initial value if none has been provided.

     # Calculate the sum of all values
     my $sum = $collection->reduce(sub { $a + $b });

     # Count how often each value occurs in collection
     my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {});

reverse

     my $new = $collection->reverse;

   Create a new collection with all elements in reverse order.

slice

     my $new = $collection->slice(4 .. 7);

   Create a new collection with all selected elements.

     # $collection contains ('A', 'B', 'C', 'D', 'E')
     $collection->slice(1, 2, 4)->join(' '); # "B C E"

shuffle

     my $new = $collection->shuffle;

   Create a new collection with all elements in random order.

size

     my $size = $collection->size;

   Number of elements in collection.

sort

     my $new = $collection->sort;
     my $new = $collection->sort(sub {...});

   Sort elements based on return value of callback and create a new
   collection from the results.

     # Sort values case-insensitive
     my $case_insensitive = $collection->sort(sub { uc($a) cmp uc($b) });

tap

     $collection = $collection->tap(sub {...});

   Equivalent to "tap" in Mojo::Base.

to_array

     my $array = $collection->to_array;

   Turn collection into array reference.

uniq

     my $new = $collection->uniq;
     my $new = $collection->uniq(sub {...});
     my $new = $collection->uniq($method);
     my $new = $collection->uniq($method, @args);

   Create a new collection without duplicate elements, using the string
   representation of either the elements or the return value of the
   callback/method.

     # Longer version
     my $new = $collection->uniq(sub { $_->$method(@args) });

     # $collection contains ('foo', 'bar', 'bar', 'baz')
     $collection->uniq->join(' '); # "foo bar baz"

     # $collection contains ([1, 2], [2, 1], [3, 2])
     $collection->uniq(sub{ $_->[1] })->to_array; # "[[1, 2], [2, 1]]"

BUGS

   Report issues related to the format of this distribution or Perl 5.8
   support to the public bugtracker. Any other issues should be reported
   directly to the upstream Mojolicious issue tracker.

AUTHOR

   Dan Book <[email protected]>

   Code and tests adapted from Mojo::DOM, a lightweight DOM parser by the
   Mojolicious team.

CONTRIBUTORS

   Matt S Trout (mst)

COPYRIGHT AND LICENSE

   Copyright (c) 2008-2016 Sebastian Riedel and others.

   Copyright (c) 2016 "AUTHOR" and "CONTRIBUTORS" for adaptation to
   standalone format.

   This is free software, licensed under:

     The Artistic License 2.0 (GPL Compatible)

SEE ALSO

   Mojo::DOM, HTML::TreeBuilder, XML::LibXML, XML::Twig, XML::Smart