use strict;
use warnings;
+use overload
+ '@{}' => sub { shift->child_nodes },
+ '%{}' => sub { shift->attr },
+ bool => sub {1},
+ '""' => sub { shift->to_string },
+ fallback => 1;
+
+use Carp 'croak';
+use DOM::Tiny::Collection;
+use DOM::Tiny::CSS;
+use DOM::Tiny::HTML;
+use Scalar::Util qw(blessed weaken);
+
our $VERSION = '0.001';
+sub all_text { shift->_all_text(1, @_) }
+
+sub ancestors { _select($_[0]->_collect($_[0]->_ancestors), $_[1]) }
+
+sub append { shift->_add(1, @_) }
+sub append_content { shift->_content(1, 0, @_) }
+
+sub at {
+ my $self = shift;
+ return undef unless my $result = $self->_css->select_one(@_);
+ return $self->_build($result, $self->xml);
+}
+
+sub attr {
+ my $self = shift;
+
+ # Hash
+ my $tree = $self->tree;
+ my $attrs = $tree->[0] ne 'tag' ? {} : $tree->[2];
+ return $attrs unless @_;
+
+ # Get
+ return $attrs->{$_[0]} unless @_ > 1 || ref $_[0];
+
+ # Set
+ my $values = ref $_[0] ? $_[0] : {@_};
+ @$attrs{keys %$values} = values %$values;
+
+ return $self;
+}
+
+sub child_nodes { $_[0]->_collect(_nodes($_[0]->tree)) }
+
+sub children { _select($_[0]->_collect(_nodes($_[0]->tree, 1)), $_[1]) }
+
+sub content {
+ my $self = shift;
+
+ my $type = $self->type;
+ if ($type eq 'root' || $type eq 'tag') {
+ return $self->_content(0, 1, @_) if @_;
+ my $html = DOM::Tiny::HTML->new(xml => $self->xml);
+ return join '', map { $html->tree($_)->render } _nodes($self->tree);
+ }
+
+ return $self->tree->[1] unless @_;
+ $self->tree->[1] = shift;
+ return $self;
+}
+
+sub descendant_nodes { $_[0]->_collect(_all(_nodes($_[0]->tree))) }
+
+sub find { $_[0]->_collect(@{$_[0]->_css->select($_[1])}) }
+
+sub following { _select($_[0]->_collect(@{$_[0]->_siblings(1)->[1]}), $_[1]) }
+sub following_nodes { $_[0]->_collect(@{$_[0]->_siblings->[1]}) }
+
+sub matches { shift->_css->matches(@_) }
+
+sub namespace {
+ my $self = shift;
+
+ return undef if (my $tree = $self->tree)->[0] ne 'tag';
+
+ # Extract namespace prefix and search parents
+ my $ns = $tree->[1] =~ /^(.*?):/ ? "xmlns:$1" : undef;
+ for my $node ($tree, $self->_ancestors) {
+
+ # Namespace for prefix
+ my $attrs = $node->[2];
+ if ($ns) { $_ eq $ns and return $attrs->{$_} for keys %$attrs }
+
+ # Namespace attribute
+ elsif (defined $attrs->{xmlns}) { return $attrs->{xmlns} }
+ }
+
+ return undef;
+}
+
+sub new {
+ my $class = shift;
+ my $self = bless \DOM::Tiny::HTML->new, ref $class || $class;
+ return @_ ? $self->parse(@_) : $self;
+}
+
+sub next { $_[0]->_maybe($_[0]->_siblings(1, 0)->[1]) }
+sub next_node { $_[0]->_maybe($_[0]->_siblings(0, 0)->[1]) }
+
+sub parent {
+ my $self = shift;
+ return undef if $self->tree->[0] eq 'root';
+ return $self->_build($self->_parent, $self->xml);
+}
+
+sub parse { shift->_delegate(parse => @_) }
+
+sub preceding { _select($_[0]->_collect(@{$_[0]->_siblings(1)->[0]}), $_[1]) }
+sub preceding_nodes { $_[0]->_collect(@{$_[0]->_siblings->[0]}) }
+
+sub prepend { shift->_add(0, @_) }
+sub prepend_content { shift->_content(0, 0, @_) }
+
+sub previous { $_[0]->_maybe($_[0]->_siblings(1, -1)->[0]) }
+sub previous_node { $_[0]->_maybe($_[0]->_siblings(0, -1)->[0]) }
+
+sub remove { shift->replace('') }
+
+sub replace {
+ my ($self, $new) = @_;
+ return $self->parse($new) if (my $tree = $self->tree)->[0] eq 'root';
+ return $self->_replace($self->_parent, $tree, _nodes($self->_parse($new)));
+}
+
+sub root {
+ my $self = shift;
+ return $self unless my $tree = $self->_ancestors(1);
+ return $self->_build($tree, $self->xml);
+}
+
+sub strip {
+ my $self = shift;
+ return $self if (my $tree = $self->tree)->[0] ne 'tag';
+ return $self->_replace($tree->[3], $tree, _nodes($tree));
+}
+
+sub tag {
+ my ($self, $tag) = @_;
+ return undef if (my $tree = $self->tree)->[0] ne 'tag';
+ return $tree->[1] unless $tag;
+ $tree->[1] = $tag;
+ return $self;
+}
+
+sub tap { shift->DOM::Tiny::Collection::tap(@_) }
+
+sub text { shift->_all_text(0, @_) }
+
+sub to_string { shift->_delegate('render') }
+
+sub tree { shift->_delegate(tree => @_) }
+
+sub type { shift->tree->[0] }
+
+sub val {
+ my $self = shift;
+
+ # "option"
+ return $self->{value} // $self->text if (my $tag = $self->tag) eq 'option';
+
+ # "textarea", "input" or "button"
+ return $tag eq 'textarea' ? $self->text : $self->{value} if $tag ne 'select';
+
+ # "select"
+ my $v = $self->find('option:checked')->map('val');
+ return exists $self->{multiple} ? $v->size ? $v->to_array : undef : $v->last;
+}
+
+sub wrap { shift->_wrap(0, @_) }
+sub wrap_content { shift->_wrap(1, @_) }
+
+sub xml { shift->_delegate(xml => @_) }
+
+sub _add {
+ my ($self, $offset, $new) = @_;
+
+ return $self if (my $tree = $self->tree)->[0] eq 'root';
+
+ my $parent = $self->_parent;
+ splice @$parent, _offset($parent, $tree) + $offset, 0,
+ _link($parent, _nodes($self->_parse($new)));
+
+ return $self;
+}
+
+sub _all {
+ map { $_->[0] eq 'tag' ? ($_, _all(_nodes($_))) : ($_) } @_;
+}
+
+sub _all_text {
+ my ($self, $recurse, $trim) = @_;
+
+ # Detect "pre" tag
+ my $tree = $self->tree;
+ $trim = 1 unless defined $trim;
+ map { $_->[1] eq 'pre' and $trim = 0 } $self->_ancestors, $tree
+ if $trim && $tree->[0] ne 'root';
+
+ return _text([_nodes($tree)], $recurse, $trim);
+}
+
+sub _ancestors {
+ my ($self, $root) = @_;
+
+ return unless my $tree = $self->_parent;
+ my @ancestors;
+ do { push @ancestors, $tree }
+ while ($tree->[0] eq 'tag') && ($tree = $tree->[3]);
+ return $root ? $ancestors[-1] : @ancestors[0 .. $#ancestors - 1];
+}
+
+sub _build { shift->new->tree(shift)->xml(shift) }
+
+sub _collect {
+ my $self = shift;
+ my $xml = $self->xml;
+ return DOM::Tiny::Collection->new(map { $self->_build($_, $xml) } @_);
+}
+
+sub _content {
+ my ($self, $start, $offset, $new) = @_;
+
+ my $tree = $self->tree;
+ unless ($tree->[0] eq 'root' || $tree->[0] eq 'tag') {
+ my $old = $self->content;
+ return $self->content($start ? "$old$new" : "$new$old");
+ }
+
+ $start = $start ? ($#$tree + 1) : _start($tree);
+ $offset = $offset ? $#$tree : 0;
+ splice @$tree, $start, $offset, _link($tree, _nodes($self->_parse($new)));
+
+ return $self;
+}
+
+sub _css { DOM::Tiny::CSS->new(tree => shift->tree) }
+
+sub _delegate {
+ my ($self, $method) = (shift, shift);
+ return $$self->$method unless @_;
+ $$self->$method(@_);
+ return $self;
+}
+
+sub _link {
+ my ($parent, @children) = @_;
+
+ # Link parent to children
+ for my $node (@children) {
+ my $offset = $node->[0] eq 'tag' ? 3 : 2;
+ $node->[$offset] = $parent;
+ weaken $node->[$offset];
+ }
+
+ return @children;
+}
+
+sub _maybe { $_[1] ? $_[0]->_build($_[1], $_[0]->xml) : undef }
+
+sub _nodes {
+ return unless my $tree = shift;
+ my @nodes = @$tree[_start($tree) .. $#$tree];
+ return shift() ? grep { $_->[0] eq 'tag' } @nodes : @nodes;
+}
+
+sub _offset {
+ my ($parent, $child) = @_;
+ my $i = _start($parent);
+ $_ eq $child ? last : $i++ for @$parent[$i .. $#$parent];
+ return $i;
+}
+
+sub _parent { $_[0]->tree->[$_[0]->type eq 'tag' ? 3 : 2] }
+
+sub _parse { DOM::Tiny::HTML->new(xml => shift->xml)->parse(shift)->tree }
+
+sub _replace {
+ my ($self, $parent, $tree, @nodes) = @_;
+ splice @$parent, _offset($parent, $tree), 1, _link($parent, @nodes);
+ return $self->parent;
+}
+
+sub _select {
+ my ($collection, $selector) = @_;
+ return $collection unless $selector;
+ return $collection->new(grep { $_->matches($selector) } @$collection);
+}
+
+sub _siblings {
+ my ($self, $tags, $i) = @_;
+
+ return [] unless my $parent = $self->parent;
+
+ my $tree = $self->tree;
+ my (@before, @after, $match);
+ for my $node (_nodes($parent->tree)) {
+ ++$match and next if !$match && $node eq $tree;
+ next if $tags && $node->[0] ne 'tag';
+ $match ? push @after, $node : push @before, $node;
+ }
+
+ return defined $i ? [$before[$i], $after[$i]] : [\@before, \@after];
+}
+
+sub _squish {
+ my $str = shift;
+ $str =~ s/^\s+//;
+ $str =~ s/\s+$//;
+ $str =~ s/\s+/ /g;
+ return $str;
+}
+
+sub _start { $_[0][0] eq 'root' ? 1 : 4 }
+
+sub _text {
+ my ($nodes, $recurse, $trim) = @_;
+
+ # Merge successive text nodes
+ my $i = 0;
+ while (my $next = $nodes->[$i + 1]) {
+ ++$i and next unless $nodes->[$i][0] eq 'text' && $next->[0] eq 'text';
+ splice @$nodes, $i, 2, ['text', $nodes->[$i][1] . $next->[1]];
+ }
+
+ my $text = '';
+ for my $node (@$nodes) {
+ my $type = $node->[0];
+
+ # Text
+ my $chunk = '';
+ if ($type eq 'text') { $chunk = $trim ? _squish $node->[1] : $node->[1] }
+
+ # CDATA or raw text
+ elsif ($type eq 'cdata' || $type eq 'raw') { $chunk = $node->[1] }
+
+ # Nested tag
+ elsif ($type eq 'tag' && $recurse) {
+ no warnings 'recursion';
+ $chunk = _text([_nodes($node)], 1, $node->[1] eq 'pre' ? 0 : $trim);
+ }
+
+ # Add leading whitespace if punctuation allows it
+ $chunk = " $chunk" if $text =~ /\S\z/ && $chunk =~ /^[^.!?,;:\s]+/;
+
+ # Trim whitespace blocks
+ $text .= $chunk if $chunk =~ /\S+/ || !$trim;
+ }
+
+ return $text;
+}
+
+sub _wrap {
+ my ($self, $content, $new) = @_;
+
+ $content = 1 if (my $tree = $self->tree)->[0] eq 'root';
+ $content = 0 if $tree->[0] ne 'root' && $tree->[0] ne 'tag';
+
+ # Find innermost tag
+ my $current;
+ my $first = $new = $self->_parse($new);
+ $current = $first while $first = (_nodes($first, 1))[0];
+ return $self unless $current;
+
+ # Wrap content
+ if ($content) {
+ push @$current, _link($current, _nodes($tree));
+ splice @$tree, _start($tree), $#$tree, _link($tree, _nodes($new));
+ return $self;
+ }
+
+ # Wrap element
+ $self->_replace($self->_parent, $tree, _nodes($new));
+ push @$current, _link($current, $tree);
+ return $self;
+}
+
1;
+=encoding utf8
+
=head1 NAME
-DOM::Tiny - Module abstract
+DOM::Tiny - Minimalistic HTML/XML DOM parser with CSS selectors
=head1 SYNOPSIS
+ use DOM::Tiny;
+
+ # Parse
+ my $dom = DOM::Tiny->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";
+
=head1 DESCRIPTION
+L<DOM::Tiny> is a minimalistic and relaxed HTML/XML DOM parser with CSS
+selector support based on L<Mojo::DOM>. It will even try to interpret broken
+HTML and XML, so you should not use it for validation.
+
+=head1 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, C<cdata>, C<comment>,
+C<doctype>, C<pi>, C<raw>, C<root>, C<tag> and C<text>. Elements are nodes of
+the type C<tag>.
+
+ root
+ |- doctype (html)
+ +- tag (html)
+ |- tag (head)
+ | +- tag (title)
+ | +- raw (Hello)
+ +- tag (body)
+ +- text (World!)
+
+While all node types are represented as L<DOM::Tiny> objects, some methods like
+L</"attr"> and L</"namespace"> only apply to elements.
+
+=head1 CASE-SENSITIVITY
+
+L<DOM::Tiny> 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 = 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.
+
+ # XML semantics
+ my $dom = DOM::Tiny->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
+ say $dom->at('P[ID]')->text;
+
+XML detection can also be disabled with the L</"xml"> method.
+
+ # Force XML semantics
+ my $dom = DOM::Tiny->new->xml(1)->parse('<P ID="greeting">Hi!</P>');
+ say $dom->at('P[ID]')->text;
+
+ # Force HTML semantics
+ my $dom = DOM::Tiny->new->xml(0)->parse('<P ID="greeting">Hi!</P>');
+ say $dom->at('p[id]')->text;
+
+=head1 METHODS
+
+L<DOM::Tiny> implements the following methods.
+
+=head2 all_text
+
+ my $trimmed = $dom->all_text;
+ my $untrimmed = $dom->all_text(0);
+
+Extract text content from all descendant nodes of this element, smart
+whitespace trimming is enabled by default.
+
+ # "foo bar baz"
+ $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text;
+
+ # "foo\nbarbaz\n"
+ $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text(0);
+
+=head2 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
+L<DOM::Tiny::Collection> object containing these elements as L<DOM::Tiny>
+objects. All selectors from L<DOM::Tiny::CSS/"SELECTORS"> are supported.
+
+ # List tag names of ancestor elements
+ say $dom->ancestors->map('tag')->join("\n");
+
+=head2 append
+
+ $dom = $dom->append('<p>I ♥ DOM::Tiny!</p>');
+
+Append HTML/XML fragment to this node.
+
+ # "<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;
+
+=head2 append_content
+
+ $dom = $dom->append_content('<p>I ♥ DOM::Tiny!</p>');
+
+Append HTML/XML fragment (for C<root> and C<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;
+
+=head2 at
+
+ 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 from L<DOM::Tiny::CSS/"SELECTORS"> are supported.
+
+ # Find first element with "svg" namespace definition
+ my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};
+
+=head2 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");
+
+=head2 child_nodes
+
+ my $collection = $dom->child_nodes;
+
+Return a L<DOM::Tiny::Collection> object containing all child nodes of this
+element as L<DOM::Tiny> 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;
+
+=head2 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
+L<DOM::Tiny::Collection> object containing these elements as L<DOM::Tiny>
+objects. All selectors from L<DOM::Tiny::CSS/"SELECTORS"> are supported.
+
+ # Show tag name of random child element
+ say $dom->children->shuffle->first->tag;
+
+=head2 content
+
+ my $str = $dom->content;
+ $dom = $dom->content('<p>I ♥ DOM::Tiny!</p>');
+
+Return this node's content or replace it with HTML/XML fragment (for C<root>
+and C<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;
+
+=head2 descendant_nodes
+
+ my $collection = $dom->descendant_nodes;
+
+Return a L<DOM::Tiny::Collection> object containing all descendant nodes of
+this element as L<DOM::Tiny> 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;
+
+=head2 find
+
+ my $collection = $dom->find('div ~ p');
+
+Find all descendant elements of this element matching the CSS selector and
+return a L<DOM::Tiny::Collection> object containing these elements as
+L<DOM::Tiny> objects. All selectors from L<DOM::Tiny::CSS/"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;
+
+=head2 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 L<DOM::Tiny::Collection> object containing these elements as L<DOM::Tiny>
+objects. All selectors from L<DOM::Tiny::CSS/"SELECTORS"> are supported.
+
+ # List tags of sibling elements after this node
+ say $dom->following->map('tag')->join("\n");
+
+=head2 following_nodes
+
+ my $collection = $dom->following_nodes;
+
+Return a L<DOM::Tiny::Collection> object containing all sibling nodes after
+this node as L<DOM::Tiny> objects.
+
+ # "C"
+ $dom->parse('<p>A</p><!-- B -->C')->at('p')->following_nodes->last->content;
+
+=head2 matches
+
+ my $bool = $dom->matches('div ~ p');
+
+Check if this element matches the CSS selector. All selectors from
+L<DOM::Tiny::CSS/"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]');
+
+=head2 namespace
+
+ my $namespace = $dom->namespace;
+
+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;
+
+ # Find namespace for an element that may or may not have a namespace prefix
+ my $namespace = $dom->at('svg > circle')->namespace;
+
+=head2 new
+
+ my $dom = DOM::Tiny->new;
+ my $dom = DOM::Tiny->new('<foo bar="baz">I ♥ DOM::Tiny!</foo>');
+
+Construct a new scalar-based L<DOM::Tiny> object and L</"parse"> HTML/XML
+fragment if necessary.
+
+=head2 next
+
+ my $sibling = $dom->next;
+
+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;
+
+=head2 next_node
+
+ my $sibling = $dom->next_node;
+
+Return L<DOM::Tiny> object for next sibling node or C<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;
+
+=head2 parent
+
+ my $parent = $dom->parent;
+
+Return L<DOM::Tiny> object for parent of this node or C<undef> if this node has
+no parent.
+
+=head2 parse
+
+ $dom = $dom->parse('<foo bar="baz">I ♥ DOM::Tiny!</foo>');
+
+Parse HTML/XML fragment with L<DOM::Tiny::HTML>.
+
+ # Parse XML
+ my $dom = DOM::Tiny->new->xml(1)->parse($xml);
+
+=head2 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 L<DOM::Tiny::Collection> object containing these elements as L<DOM::Tiny>
+objects. All selectors from L<DOM::Tiny::CSS/"SELECTORS"> are supported.
+
+ # List tags of sibling elements before this node
+ say $dom->preceding->map('tag')->join("\n");
+
+=head2 preceding_nodes
+
+ my $collection = $dom->preceding_nodes;
+
+Return a L<DOM::Tiny::Collection> object containing all sibling nodes before
+this node as L<DOM::Tiny> objects.
+
+ # "A"
+ $dom->parse('A<!-- B --><p>C</p>')->at('p')->preceding_nodes->first->content;
+
+=head2 prepend
+
+ $dom = $dom->prepend('<p>I ♥ DOM::Tiny!</p>');
+
+Prepend HTML/XML fragment to this node.
+
+ # "<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;
+
+=head2 prepend_content
+
+ $dom = $dom->prepend_content('<p>I ♥ DOM::Tiny!</p>');
+
+Prepend HTML/XML fragment (for C<root> and C<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;
+
+=head2 previous
+
+ my $sibling = $dom->previous;
+
+Return L<DOM::Tiny> object for previous sibling element or C<undef> if there
+are no more siblings.
+
+ # "<h1>Test</h1>"
+ $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h2')->previous;
+
+=head2 previous_node
+
+ my $sibling = $dom->previous_node;
+
+Return L<DOM::Tiny> object for previous sibling node or C<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;
+
+=head2 remove
+
+ my $parent = $dom->remove;
+
+Remove this node and return L</"root"> (for C<root> nodes) or L</"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;
+
+=head2 replace
+
+ my $parent = $dom->replace('<div>I ♥ DOM::Tiny!</div>');
+
+Replace this node with HTML/XML fragment and return L</"root"> (for C<root>
+nodes) or L</"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;
+
+=head2 root
+
+ my $root = $dom->root;
+
+Return L<DOM::Tiny> object for C<root> node.
+
+=head2 strip
+
+ my $parent = $dom->strip;
+
+Remove this element while preserving its content and return L</"parent">.
+
+ # "<div>Test</div>"
+ $dom->parse('<div><h1>Test</h1></div>')->at('h1')->strip;
+
+=head2 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");
+
+=head2 tap
+
+ $dom = $dom->tap(sub {...});
+
+Alias for L<Mojo::Base/"tap">.
+
+=head2 text
+
+ my $trimmed = $dom->text;
+ my $untrimmed = $dom->text(0);
+
+Extract text content from this element only (not including child elements),
+smart whitespace trimming is enabled by default.
+
+ # "foo baz"
+ $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text;
+
+ # "foo\nbaz\n"
+ $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text(0);
+
+=head2 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;
+
+=head2 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.
+
+=head2 type
+
+ my $type = $dom->type;
+
+This node's type, usually C<cdata>, C<comment>, C<doctype>, C<pi>, C<raw>,
+C<root>, C<tag> or C<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;
+
+=head2 val
+
+ 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
+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.
+
+ # "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];
+
+=head2 wrap
+
+ $dom = $dom->wrap('<div></div>');
+
+Wrap HTML/XML fragment around this node, 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;
+
+=head2 wrap_content
+
+ $dom = $dom->wrap_content('<div></div>');
+
+Wrap HTML/XML fragment around this node's content, 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>');
+
+=head2 xml
+
+ my $bool = $dom->xml;
+ $dom = $dom->xml($bool);
+
+Disable HTML semantics in parser and activate case-sensitivity, defaults to
+auto detection based on processing instructions.
+
+=head1 OPERATORS
+
+L<DOM::Tiny> overloads the following operators.
+
+=head2 array
+
+ my @nodes = @$dom;
+
+Alias for L</"child_nodes">.
+
+ # "<!-- Test -->"
+ $dom->parse('<!-- Test --><b>123</b>')->[0];
+
+=head2 bool
+
+ my $bool = !!$dom;
+
+Always true.
+
+=head2 hash
+
+ my %attrs = %$dom;
+
+Alias for L</"attr">.
+
+ # "test"
+ $dom->parse('<div id="test">Test</div>')->at('div')->{id};
+
+=head2 stringify
+
+ my $str = "$dom";
+
+Alias for L</"to_string">.
+
=head1 BUGS
Report any issues on the public bugtracker.
=head1 SEE ALSO
+L<Mojo::DOM>, L<XML::LibXML>, L<XML::Twig>, L<HTML::TreeBuilder>, L<XML::Smart>
--- /dev/null
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use DOM::Tiny;
+
+# Empty
+is(DOM::Tiny->new, '', 'right result');
+is(DOM::Tiny->new(''), '', 'right result');
+is(DOM::Tiny->new->parse(''), '', 'right result');
+is(DOM::Tiny->new->at('p'), undef, 'no result');
+is(DOM::Tiny->new->append_content(''), '', 'right result');
+is(DOM::Tiny->new->all_text, '', 'right result');
+
+# Simple (basics)
+my $dom
+ = DOM::Tiny->new('<div><div FOO="0" id="a">A</div><div id="b">B</div></div>');
+is $dom->at('#b')->text, 'B', 'right text';
+my @div;
+push @div, $dom->find('div[id]')->map('text')->each;
+is_deeply \@div, [qw(A B)], 'found all div elements with id';
+@div = ();
+$dom->find('div[id]')->each(sub { push @div, $_->text });
+is_deeply \@div, [qw(A B)], 'found all div elements with id';
+is $dom->at('#a')->attr('foo'), 0, 'right attribute';
+is $dom->at('#a')->attr->{foo}, 0, 'right attribute';
+is "$dom", '<div><div foo="0" id="a">A</div><div id="b">B</div></div>',
+ 'right result';
+
+# Tap into method chain
+$dom = DOM::Tiny->new->parse('<div id="a">A</div><div id="b">B</div>');
+is_deeply [$dom->find('[id]')->map(attr => 'id')->each], [qw(a b)],
+ 'right result';
+is $dom->tap(sub { $_->at('#b')->remove }), '<div id="a">A</div>',
+ 'right result';
+
+# Build tree from scratch
+is(DOM::Tiny->new->append_content('<p>')->at('p')->append_content('0')->text,
+ '0', 'right text');
+
+# Simple nesting with healing (tree structure)
+$dom = DOM::Tiny->new(<<EOF);
+<foo><bar a="b<c">ju<baz a23>s<bazz />t</bar>works</foo>
+EOF
+is $dom->tree->[0], 'root', 'right type';
+is $dom->tree->[1][0], 'tag', 'right type';
+is $dom->tree->[1][1], 'foo', 'right tag';
+is_deeply $dom->tree->[1][2], {}, 'empty attributes';
+is $dom->tree->[1][3], $dom->tree, 'right parent';
+is $dom->tree->[1][4][0], 'tag', 'right type';
+is $dom->tree->[1][4][1], 'bar', 'right tag';
+is_deeply $dom->tree->[1][4][2], {a => 'b<c'}, 'right attributes';
+is $dom->tree->[1][4][3], $dom->tree->[1], 'right parent';
+is $dom->tree->[1][4][4][0], 'text', 'right type';
+is $dom->tree->[1][4][4][1], 'ju', 'right text';
+is $dom->tree->[1][4][4][2], $dom->tree->[1][4], 'right parent';
+is $dom->tree->[1][4][5][0], 'tag', 'right type';
+is $dom->tree->[1][4][5][1], 'baz', 'right tag';
+is_deeply $dom->tree->[1][4][5][2], {a23 => undef}, 'right attributes';
+is $dom->tree->[1][4][5][3], $dom->tree->[1][4], 'right parent';
+is $dom->tree->[1][4][5][4][0], 'text', 'right type';
+is $dom->tree->[1][4][5][4][1], 's', 'right text';
+is $dom->tree->[1][4][5][4][2], $dom->tree->[1][4][5], 'right parent';
+is $dom->tree->[1][4][5][5][0], 'tag', 'right type';
+is $dom->tree->[1][4][5][5][1], 'bazz', 'right tag';
+is_deeply $dom->tree->[1][4][5][5][2], {}, 'empty attributes';
+is $dom->tree->[1][4][5][5][3], $dom->tree->[1][4][5], 'right parent';
+is $dom->tree->[1][4][5][6][0], 'text', 'right type';
+is $dom->tree->[1][4][5][6][1], 't', 'right text';
+is $dom->tree->[1][4][5][6][2], $dom->tree->[1][4][5], 'right parent';
+is $dom->tree->[1][5][0], 'text', 'right type';
+is $dom->tree->[1][5][1], 'works', 'right text';
+is $dom->tree->[1][5][2], $dom->tree->[1], 'right parent';
+is "$dom", <<EOF, 'right result';
+<foo><bar a="b<c">ju<baz a23>s<bazz></bazz>t</baz></bar>works</foo>
+EOF
+
+# Select based on parent
+$dom = DOM::Tiny->new(<<EOF);
+<body>
+ <div>test1</div>
+ <div><div>test2</div></div>
+<body>
+EOF
+is $dom->find('body > div')->[0]->text, 'test1', 'right text';
+is $dom->find('body > div')->[1]->text, '', 'no content';
+is $dom->find('body > div')->[2], undef, 'no result';
+is $dom->find('body > div')->size, 2, 'right number of elements';
+is $dom->find('body > div > div')->[0]->text, 'test2', 'right text';
+is $dom->find('body > div > div')->[1], undef, 'no result';
+is $dom->find('body > div > div')->size, 1, 'right number of elements';
+
+# A bit of everything (basic navigation)
+$dom = DOM::Tiny->new->parse(<<EOF);
+<!doctype foo>
+<foo bar="ba<z">
+ test
+ <simple class="working">easy</simple>
+ <test foo="bar" id="test" />
+ <!-- lala -->
+ works well
+ <![CDATA[ yada yada]]>
+ <?boom lalalala ?>
+ <a little bit broken>
+ < very broken
+ <br />
+ more text
+</foo>
+EOF
+ok !$dom->xml, 'XML mode not detected';
+is $dom->tag, undef, 'no tag';
+is $dom->attr('foo'), undef, 'no attribute';
+is $dom->attr(foo => 'bar')->attr('foo'), undef, 'no attribute';
+is $dom->tree->[1][0], 'doctype', 'right type';
+is $dom->tree->[1][1], ' foo', 'right doctype';
+is "$dom", <<EOF, 'right result';
+<!DOCTYPE foo>
+<foo bar="ba<z">
+ test
+ <simple class="working">easy</simple>
+ <test foo="bar" id="test"></test>
+ <!-- lala -->
+ works well
+ <![CDATA[ yada yada]]>
+ <?boom lalalala ?>
+ <a bit broken little>
+ < very broken
+ <br>
+ more text
+</a></foo>
+EOF
+my $simple = $dom->at('foo simple.working[class^="wor"]');
+is $simple->parent->all_text,
+ 'test easy works well yada yada < very broken more text', 'right text';
+is $simple->tag, 'simple', 'right tag';
+is $simple->attr('class'), 'working', 'right class attribute';
+is $simple->text, 'easy', 'right text';
+is $simple->parent->tag, 'foo', 'right parent tag';
+is $simple->parent->attr->{bar}, 'ba<z', 'right parent attribute';
+is $simple->parent->children->[1]->tag, 'test', 'right sibling';
+is $simple->to_string, '<simple class="working">easy</simple>',
+ 'stringified right';
+$simple->parent->attr(bar => 'baz')->attr({this => 'works', too => 'yea'});
+is $simple->parent->attr('bar'), 'baz', 'right parent attribute';
+is $simple->parent->attr('this'), 'works', 'right parent attribute';
+is $simple->parent->attr('too'), 'yea', 'right parent attribute';
+is $dom->at('test#test')->tag, 'test', 'right tag';
+is $dom->at('[class$="ing"]')->tag, 'simple', 'right tag';
+is $dom->at('[class="working"]')->tag, 'simple', 'right tag';
+is $dom->at('[class$=ing]')->tag, 'simple', 'right tag';
+is $dom->at('[class=working][class]')->tag, 'simple', 'right tag';
+is $dom->at('foo > simple')->next->tag, 'test', 'right tag';
+is $dom->at('foo > simple')->next->next->tag, 'a', 'right tag';
+is $dom->at('foo > test')->previous->tag, 'simple', 'right tag';
+is $dom->next, undef, 'no siblings';
+is $dom->previous, undef, 'no siblings';
+is $dom->at('foo > a')->next, undef, 'no next sibling';
+is $dom->at('foo > simple')->previous, undef, 'no previous sibling';
+is_deeply [$dom->at('simple')->ancestors->map('tag')->each], ['foo'],
+ 'right results';
+ok !$dom->at('simple')->ancestors->first->xml, 'XML mode not active';
+
+# Nodes
+$dom = DOM::Tiny->new(
+ '<!DOCTYPE before><p>test<![CDATA[123]]><!-- 456 --></p><?after?>');
+is $dom->at('p')->preceding_nodes->first->content, ' before', 'right content';
+is $dom->at('p')->preceding_nodes->size, 1, 'right number of nodes';
+is $dom->at('p')->child_nodes->last->preceding_nodes->first->content, 'test',
+ 'right content';
+is $dom->at('p')->child_nodes->last->preceding_nodes->last->content, '123',
+ 'right content';
+is $dom->at('p')->child_nodes->last->preceding_nodes->size, 2,
+ 'right number of nodes';
+is $dom->preceding_nodes->size, 0, 'no preceding nodes';
+is $dom->at('p')->following_nodes->first->content, 'after', 'right content';
+is $dom->at('p')->following_nodes->size, 1, 'right number of nodes';
+is $dom->child_nodes->first->following_nodes->first->tag, 'p', 'right tag';
+is $dom->child_nodes->first->following_nodes->last->content, 'after',
+ 'right content';
+is $dom->child_nodes->first->following_nodes->size, 2, 'right number of nodes';
+is $dom->following_nodes->size, 0, 'no following nodes';
+is $dom->at('p')->previous_node->content, ' before', 'right content';
+is $dom->at('p')->previous_node->previous_node, undef, 'no more siblings';
+is $dom->at('p')->next_node->content, 'after', 'right content';
+is $dom->at('p')->next_node->next_node, undef, 'no more siblings';
+is $dom->at('p')->child_nodes->last->previous_node->previous_node->content,
+ 'test', 'right content';
+is $dom->at('p')->child_nodes->first->next_node->next_node->content, ' 456 ',
+ 'right content';
+is $dom->descendant_nodes->[0]->type, 'doctype', 'right type';
+is $dom->descendant_nodes->[0]->content, ' before', 'right content';
+is $dom->descendant_nodes->[0], '<!DOCTYPE before>', 'right content';
+is $dom->descendant_nodes->[1]->tag, 'p', 'right tag';
+is $dom->descendant_nodes->[2]->type, 'text', 'right type';
+is $dom->descendant_nodes->[2]->content, 'test', 'right content';
+is $dom->descendant_nodes->[5]->type, 'pi', 'right type';
+is $dom->descendant_nodes->[5]->content, 'after', 'right content';
+is $dom->at('p')->descendant_nodes->[0]->type, 'text', 'right type';
+is $dom->at('p')->descendant_nodes->[0]->content, 'test', 'right type';
+is $dom->at('p')->descendant_nodes->last->type, 'comment', 'right type';
+is $dom->at('p')->descendant_nodes->last->content, ' 456 ', 'right type';
+is $dom->child_nodes->[1]->child_nodes->first->parent->tag, 'p', 'right tag';
+is $dom->child_nodes->[1]->child_nodes->first->content, 'test', 'right content';
+is $dom->child_nodes->[1]->child_nodes->first, 'test', 'right content';
+is $dom->at('p')->child_nodes->first->type, 'text', 'right type';
+is $dom->at('p')->child_nodes->first->remove->tag, 'p', 'right tag';
+is $dom->at('p')->child_nodes->first->type, 'cdata', 'right type';
+is $dom->at('p')->child_nodes->first->content, '123', 'right content';
+is $dom->at('p')->child_nodes->[1]->type, 'comment', 'right type';
+is $dom->at('p')->child_nodes->[1]->content, ' 456 ', 'right content';
+is $dom->[0]->type, 'doctype', 'right type';
+is $dom->[0]->content, ' before', 'right content';
+is $dom->child_nodes->[2]->type, 'pi', 'right type';
+is $dom->child_nodes->[2]->content, 'after', 'right content';
+is $dom->child_nodes->first->content(' again')->content, ' again',
+ 'right content';
+is $dom->child_nodes->grep(sub { $_->type eq 'pi' })->map('remove')
+ ->first->type, 'root', 'right type';
+is "$dom", '<!DOCTYPE again><p><![CDATA[123]]><!-- 456 --></p>', 'right result';
+
+# Modify nodes
+$dom = DOM::Tiny->new('<script>la<la>la</script>');
+is $dom->at('script')->type, 'tag', 'right type';
+is $dom->at('script')->[0]->type, 'raw', 'right type';
+is $dom->at('script')->[0]->content, 'la<la>la', 'right content';
+is "$dom", '<script>la<la>la</script>', 'right result';
+is $dom->at('script')->child_nodes->first->replace('a<b>c</b>1<b>d</b>')->tag,
+ 'script', 'right tag';
+is "$dom", '<script>a<b>c</b>1<b>d</b></script>', 'right result';
+is $dom->at('b')->child_nodes->first->append('e')->content, 'c',
+ 'right content';
+is $dom->at('b')->child_nodes->first->prepend('f')->type, 'text', 'right type';
+is "$dom", '<script>a<b>fce</b>1<b>d</b></script>', 'right result';
+is $dom->at('script')->child_nodes->first->following->first->tag, 'b',
+ 'right tag';
+is $dom->at('script')->child_nodes->first->next->content, 'fce',
+ 'right content';
+is $dom->at('script')->child_nodes->first->previous, undef, 'no siblings';
+is $dom->at('script')->child_nodes->[2]->previous->content, 'fce',
+ 'right content';
+is $dom->at('b')->child_nodes->[1]->next, undef, 'no siblings';
+is $dom->at('script')->child_nodes->first->wrap('<i>:)</i>')->root,
+ '<script><i>:)a</i><b>fce</b>1<b>d</b></script>', 'right result';
+is $dom->at('i')->child_nodes->first->wrap_content('<b></b>')->root,
+ '<script><i><b>:)</b>a</i><b>fce</b>1<b>d</b></script>', 'right result';
+is $dom->at('b')->child_nodes->first->ancestors->map('tag')->join(','),
+ 'b,i,script', 'right result';
+is $dom->at('b')->child_nodes->first->append_content('g')->content, ':)g',
+ 'right content';
+is $dom->at('b')->child_nodes->first->prepend_content('h')->content, 'h:)g',
+ 'right content';
+is "$dom", '<script><i><b>h:)g</b>a</i><b>fce</b>1<b>d</b></script>',
+ 'right result';
+is $dom->at('script > b:last-of-type')->append('<!--y-->')
+ ->following_nodes->first->content, 'y', 'right content';
+is $dom->at('i')->prepend('z')->preceding_nodes->first->content, 'z',
+ 'right content';
+is $dom->at('i')->following->last->text, 'd', 'right text';
+is $dom->at('i')->following->size, 2, 'right number of following elements';
+is $dom->at('i')->following('b:last-of-type')->first->text, 'd', 'right text';
+is $dom->at('i')->following('b:last-of-type')->size, 1,
+ 'right number of following elements';
+is $dom->following->size, 0, 'no following elements';
+is $dom->at('script > b:last-of-type')->preceding->first->tag, 'i', 'right tag';
+is $dom->at('script > b:last-of-type')->preceding->size, 2,
+ 'right number of preceding elements';
+is $dom->at('script > b:last-of-type')->preceding('b')->first->tag, 'b',
+ 'right tag';
+is $dom->at('script > b:last-of-type')->preceding('b')->size, 1,
+ 'right number of preceding elements';
+is $dom->preceding->size, 0, 'no preceding elements';
+is "$dom", '<script>z<i><b>h:)g</b>a</i><b>fce</b>1<b>d</b><!--y--></script>',
+ 'right result';
+
+# XML nodes
+$dom = DOM::Tiny->new->xml(1)->parse('<b>test<image /></b>');
+ok $dom->at('b')->child_nodes->first->xml, 'XML mode active';
+ok $dom->at('b')->child_nodes->first->replace('<br>')->child_nodes->first->xml,
+ 'XML mode active';
+is "$dom", '<b><br /><image /></b>', 'right result';
+
+# Treating nodes as elements
+$dom = DOM::Tiny->new('foo<b>bar</b>baz');
+is $dom->child_nodes->first->child_nodes->size, 0, 'no nodes';
+is $dom->child_nodes->first->descendant_nodes->size, 0, 'no nodes';
+is $dom->child_nodes->first->children->size, 0, 'no children';
+is $dom->child_nodes->first->strip->parent, 'foo<b>bar</b>baz', 'no changes';
+is $dom->child_nodes->first->at('b'), undef, 'no result';
+is $dom->child_nodes->first->find('*')->size, 0, 'no results';
+ok !$dom->child_nodes->first->matches('*'), 'no match';
+is_deeply $dom->child_nodes->first->attr, {}, 'no attributes';
+is $dom->child_nodes->first->namespace, undef, 'no namespace';
+is $dom->child_nodes->first->tag, undef, 'no tag';
+is $dom->child_nodes->first->text, '', 'no text';
+is $dom->child_nodes->first->all_text, '', 'no text';
+
+# Class and ID
+$dom = DOM::Tiny->new('<div id="id" class="class">a</div>');
+is $dom->at('div#id.class')->text, 'a', 'right text';
+
+# Deep nesting (parent combinator)
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head>
+ <title>Foo</title>
+ </head>
+ <body>
+ <div id="container">
+ <div id="header">
+ <div id="logo">Hello World</div>
+ <div id="buttons">
+ <p id="foo">Foo</p>
+ </div>
+ </div>
+ <form>
+ <div id="buttons">
+ <p id="bar">Bar</p>
+ </div>
+ </form>
+ <div id="content">More stuff</div>
+ </div>
+ </body>
+</html>
+EOF
+my $p = $dom->find('body > #container > div p[id]');
+is $p->[0]->attr('id'), 'foo', 'right id attribute';
+is $p->[1], undef, 'no second result';
+is $p->size, 1, 'right number of elements';
+my @p;
+@div = ();
+$dom->find('div')->each(sub { push @div, $_->attr('id') });
+$dom->find('p')->each(sub { push @p, $_->attr('id') });
+is_deeply \@p, [qw(foo bar)], 'found all p elements';
+my $ids = [qw(container header logo buttons buttons content)];
+is_deeply \@div, $ids, 'found all div elements';
+is_deeply [$dom->at('p')->ancestors->map('tag')->each],
+ [qw(div div div body html)], 'right results';
+is_deeply [$dom->at('html')->ancestors->each], [], 'no results';
+is_deeply [$dom->ancestors->each], [], 'no results';
+
+# Script tag
+$dom = DOM::Tiny->new(<<EOF);
+<script charset="utf-8">alert('lalala');</script>
+EOF
+is $dom->at('script')->text, "alert('lalala');", 'right script content';
+
+# HTML5 (unquoted values)
+$dom = DOM::Tiny->new(
+ '<div id = test foo ="bar" class=tset bar=/baz/ baz=//>works</div>');
+is $dom->at('#test')->text, 'works', 'right text';
+is $dom->at('div')->text, 'works', 'right text';
+is $dom->at('[foo=bar][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';
+is $dom->at('[bar=/baz/]')->text, 'works', 'right text';
+is $dom->at('[baz=//]')->text, 'works', 'right text';
+
+# HTML1 (single quotes, uppercase tags and whitespace in attributes)
+$dom = DOM::Tiny->new(q{<DIV id = 'test' foo ='bar' class= "tset">works</DIV>});
+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
+$dom = DOM::Tiny->new('<div id="snow'm"an">☃</div>');
+is $dom->at('[id="snow\'m\"an"]')->text, '☃', 'right text';
+is $dom->at('[id="snow\'m\22 an"]')->text, '☃', 'right text';
+is $dom->at('[id="snow\'m\000022an"]')->text, '☃', 'right text';
+is $dom->at('[id="snow\'m\22an"]'), undef, 'no result';
+is $dom->at('[id="snow\'m\21 an"]'), undef, 'no result';
+is $dom->at('[id="snow\'m\000021an"]'), undef, 'no result';
+is $dom->at('[id="snow\'m\000021 an"]'), undef, 'no result';
+is $dom->at("[id='snow\\'m\"an']")->text, '☃', 'right text';
+is $dom->at("[id='snow\\27m\"an']")->text, '☃', 'right text';
+
+# Unicode and escaped selectors
+my $html
+ = '<html><div id="☃x">Snowman</div><div class="x ♥">Heart</div></html>';
+$dom = DOM::Tiny->new($html);
+is $dom->at("#\\\n\\002603x")->text, 'Snowman', 'right text';
+is $dom->at('#\\2603 x')->text, 'Snowman', 'right text';
+is $dom->at("#\\\n\\2603 x")->text, 'Snowman', 'right text';
+is $dom->at(qq{[id="\\\n\\2603 x"]})->text, 'Snowman', 'right text';
+is $dom->at(qq{[id="\\\n\\002603x"]})->text, 'Snowman', 'right text';
+is $dom->at(qq{[id="\\\\2603 x"]})->text, 'Snowman', 'right text';
+is $dom->at("html #\\\n\\002603x")->text, 'Snowman', 'right text';
+is $dom->at('html #\\2603 x')->text, 'Snowman', 'right text';
+is $dom->at("html #\\\n\\2603 x")->text, 'Snowman', 'right text';
+is $dom->at(qq{html [id="\\\n\\2603 x"]})->text, 'Snowman', 'right text';
+is $dom->at(qq{html [id="\\\n\\002603x"]})->text, 'Snowman', 'right text';
+is $dom->at(qq{html [id="\\\\2603 x"]})->text, 'Snowman', 'right text';
+is $dom->at('#☃x')->text, 'Snowman', 'right text';
+is $dom->at('div#☃x')->text, 'Snowman', 'right text';
+is $dom->at('html div#☃x')->text, 'Snowman', 'right text';
+is $dom->at('[id^="☃"]')->text, 'Snowman', 'right text';
+is $dom->at('div[id^="☃"]')->text, 'Snowman', 'right text';
+is $dom->at('html div[id^="☃"]')->text, 'Snowman', 'right text';
+is $dom->at('html > 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('html div[id^=☃]')->text, 'Snowman', 'right text';
+is $dom->at('html > 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("html .\\\n\\002665")->text, 'Heart', 'right text';
+is $dom->at('html .\\2665')->text, 'Heart', 'right text';
+is $dom->at(qq{html [class\$="\\\n\\002665"]})->text, 'Heart', 'right text';
+is $dom->at(qq{html [class\$="\\2665"]})->text, 'Heart', 'right text';
+is $dom->at(qq{[class\$="\\\n\\002665"]})->text, 'Heart', 'right text';
+is $dom->at(qq{[class\$="\\2665"]})->text, 'Heart', 'right text';
+is $dom->at('.x')->text, 'Heart', 'right text';
+is $dom->at('html .x')->text, 'Heart', 'right text';
+is $dom->at('.♥')->text, 'Heart', 'right text';
+is $dom->at('html .♥')->text, 'Heart', 'right text';
+is $dom->at('div.♥')->text, 'Heart', 'right text';
+is $dom->at('html div.♥')->text, 'Heart', 'right text';
+is $dom->at('[class$="♥"]')->text, 'Heart', 'right text';
+is $dom->at('div[class$="♥"]')->text, 'Heart', 'right text';
+is $dom->at('html div[class$="♥"]')->text, 'Heart', 'right text';
+is $dom->at('html > 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('html div[class$=♥]')->text, 'Heart', 'right text';
+is $dom->at('html > 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('html div[class~="♥"]')->text, 'Heart', 'right text';
+is $dom->at('html > 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('html div[class~=♥]')->text, 'Heart', 'right text';
+is $dom->at('html > 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('html div[class~="x"]')->text, 'Heart', 'right text';
+is $dom->at('html > 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('html div[class~=x]')->text, 'Heart', 'right text';
+is $dom->at('html > div[class~=x]')->text, 'Heart', 'right text';
+is $dom->at('html'), $html, 'right result';
+is $dom->at('#☃x')->parent, $html, 'right result';
+is $dom->at('#☃x')->root, $html, 'right result';
+is $dom->children('html')->first, $html, 'right result';
+is $dom->to_string, $html, 'right result';
+is $dom->content, $html, 'right result';
+
+# Looks remotely like HTML
+$dom = DOM::Tiny->new(
+ '<!DOCTYPE H "-/W/D HT 4/E">☃<title class=test>♥</title>☃');
+is $dom->at('title')->text, '♥', 'right text';
+is $dom->at('*')->text, '♥', 'right text';
+is $dom->at('.test')->text, '♥', 'right text';
+
+# Replace elements
+$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
+is $dom->at('p')->replace('<foo>bar</foo>'), '<div>foo<foo>bar</foo>bar</div>',
+ 'right result';
+is "$dom", '<div>foo<foo>bar</foo>bar</div>', 'right result';
+$dom->at('foo')->replace(DOM::Tiny->new('text'));
+is "$dom", '<div>footextbar</div>', 'right result';
+$dom = DOM::Tiny->new('<div>foo</div><div>bar</div>');
+$dom->find('div')->each(sub { shift->replace('<p>test</p>') });
+is "$dom", '<p>test</p><p>test</p>', 'right result';
+$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
+is $dom->replace('♥'), '♥', 'right result';
+is "$dom", '♥', 'right result';
+$dom->replace('<div>foo<p>lalala</p>bar</div>');
+is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
+is $dom->at('p')->replace(''), '<div>foobar</div>', 'right result';
+is "$dom", '<div>foobar</div>', 'right result';
+is $dom->replace(''), '', 'no result';
+is "$dom", '', 'no result';
+$dom->replace('<div>foo<p>lalala</p>bar</div>');
+is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
+$dom->find('p')->map(replace => '');
+is "$dom", '<div>foobar</div>', 'right result';
+$dom = DOM::Tiny->new('<div>♥</div>');
+$dom->at('div')->content('☃');
+is "$dom", '<div>☃</div>', 'right result';
+$dom = DOM::Tiny->new('<div>♥</div>');
+$dom->at('div')->content("\x{2603}");
+is $dom->to_string, '<div>☃</div>', 'right result';
+is $dom->at('div')->replace('<p>♥</p>')->root, '<p>♥</p>', 'right result';
+is $dom->to_string, '<p>♥</p>', 'right result';
+is $dom->replace('<b>whatever</b>')->root, '<b>whatever</b>', 'right result';
+is $dom->to_string, '<b>whatever</b>', 'right result';
+$dom->at('b')->prepend('<p>foo</p>')->append('<p>bar</p>');
+is "$dom", '<p>foo</p><b>whatever</b><p>bar</p>', 'right result';
+is $dom->find('p')->map('remove')->first->root->at('b')->text, 'whatever',
+ 'right result';
+is "$dom", '<b>whatever</b>', 'right result';
+is $dom->at('b')->strip, 'whatever', 'right result';
+is $dom->strip, 'whatever', 'right result';
+is $dom->remove, '', 'right result';
+$dom->replace('A<div>B<p>C<b>D<i><u>E</u></i>F</b>G</p><div>H</div></div>I');
+is $dom->find(':not(div):not(i):not(u)')->map('strip')->first->root,
+ 'A<div>BCD<i><u>E</u></i>FG<div>H</div></div>I', 'right result';
+is $dom->at('i')->to_string, '<i><u>E</u></i>', 'right result';
+$dom = DOM::Tiny->new('<div><div>A</div><div>B</div>C</div>');
+is $dom->at('div')->at('div')->text, 'A', 'right text';
+$dom->at('div')->find('div')->map('strip');
+is "$dom", '<div>ABC</div>', 'right result';
+
+# Replace element content
+$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
+is $dom->at('p')->content('bar'), '<p>bar</p>', 'right result';
+is "$dom", '<div>foo<p>bar</p>bar</div>', 'right result';
+$dom->at('p')->content(DOM::Tiny->new('text'));
+is "$dom", '<div>foo<p>text</p>bar</div>', 'right result';
+$dom = DOM::Tiny->new('<div>foo</div><div>bar</div>');
+$dom->find('div')->each(sub { shift->content('<p>test</p>') });
+is "$dom", '<div><p>test</p></div><div><p>test</p></div>', 'right result';
+$dom->find('p')->each(sub { shift->content('') });
+is "$dom", '<div><p></p></div><div><p></p></div>', 'right result';
+$dom = DOM::Tiny->new('<div><p id="☃" /></div>');
+$dom->at('#☃')->content('♥');
+is "$dom", '<div><p id="☃">♥</p></div>', 'right result';
+$dom = DOM::Tiny->new('<div>foo<p>lalala</p>bar</div>');
+$dom->content('♥');
+is "$dom", '♥', 'right result';
+is $dom->content('<div>foo<p>lalala</p>bar</div>'),
+ '<div>foo<p>lalala</p>bar</div>', 'right result';
+is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
+is $dom->content(''), '', 'no result';
+is "$dom", '', 'no result';
+$dom->content('<div>foo<p>lalala</p>bar</div>');
+is "$dom", '<div>foo<p>lalala</p>bar</div>', 'right result';
+is $dom->at('p')->content(''), '<p></p>', 'right result';
+
+# Mixed search and tree walk
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <tr>
+ <td>text1</td>
+ <td>text2</td>
+ </tr>
+</table>
+EOF
+my @data;
+for my $tr ($dom->find('table tr')->each) {
+ for my $td (@{$tr->children}) {
+ push @data, $td->tag, $td->all_text;
+ }
+}
+is $data[0], 'td', 'right tag';
+is $data[1], 'text1', 'right text';
+is $data[2], 'td', 'right tag';
+is $data[3], 'text2', 'right text';
+is $data[4], undef, 'no tag';
+
+# RSS
+$dom = DOM::Tiny->new(<<EOF);
+<?xml version="1.0" encoding="UTF-8"?>
+<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
+ <channel>
+ <title>Test Blog</title>
+ <link>http://blog.example.com</link>
+ <description>lalala</description>
+ <generator>DOM::Tiny</generator>
+ <item>
+ <pubDate>Mon, 12 Jul 2010 20:42:00</pubDate>
+ <title>Works!</title>
+ <link>http://blog.example.com/test</link>
+ <guid>http://blog.example.com/test</guid>
+ <description>
+ <![CDATA[<p>trololololo>]]>
+ </description>
+ <my:extension foo:id="works">
+ <![CDATA[
+ [awesome]]
+ ]]>
+ </my:extension>
+ </item>
+ </channel>
+</rss>
+EOF
+ok $dom->xml, 'XML mode detected';
+is $dom->find('rss')->[0]->attr('version'), '2.0', 'right version';
+is_deeply [$dom->at('title')->ancestors->map('tag')->each], [qw(channel rss)],
+ 'right results';
+is $dom->at('extension')->attr('foo:id'), 'works', 'right id';
+like $dom->at('#works')->text, qr/\[awesome\]\]/, 'right text';
+like $dom->at('[id="works"]')->text, qr/\[awesome\]\]/, 'right text';
+is $dom->find('description')->[1]->text, '<p>trololololo>', 'right text';
+is $dom->at('pubDate')->text, 'Mon, 12 Jul 2010 20:42:00', 'right text';
+like $dom->at('[id*="ork"]')->text, qr/\[awesome\]\]/, 'right text';
+like $dom->at('[id*="orks"]')->text, qr/\[awesome\]\]/, 'right text';
+like $dom->at('[id*="work"]')->text, qr/\[awesome\]\]/, 'right text';
+like $dom->at('[id*="or"]')->text, qr/\[awesome\]\]/, 'right text';
+ok $dom->at('rss')->xml, 'XML mode active';
+ok $dom->at('extension')->parent->xml, 'XML mode active';
+ok $dom->at('extension')->root->xml, 'XML mode active';
+ok $dom->children('rss')->first->xml, 'XML mode active';
+ok $dom->at('title')->ancestors->first->xml, 'XML mode active';
+
+# Namespace
+$dom = DOM::Tiny->new(<<EOF);
+<?xml version="1.0"?>
+<bk:book xmlns='uri:default-ns'
+ xmlns:bk='uri:book-ns'
+ xmlns:isbn='uri:isbn-ns'>
+ <bk:title>Programming Perl</bk:title>
+ <comment>rocks!</comment>
+ <nons xmlns=''>
+ <section>Nothing</section>
+ </nons>
+ <meta xmlns='uri:meta-ns'>
+ <isbn:number>978-0596000271</isbn:number>
+ </meta>
+</bk:book>
+EOF
+ok $dom->xml, 'XML mode detected';
+is $dom->namespace, undef, 'no namespace';
+is $dom->at('book comment')->namespace, 'uri:default-ns', 'right namespace';
+is $dom->at('book comment')->text, 'rocks!', 'right text';
+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',
+ 'right attribute';
+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';
+is $dom->at('[bk]')->{'xmlns:bk'}, 'uri:book-ns', 'right attribute';
+is $dom->at('[bk]')->attr('xmlns:bk'), 'uri:book-ns', 'right attribute';
+is $dom->at('[bk]')->attr('s:bk'), undef, 'no attribute';
+is $dom->at('[bk]')->attr('bk'), undef, 'no attribute';
+is $dom->at('[bk]')->attr('k'), undef, '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')->matches('book > nons'), 'element did match';
+ok !$dom->at('title')->matches('book > nons > section'),
+ 'element did not match';
+
+# Dots
+$dom = DOM::Tiny->new(<<EOF);
+<?xml version="1.0"?>
+<foo xmlns:foo.bar="uri:first">
+ <bar xmlns:fooxbar="uri:second">
+ <foo.bar:baz>First</fooxbar:baz>
+ <fooxbar:ya.da>Second</foo.bar:ya.da>
+ </bar>
+</foo>
+EOF
+is $dom->at('foo bar baz')->text, 'First', 'right text';
+is $dom->at('baz')->namespace, 'uri:first', 'right namespace';
+is $dom->at('foo bar ya\.da')->text, 'Second', 'right text';
+is $dom->at('ya\.da')->namespace, 'uri:second', 'right namespace';
+is $dom->at('foo')->namespace, undef, 'no namespace';
+is $dom->at('[xml\.s]'), undef, 'no result';
+is $dom->at('b\.z'), undef, 'no result';
+
+# Yadis
+$dom = DOM::Tiny->new(<<'EOF');
+<?xml version="1.0" encoding="UTF-8"?>
+<XRDS xmlns="xri://$xrds">
+ <XRD xmlns="xri://$xrd*($v*2.0)">
+ <Service>
+ <Type>http://o.r.g/sso/2.0</Type>
+ </Service>
+ <Service>
+ <Type>http://o.r.g/sso/1.0</Type>
+ </Service>
+ </XRD>
+</XRDS>
+EOF
+ok $dom->xml, 'XML mode detected';
+is $dom->at('XRDS')->namespace, 'xri://$xrds', 'right namespace';
+is $dom->at('XRD')->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+my $s = $dom->find('XRDS XRD Service');
+is $s->[0]->at('Type')->text, 'http://o.r.g/sso/2.0', 'right text';
+is $s->[0]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+is $s->[1]->at('Type')->text, 'http://o.r.g/sso/1.0', 'right text';
+is $s->[1]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+is $s->[2], undef, 'no result';
+is $s->size, 2, 'right number of elements';
+
+# Yadis (roundtrip with namespace)
+my $yadis = <<'EOF';
+<?xml version="1.0" encoding="UTF-8"?>
+<xrds:XRDS xmlns="xri://$xrd*($v*2.0)" xmlns:xrds="xri://$xrds">
+ <XRD>
+ <Service>
+ <Type>http://o.r.g/sso/3.0</Type>
+ </Service>
+ <xrds:Service>
+ <Type>http://o.r.g/sso/4.0</Type>
+ </xrds:Service>
+ </XRD>
+ <XRD>
+ <Service>
+ <Type test="23">http://o.r.g/sso/2.0</Type>
+ </Service>
+ <Service>
+ <Type Test="23" test="24">http://o.r.g/sso/1.0</Type>
+ </Service>
+ </XRD>
+</xrds:XRDS>
+EOF
+$dom = DOM::Tiny->new($yadis);
+ok $dom->xml, 'XML mode detected';
+is $dom->at('XRDS')->namespace, 'xri://$xrds', 'right namespace';
+is $dom->at('XRD')->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+$s = $dom->find('XRDS XRD Service');
+is $s->[0]->at('Type')->text, 'http://o.r.g/sso/3.0', 'right text';
+is $s->[0]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+is $s->[1]->at('Type')->text, 'http://o.r.g/sso/4.0', 'right text';
+is $s->[1]->namespace, 'xri://$xrds', 'right namespace';
+is $s->[2]->at('Type')->text, 'http://o.r.g/sso/2.0', 'right text';
+is $s->[2]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+is $s->[3]->at('Type')->text, 'http://o.r.g/sso/1.0', 'right text';
+is $s->[3]->namespace, 'xri://$xrd*($v*2.0)', 'right namespace';
+is $s->[4], undef, 'no result';
+is $s->size, 4, 'right number of elements';
+is $dom->at('[Test="23"]')->text, 'http://o.r.g/sso/1.0', 'right text';
+is $dom->at('[test="23"]')->text, 'http://o.r.g/sso/2.0', 'right text';
+is $dom->find('xrds\:Service > Type')->[0]->text, 'http://o.r.g/sso/4.0',
+ 'right text';
+is $dom->find('xrds\:Service > Type')->[1], undef, 'no result';
+is $dom->find('xrds\3AService > Type')->[0]->text, 'http://o.r.g/sso/4.0',
+ 'right text';
+is $dom->find('xrds\3AService > Type')->[1], undef, 'no result';
+is $dom->find('xrds\3A Service > Type')->[0]->text, 'http://o.r.g/sso/4.0',
+ 'right text';
+is $dom->find('xrds\3A Service > Type')->[1], undef, 'no result';
+is $dom->find('xrds\00003AService > Type')->[0]->text, 'http://o.r.g/sso/4.0',
+ 'right text';
+is $dom->find('xrds\00003AService > Type')->[1], undef, 'no result';
+is $dom->find('xrds\00003A Service > Type')->[0]->text, 'http://o.r.g/sso/4.0',
+ 'right text';
+is $dom->find('xrds\00003A Service > Type')->[1], undef, 'no result';
+is "$dom", $yadis, 'successful roundtrip';
+
+# Result and iterator order
+$dom = DOM::Tiny->new('<a><b>1</b></a><b>2</b><b>3</b>');
+my @numbers;
+$dom->find('b')->each(sub { push @numbers, pop, shift->text });
+is_deeply \@numbers, [1, 1, 2, 2, 3, 3], 'right order';
+
+# Attributes on multiple lines
+$dom = DOM::Tiny->new("<div test=23 id='a' \n class='x' foo=bar />");
+is $dom->at('div.x')->attr('test'), 23, 'right attribute';
+is $dom->at('[foo="bar"]')->attr('class'), 'x', 'right attribute';
+is $dom->at('div')->attr(baz => undef)->root->to_string,
+ '<div baz class="x" foo="bar" id="a" test="23"></div>', 'right result';
+
+# Markup characters in attribute values
+$dom = DOM::Tiny->new(qq{<div id="<a>" \n test='='>Test<div id='><' /></div>});
+is $dom->at('div[id="<a>"]')->attr->{test}, '=', 'right attribute';
+is $dom->at('[id="<a>"]')->text, 'Test', 'right text';
+is $dom->at('[id="><"]')->attr->{id}, '><', 'right attribute';
+
+# Empty attributes
+$dom = DOM::Tiny->new(qq{<div test="" test2='' />});
+is $dom->at('div')->attr->{test}, '', 'empty attribute value';
+is $dom->at('div')->attr->{test2}, '', 'empty attribute value';
+is $dom->at('[test]')->tag, 'div', 'right tag';
+is $dom->at('[test2]')->tag, 'div', 'right tag';
+is $dom->at('[test3]'), undef, 'no result';
+is $dom->at('[test=""]')->tag, 'div', 'right tag';
+is $dom->at('[test2=""]')->tag, 'div', 'right tag';
+is $dom->at('[test3=""]'), undef, 'no result';
+
+# Multi-line in attribute
+$dom = DOM::Tiny->new(qq{<div test="line1\nline2" />});
+is $dom->at('div')->attr->{test}, "line1\nline2", 'multi-line attribute';
+
+# Whitespaces before closing bracket
+$dom = DOM::Tiny->new('<div >content</div>');
+ok $dom->at('div'), 'tag found';
+is $dom->at('div')->text, 'content', 'right text';
+is $dom->at('div')->content, 'content', 'right text';
+
+# Class with hyphen
+$dom = DOM::Tiny->new('<div class="a">A</div><div class="a-1">A1</div>');
+@div = ();
+$dom->find('.a')->each(sub { push @div, shift->text });
+is_deeply \@div, ['A'], 'found first element only';
+@div = ();
+$dom->find('.a-1')->each(sub { push @div, shift->text });
+is_deeply \@div, ['A1'], 'found last element only';
+
+# Defined but false text
+$dom = DOM::Tiny->new(
+ '<div><div id="a">A</div><div id="b">B</div></div><div id="0">0</div>');
+@div = ();
+$dom->find('div[id]')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A B 0)], 'found all div elements with id';
+
+# Empty tags
+$dom = DOM::Tiny->new('<hr /><br/><br id="br"/><br />');
+is "$dom", '<hr><br><br id="br"><br>', 'right result';
+is $dom->at('br')->content, '', 'empty result';
+
+# Inner XML
+$dom = DOM::Tiny->new('<a>xxx<x>x</x>xxx</a>');
+is $dom->at('a')->content, 'xxx<x>x</x>xxx', 'right result';
+is $dom->content, '<a>xxx<x>x</x>xxx</a>', 'right result';
+
+# Multiple selectors
+$dom = DOM::Tiny->new(
+ '<div id="a">A</div><div id="b">B</div><div id="c">C</div><p>D</p>');
+@div = ();
+$dom->find('p, div')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A B C D)], 'found all elements';
+@div = ();
+$dom->find('#a, #c')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
+@div = ();
+$dom->find('div#a, div#b')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A B)], 'found all div elements with the right ids';
+@div = ();
+$dom->find('div[id="a"], div[id="c"]')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
+$dom = DOM::Tiny->new(
+ '<div id="☃">A</div><div id="b">B</div><div id="♥x">C</div>');
+@div = ();
+$dom->find('#☃, #♥x')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
+@div = ();
+$dom->find('div#☃, div#b')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A B)], 'found all div elements with the right ids';
+@div = ();
+$dom->find('div[id="☃"], div[id="♥x"]')
+ ->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A C)], 'found all div elements with the right ids';
+
+# Multiple attributes
+$dom = DOM::Tiny->new(<<EOF);
+<div foo="bar" bar="baz">A</div>
+<div foo="bar">B</div>
+<div foo="bar" bar="baz">C</div>
+<div foo="baz" bar="baz">D</div>
+EOF
+@div = ();
+$dom->find('div[foo="bar"][bar="baz"]')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A C)], 'found all div elements with the right atributes';
+@div = ();
+$dom->find('div[foo^="b"][foo$="r"]')->each(sub { push @div, shift->text });
+is_deeply \@div, [qw(A B C)], 'found all div elements with the right atributes';
+is $dom->at('[foo="bar"]')->previous, undef, 'no previous sibling';
+is $dom->at('[foo="bar"]')->next->text, 'B', 'right text';
+is $dom->at('[foo="bar"]')->next->previous->text, 'A', 'right text';
+is $dom->at('[foo="bar"]')->next->next->next->next, undef, 'no next sibling';
+
+# Pseudo-classes
+$dom = DOM::Tiny->new(<<EOF);
+<form action="/foo">
+ <input type="text" name="user" value="test" />
+ <input type="checkbox" checked="checked" name="groovy">
+ <select name="a">
+ <option value="b">b</option>
+ <optgroup label="c">
+ <option value="d">d</option>
+ <option selected="selected" value="e">E</option>
+ <option value="f">f</option>
+ </optgroup>
+ <option value="g">g</option>
+ <option selected value="h">H</option>
+ </select>
+ <input type="submit" value="Ok!" />
+ <input type="checkbox" checked name="I">
+ <p id="content">test 123</p>
+ <p id="no_content"><? test ?><!-- 123 --></p>
+</form>
+EOF
+is $dom->find(':root')->[0]->tag, 'form', 'right tag';
+is $dom->find('*:root')->[0]->tag, 'form', 'right tag';
+is $dom->find('form:root')->[0]->tag, 'form', 'right tag';
+is $dom->find(':root')->[1], undef, 'no result';
+is $dom->find(':checked')->[0]->attr->{name}, 'groovy', 'right name';
+is $dom->find('option:checked')->[0]->attr->{value}, 'e', 'right value';
+is $dom->find(':checked')->[1]->text, 'E', 'right text';
+is $dom->find('*:checked')->[1]->text, 'E', 'right text';
+is $dom->find(':checked')->[2]->text, 'H', 'right name';
+is $dom->find(':checked')->[3]->attr->{name}, 'I', 'right name';
+is $dom->find(':checked')->[4], undef, 'no result';
+is $dom->find('option[selected]')->[0]->attr->{value}, 'e', 'right value';
+is $dom->find('option[selected]')->[1]->text, 'H', 'right text';
+is $dom->find('option[selected]')->[2], undef, 'no result';
+is $dom->find(':checked[value="e"]')->[0]->text, 'E', 'right text';
+is $dom->find('*:checked[value="e"]')->[0]->text, 'E', 'right text';
+is $dom->find('option:checked[value="e"]')->[0]->text, 'E', 'right text';
+is $dom->at('optgroup option:checked[value="e"]')->text, 'E', 'right text';
+is $dom->at('select option:checked[value="e"]')->text, 'E', 'right text';
+is $dom->at('select :checked[value="e"]')->text, 'E', 'right text';
+is $dom->at('optgroup > :checked[value="e"]')->text, 'E', 'right text';
+is $dom->at('select *:checked[value="e"]')->text, 'E', 'right text';
+is $dom->at('optgroup > *:checked[value="e"]')->text, 'E', 'right text';
+is $dom->find(':checked[value="e"]')->[1], undef, 'no result';
+is $dom->find(':empty')->[0]->attr->{name}, 'user', 'right name';
+is $dom->find('input:empty')->[0]->attr->{name}, 'user', 'right name';
+is $dom->at(':empty[type^="ch"]')->attr->{name}, 'groovy', 'right name';
+is $dom->at('p')->attr->{id}, 'content', 'right attribute';
+is $dom->at('p:empty')->attr->{id}, 'no_content', 'right attribute';
+
+# More pseudo-classes
+$dom = DOM::Tiny->new(<<EOF);
+<ul>
+ <li>A</li>
+ <li>B</li>
+ <li>C</li>
+ <li>D</li>
+ <li>E</li>
+ <li>F</li>
+ <li>G</li>
+ <li>H</li>
+</ul>
+EOF
+my @li;
+$dom->find('li:nth-child(odd)')->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(ODD)')->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(odd)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(B D F H)], 'found all odd li elements';
+is $dom->find(':nth-child(odd)')->[0]->tag, 'ul', 'right tag';
+is $dom->find(':nth-child(odd)')->[1]->text, 'A', 'right text';
+is $dom->find(':nth-child(1)')->[0]->tag, 'ul', 'right tag';
+is $dom->find(':nth-child(1)')->[1]->text, 'A', 'right text';
+is $dom->find(':nth-last-child(odd)')->[0]->tag, 'ul', 'right tag';
+is $dom->find(':nth-last-child(odd)')->last->text, 'H', 'right text';
+is $dom->find(':nth-last-child(1)')->[0]->tag, 'ul', 'right tag';
+is $dom->find(':nth-last-child(1)')->[1]->text, 'H', 'right text';
+@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-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 = ();
+$dom->find('li:nth-child(even)')->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(EVEN)')->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( even )')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(A C E G)], '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-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 = ();
+$dom->find('li:nth-child(4n+1)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(A E)], 'found the right li elements';
+@li = ();
+$dom->find('li:nth-last-child(4n+1)')->each(sub { push @li, shift->text });
+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';
+@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';
+@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-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 = ();
+$dom->find('li:nth-child(-1n+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(-1n+3)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(F G H)], 'found first three li elements';
+@li = ();
+$dom->find('li:nth-child(3n)')->each(sub { push @li, shift->text });
+is_deeply \@li, [qw(C F)], 'found every third li elements';
+@li = ();
+$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-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-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 });
+is_deeply \@li, ['C'], 'found third li element';
+@li = ();
+$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';
+@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';
+@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';
+@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';
+@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';
+
+# Even more pseudo-classes
+$dom = DOM::Tiny->new(<<EOF);
+<ul>
+ <li>A</li>
+ <p>B</p>
+ <li class="test ♥">C</li>
+ <p>D</p>
+ <li>E</li>
+ <li>F</li>
+ <p>G</p>
+ <li>H</li>
+ <li>I</li>
+</ul>
+<div>
+ <div class="☃">J</div>
+</div>
+<div>
+ <a href="http://www.w3.org/DOM/">DOM</a>
+ <div class="☃">K</div>
+ <a href="http://www.w3.org/DOM/">DOM</a>
+</div>
+EOF
+my @e;
+$dom->find('ul :nth-child(odd)')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A C E G I)], 'found all odd elements';
+@e = ();
+$dom->find('li:nth-of-type(odd)')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A E H)], 'found all odd li elements';
+@e = ();
+$dom->find('li:nth-last-of-type( odd )')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(C F I)], 'found all odd li elements';
+@e = ();
+$dom->find('p:nth-of-type(odd)')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(B G)], 'found all odd p elements';
+@e = ();
+$dom->find('p:nth-last-of-type(odd)')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(B G)], 'found all odd li elements';
+@e = ();
+$dom->find('ul :nth-child(1)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['A'], 'found first child';
+@e = ();
+$dom->find('ul :first-child')->each(sub { push @e, shift->text });
+is_deeply \@e, ['A'], 'found first child';
+@e = ();
+$dom->find('p:nth-of-type(1)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['B'], 'found first child';
+@e = ();
+$dom->find('p:first-of-type')->each(sub { push @e, shift->text });
+is_deeply \@e, ['B'], 'found first child';
+@e = ();
+$dom->find('li:nth-of-type(1)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['A'], 'found first child';
+@e = ();
+$dom->find('li:first-of-type')->each(sub { push @e, shift->text });
+is_deeply \@e, ['A'], 'found first child';
+@e = ();
+$dom->find('ul :nth-last-child(-n+1)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['I'], 'found last child';
+@e = ();
+$dom->find('ul :last-child')->each(sub { push @e, shift->text });
+is_deeply \@e, ['I'], 'found last child';
+@e = ();
+$dom->find('p:nth-last-of-type(-n+1)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['G'], 'found last child';
+@e = ();
+$dom->find('p:last-of-type')->each(sub { push @e, shift->text });
+is_deeply \@e, ['G'], 'found last child';
+@e = ();
+$dom->find('li:nth-last-of-type(-n+1)')->each(sub { push @e, shift->text });
+is_deeply \@e, ['I'], 'found last child';
+@e = ();
+$dom->find('li:last-of-type')->each(sub { push @e, shift->text });
+is_deeply \@e, ['I'], 'found last child';
+@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';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not(.♥)')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A B)], 'found first and second element';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not([class$="♥"])')
+ ->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A B)], 'found first and second element';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not(li[class$="♥"])')
+ ->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A B)], 'found first and second element';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not([class$="♥"][class^="test"])')
+ ->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A B)], 'found first and second element';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not(*[class$="♥"])')
+ ->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(A B)], 'found first and second element';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not(:nth-child(-n+2))')
+ ->each(sub { push @e, shift->text });
+is_deeply \@e, ['C'], 'found third element';
+@e = ();
+$dom->find('ul :nth-child(-n+3):not(:nth-child(1)):not(:nth-child(2))')
+ ->each(sub { push @e, shift->text });
+is_deeply \@e, ['C'], 'found third element';
+@e = ();
+$dom->find(':only-child')->each(sub { push @e, shift->text });
+is_deeply \@e, ['J'], 'found only child';
+@e = ();
+$dom->find('div :only-of-type')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(J K)], 'found only child';
+@e = ();
+$dom->find('div:only-child')->each(sub { push @e, shift->text });
+is_deeply \@e, ['J'], 'found only child';
+@e = ();
+$dom->find('div div:only-of-type')->each(sub { push @e, shift->text });
+is_deeply \@e, [qw(J K)], 'found only child';
+
+# Sibling combinator
+$dom = DOM::Tiny->new(<<EOF);
+<ul>
+ <li>A</li>
+ <p>B</p>
+ <li>C</li>
+</ul>
+<h1>D</h1>
+<p id="♥">E</p>
+<p id="☃">F<b>H</b></p>
+<div>G</div>
+EOF
+is $dom->at('li ~ p')->text, 'B', 'right text';
+is $dom->at('li + p')->text, 'B', 'right text';
+is $dom->at('h1 ~ p ~ p')->text, 'F', 'right text';
+is $dom->at('h1 + p ~ p')->text, 'F', 'right text';
+is $dom->at('h1 ~ p + p')->text, 'F', 'right text';
+is $dom->at('h1 + p + p')->text, 'F', 'right text';
+is $dom->at('h1 + p+p')->text, 'F', 'right text';
+is $dom->at('ul > li ~ li')->text, 'C', 'right text';
+is $dom->at('ul li ~ li')->text, 'C', 'right text';
+is $dom->at('ul>li~li')->text, 'C', 'right text';
+is $dom->at('ul li li'), undef, 'no result';
+is $dom->at('ul ~ li ~ li'), undef, 'no result';
+is $dom->at('ul + li ~ li'), undef, 'no result';
+is $dom->at('ul > li + li'), undef, 'no result';
+is $dom->at('h1 ~ div')->text, 'G', 'right text';
+is $dom->at('h1 + div'), undef, 'no result';
+is $dom->at('p + div')->text, 'G', 'right text';
+is $dom->at('ul + h1 + p + p + div')->text, 'G', 'right text';
+is $dom->at('ul + h1 ~ p + div')->text, 'G', 'right text';
+is $dom->at('h1 ~ #♥')->text, 'E', 'right text';
+is $dom->at('h1 + #♥')->text, 'E', 'right text';
+is $dom->at('#♥~#☃')->text, 'F', 'right text';
+is $dom->at('#♥+#☃')->text, 'F', 'right text';
+is $dom->at('#♥+#☃>b')->text, 'H', 'right text';
+is $dom->at('#♥ > #☃'), undef, 'no result';
+is $dom->at('#♥ #☃'), undef, 'no result';
+is $dom->at('#♥ + #☃ + :nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ ~ #☃ + :nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ + #☃ ~ :nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ ~ #☃ ~ :nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ + :nth-last-child(2)')->text, 'F', 'right text';
+is $dom->at('#♥ ~ :nth-last-child(2)')->text, 'F', 'right text';
+is $dom->at('#♥ + #☃ + *:nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ ~ #☃ + *:nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ + #☃ ~ *:nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ ~ #☃ ~ *:nth-last-child(1)')->text, 'G', 'right text';
+is $dom->at('#♥ + *:nth-last-child(2)')->text, 'F', 'right text';
+is $dom->at('#♥ ~ *:nth-last-child(2)')->text, 'F', 'right text';
+
+# Adding nodes
+$dom = DOM::Tiny->new(<<EOF);
+<ul>
+ <li>A</li>
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>
+EOF
+$dom->at('li')->append('<p>A1</p>23');
+is "$dom", <<EOF, 'right result';
+<ul>
+ <li>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>
+EOF
+$dom->at('li')->prepend('24')->prepend('<div>A-1</div>25');
+is "$dom", <<EOF, 'right result';
+<ul>
+ 24<div>A-1</div>25<li>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>
+EOF
+is $dom->at('div')->text, 'A-1', 'right text';
+is $dom->at('iv'), undef, 'no result';
+$dom->prepend('l')->prepend('alal')->prepend('a');
+is "$dom", <<EOF, 'no change';
+<ul>
+ 24<div>A-1</div>25<li>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>
+EOF
+$dom->append('lalala');
+is "$dom", <<EOF, 'no change';
+<ul>
+ 24<div>A-1</div>25<li>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>
+EOF
+$dom->find('div')->each(sub { shift->append('works') });
+is "$dom", <<EOF, 'right result';
+<ul>
+ 24<div>A-1</div>works25<li>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>works
+EOF
+$dom->at('li')->prepend_content('A3<p>A2</p>')->prepend_content('A4');
+is $dom->at('li')->text, 'A4A3 A', 'right text';
+is "$dom", <<EOF, 'right result';
+<ul>
+ 24<div>A-1</div>works25<li>A4A3<p>A2</p>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C</li>
+</ul>
+<div>D</div>works
+EOF
+$dom->find('li')->[1]->append_content('<p>C2</p>C3')->append_content(' C4')
+ ->append_content('C5');
+is $dom->find('li')->[1]->text, 'C C3 C4C5', 'right text';
+is "$dom", <<EOF, 'right result';
+<ul>
+ 24<div>A-1</div>works25<li>A4A3<p>A2</p>A</li><p>A1</p>23
+ <p>B</p>
+ <li>C<p>C2</p>C3 C4C5</li>
+</ul>
+<div>D</div>works
+EOF
+
+# Optional "head" and "body" tags
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head>
+ <title>foo</title>
+ <body>bar
+EOF
+is $dom->at('html > head > title')->text, 'foo', 'right text';
+is $dom->at('html > body')->text, 'bar', 'right text';
+
+# Optional "li" tag
+$dom = DOM::Tiny->new(<<EOF);
+<ul>
+ <li>
+ <ol>
+ <li>F
+ <li>G
+ </ol>
+ <li>A</li>
+ <LI>B
+ <li>C</li>
+ <li>D
+ <li>E
+</ul>
+EOF
+is $dom->find('ul > li > ol > li')->[0]->text, 'F', 'right text';
+is $dom->find('ul > li > ol > li')->[1]->text, 'G', 'right text';
+is $dom->find('ul > li')->[1]->text, 'A', 'right text';
+is $dom->find('ul > li')->[2]->text, 'B', 'right text';
+is $dom->find('ul > li')->[3]->text, 'C', 'right text';
+is $dom->find('ul > li')->[4]->text, 'D', 'right text';
+is $dom->find('ul > li')->[5]->text, 'E', 'right text';
+
+# Optional "p" tag
+$dom = DOM::Tiny->new(<<EOF);
+<div>
+ <p>A</p>
+ <P>B
+ <p>C</p>
+ <p>D<div>X</div>
+ <p>E<img src="foo.png">
+ <p>F<br>G
+ <p>H
+</div>
+EOF
+is $dom->find('div > p')->[0]->text, 'A', 'right text';
+is $dom->find('div > p')->[1]->text, 'B', 'right text';
+is $dom->find('div > p')->[2]->text, 'C', 'right text';
+is $dom->find('div > p')->[3]->text, 'D', 'right text';
+is $dom->find('div > p')->[4]->text, 'E', 'right text';
+is $dom->find('div > p')->[5]->text, 'F G', 'right text';
+is $dom->find('div > p')->[6]->text, 'H', 'right text';
+is $dom->find('div > p > p')->[0], undef, 'no results';
+is $dom->at('div > p > img')->attr->{src}, 'foo.png', 'right attribute';
+is $dom->at('div > div')->text, 'X', 'right text';
+
+# Optional "dt" and "dd" tags
+$dom = DOM::Tiny->new(<<EOF);
+<dl>
+ <dt>A</dt>
+ <DD>B
+ <dt>C</dt>
+ <dd>D
+ <dt>E
+ <dd>F
+</dl>
+EOF
+is $dom->find('dl > dt')->[0]->text, 'A', 'right text';
+is $dom->find('dl > dd')->[0]->text, 'B', 'right text';
+is $dom->find('dl > dt')->[1]->text, 'C', 'right text';
+is $dom->find('dl > dd')->[1]->text, 'D', 'right text';
+is $dom->find('dl > dt')->[2]->text, 'E', 'right text';
+is $dom->find('dl > dd')->[2]->text, 'F', 'right text';
+
+# Optional "rp" and "rt" tags
+$dom = DOM::Tiny->new(<<EOF);
+<ruby>
+ <rp>A</rp>
+ <RT>B
+ <rp>C</rp>
+ <rt>D
+ <rp>E
+ <rt>F
+</ruby>
+EOF
+is $dom->find('ruby > rp')->[0]->text, 'A', 'right text';
+is $dom->find('ruby > rt')->[0]->text, 'B', 'right text';
+is $dom->find('ruby > rp')->[1]->text, 'C', 'right text';
+is $dom->find('ruby > rt')->[1]->text, 'D', 'right text';
+is $dom->find('ruby > rp')->[2]->text, 'E', 'right text';
+is $dom->find('ruby > rt')->[2]->text, 'F', 'right text';
+
+# Optional "optgroup" and "option" tags
+$dom = DOM::Tiny->new(<<EOF);
+<div>
+ <optgroup>A
+ <option id="foo">B
+ <option>C</option>
+ <option>D
+ <OPTGROUP>E
+ <option>F
+ <optgroup>G
+ <option>H
+</div>
+EOF
+is $dom->find('div > optgroup')->[0]->text, 'A', 'right text';
+is $dom->find('div > optgroup > #foo')->[0]->text, 'B', 'right text';
+is $dom->find('div > optgroup > option')->[1]->text, 'C', 'right text';
+is $dom->find('div > optgroup > option')->[2]->text, 'D', 'right text';
+is $dom->find('div > optgroup')->[1]->text, 'E', 'right text';
+is $dom->find('div > optgroup > option')->[3]->text, 'F', 'right text';
+is $dom->find('div > optgroup')->[2]->text, 'G', 'right text';
+is $dom->find('div > optgroup > option')->[4]->text, 'H', 'right text';
+
+# Optional "colgroup" tag
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <col id=morefail>
+ <col id=fail>
+ <colgroup>
+ <col id=foo>
+ <col class=foo>
+ <colgroup>
+ <col id=bar>
+</table>
+EOF
+is $dom->find('table > col')->[0]->attr->{id}, 'morefail', 'right attribute';
+is $dom->find('table > col')->[1]->attr->{id}, 'fail', 'right attribute';
+is $dom->find('table > colgroup > col')->[0]->attr->{id}, 'foo',
+ 'right attribute';
+is $dom->find('table > colgroup > col')->[1]->attr->{class}, 'foo',
+ 'right attribute';
+is $dom->find('table > colgroup > col')->[2]->attr->{id}, 'bar',
+ 'right attribute';
+
+# Optional "thead", "tbody", "tfoot", "tr", "th" and "td" tags
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <thead>
+ <tr>
+ <th>A</th>
+ <th>D
+ <tfoot>
+ <tr>
+ <td>C
+ <tbody>
+ <tr>
+ <td>B
+</table>
+EOF
+is $dom->at('table > thead > tr > th')->text, 'A', 'right text';
+is $dom->find('table > thead > tr > th')->[1]->text, 'D', 'right text';
+is $dom->at('table > tbody > tr > td')->text, 'B', 'right text';
+is $dom->at('table > tfoot > tr > td')->text, 'C', 'right text';
+
+# Optional "colgroup", "thead", "tbody", "tr", "th" and "td" tags
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <col id=morefail>
+ <col id=fail>
+ <colgroup>
+ <col id=foo />
+ <col class=foo>
+ <colgroup>
+ <col id=bar>
+ </colgroup>
+ <thead>
+ <tr>
+ <th>A</th>
+ <th>D
+ <tbody>
+ <tr>
+ <td>B
+ <tbody>
+ <tr>
+ <td>E
+</table>
+EOF
+is $dom->find('table > col')->[0]->attr->{id}, 'morefail', 'right attribute';
+is $dom->find('table > col')->[1]->attr->{id}, 'fail', 'right attribute';
+is $dom->find('table > colgroup > col')->[0]->attr->{id}, 'foo',
+ 'right attribute';
+is $dom->find('table > colgroup > col')->[1]->attr->{class}, 'foo',
+ 'right attribute';
+is $dom->find('table > colgroup > col')->[2]->attr->{id}, 'bar',
+ 'right attribute';
+is $dom->at('table > thead > tr > th')->text, 'A', 'right text';
+is $dom->find('table > thead > tr > th')->[1]->text, 'D', 'right text';
+is $dom->at('table > tbody > tr > td')->text, 'B', 'right text';
+is $dom->find('table > tbody > tr > td')->map('text')->join("\n"), "B\nE",
+ 'right text';
+
+# Optional "colgroup", "tbody", "tr", "th" and "td" tags
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <colgroup>
+ <col id=foo />
+ <col class=foo>
+ <colgroup>
+ <col id=bar>
+ </colgroup>
+ <tbody>
+ <tr>
+ <td>B
+</table>
+EOF
+is $dom->find('table > colgroup > col')->[0]->attr->{id}, 'foo',
+ 'right attribute';
+is $dom->find('table > colgroup > col')->[1]->attr->{class}, 'foo',
+ 'right attribute';
+is $dom->find('table > colgroup > col')->[2]->attr->{id}, 'bar',
+ 'right attribute';
+is $dom->at('table > tbody > tr > td')->text, 'B', 'right text';
+
+# Optional "tr" and "td" tags
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <tr>
+ <td>A
+ <td>B</td>
+ <tr>
+ <td>C
+ </tr>
+ <tr>
+ <td>D
+</table>
+EOF
+is $dom->find('table > tr > td')->[0]->text, 'A', 'right text';
+is $dom->find('table > tr > td')->[1]->text, 'B', 'right text';
+is $dom->find('table > tr > td')->[2]->text, 'C', 'right text';
+is $dom->find('table > tr > td')->[3]->text, 'D', 'right text';
+
+# Real world table
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head>
+ <title>Real World!</title>
+ <body>
+ <p>Just a test
+ <table class=RealWorld>
+ <thead>
+ <tr>
+ <th class=one>One
+ <th class=two>Two
+ <th class=three>Three
+ <th class=four>Four
+ <tbody>
+ <tr>
+ <td class=alpha>Alpha
+ <td class=beta>Beta
+ <td class=gamma><a href="#gamma">Gamma</a>
+ <td class=delta>Delta
+ <tr>
+ <td class=alpha>Alpha Two
+ <td class=beta>Beta Two
+ <td class=gamma><a href="#gamma-two">Gamma Two</a>
+ <td class=delta>Delta Two
+ </table>
+EOF
+is $dom->find('html > head > title')->[0]->text, 'Real World!', 'right text';
+is $dom->find('html > body > p')->[0]->text, 'Just a test', 'right text';
+is $dom->find('p')->[0]->text, 'Just a test', 'right text';
+is $dom->find('thead > tr > .three')->[0]->text, 'Three', 'right text';
+is $dom->find('thead > tr > .four')->[0]->text, 'Four', 'right text';
+is $dom->find('tbody > tr > .beta')->[0]->text, 'Beta', 'right text';
+is $dom->find('tbody > tr > .gamma')->[0]->text, '', 'no text';
+is $dom->find('tbody > tr > .gamma > a')->[0]->text, 'Gamma', 'right text';
+is $dom->find('tbody > tr > .alpha')->[1]->text, 'Alpha Two', 'right text';
+is $dom->find('tbody > tr > .gamma > a')->[1]->text, 'Gamma Two', 'right text';
+my @following
+ = $dom->find('tr > td:nth-child(1)')->map(following => ':nth-child(even)')
+ ->flatten->map('all_text')->each;
+is_deeply \@following, ['Beta', 'Delta', 'Beta Two', 'Delta Two'],
+ 'right results';
+
+# Real world list
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head>
+ <title>Real World!</title>
+ <body>
+ <ul>
+ <li>
+ Test
+ <br>
+ 123
+ <p>
+
+ <li>
+ Test
+ <br>
+ 321
+ <p>
+ <li>
+ Test
+ 3
+ 2
+ 1
+ <p>
+ </ul>
+EOF
+is $dom->find('html > head > title')->[0]->text, 'Real World!', 'right text';
+is $dom->find('body > ul > li')->[0]->text, 'Test 123', 'right text';
+is $dom->find('body > ul > li > p')->[0]->text, '', 'no text';
+is $dom->find('body > ul > li')->[1]->text, 'Test 321', 'right text';
+is $dom->find('body > ul > li > p')->[1]->text, '', 'no text';
+is $dom->find('body > ul > li')->[1]->all_text, 'Test 321', 'right text';
+is $dom->find('body > ul > li > p')->[1]->all_text, '', 'no text';
+is $dom->find('body > ul > li')->[2]->text, 'Test 3 2 1', 'right text';
+is $dom->find('body > ul > li > p')->[2]->text, '', 'no text';
+is $dom->find('body > ul > li')->[2]->all_text, 'Test 3 2 1', 'right text';
+is $dom->find('body > ul > li > p')->[2]->all_text, '', 'no text';
+
+# Advanced whitespace trimming (punctuation)
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head>
+ <title>Real World!</title>
+ <body>
+ <div>foo <strong>bar</strong>.</div>
+ <div>foo<strong>, bar</strong>baz<strong>; yada</strong>.</div>
+ <div>foo<strong>: bar</strong>baz<strong>? yada</strong>!</div>
+EOF
+is $dom->find('html > head > title')->[0]->text, 'Real World!', 'right text';
+is $dom->find('body > div')->[0]->all_text, 'foo bar.', 'right text';
+is $dom->find('body > div')->[1]->all_text, 'foo, bar baz; yada.', 'right text';
+is $dom->find('body > div')->[1]->text, 'foo baz.', 'right text';
+is $dom->find('body > div')->[2]->all_text, 'foo: bar baz? yada!', 'right text';
+is $dom->find('body > div')->[2]->text, 'foo baz!', 'right text';
+
+# Real world JavaScript and CSS
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head>
+ <style test=works>#style { foo: style('<test>'); }</style>
+ <script>
+ if (a < b) {
+ alert('<123>');
+ }
+ </script>
+ < sCriPt two="23" >if (b > c) { alert('&<ohoh>') }< / scRiPt >
+ <body>Foo!</body>
+EOF
+is $dom->find('html > body')->[0]->text, 'Foo!', 'right text';
+is $dom->find('html > head > style')->[0]->text,
+ "#style { foo: style('<test>'); }", 'right text';
+is $dom->find('html > head > script')->[0]->text,
+ "\n if (a < b) {\n alert('<123>');\n }\n ", 'right text';
+is $dom->find('html > head > script')->[1]->text,
+ "if (b > c) { alert('&<ohoh>') }", 'right text';
+
+# More real world JavaScript
+$dom = DOM::Tiny->new(<<EOF);
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Foo</title>
+ <script src="/js/one.js"></script>
+ <script src="/js/two.js"></script>
+ <script src="/js/three.js"></script>
+ </head>
+ <body>Bar</body>
+</html>
+EOF
+is $dom->at('title')->text, 'Foo', 'right text';
+is $dom->find('html > head > script')->[0]->attr('src'), '/js/one.js',
+ 'right attribute';
+is $dom->find('html > head > script')->[1]->attr('src'), '/js/two.js',
+ 'right attribute';
+is $dom->find('html > head > script')->[2]->attr('src'), '/js/three.js',
+ 'right attribute';
+is $dom->find('html > head > script')->[2]->text, '', 'no text';
+is $dom->at('html > body')->text, 'Bar', 'right text';
+
+# Even more real world JavaScript
+$dom = DOM::Tiny->new(<<EOF);
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Foo</title>
+ <script src="/js/one.js"></script>
+ <script src="/js/two.js"></script>
+ <script src="/js/three.js">
+ </head>
+ <body>Bar</body>
+</html>
+EOF
+is $dom->at('title')->text, 'Foo', 'right text';
+is $dom->find('html > head > script')->[0]->attr('src'), '/js/one.js',
+ 'right attribute';
+is $dom->find('html > head > script')->[1]->attr('src'), '/js/two.js',
+ 'right attribute';
+is $dom->find('html > head > script')->[2]->attr('src'), '/js/three.js',
+ 'right attribute';
+is $dom->find('html > head > script')->[2]->text, '', 'no text';
+is $dom->at('html > body')->text, 'Bar', 'right text';
+
+# Inline DTD
+$dom = DOM::Tiny->new(<<EOF);
+<?xml version="1.0"?>
+<!-- This is a Test! -->
+<!DOCTYPE root [
+ <!ELEMENT root (#PCDATA)>
+ <!ATTLIST root att CDATA #REQUIRED>
+]>
+<root att="test">
+ <![CDATA[<hello>world</hello>]]>
+</root>
+EOF
+ok $dom->xml, 'XML mode detected';
+is $dom->at('root')->attr('att'), 'test', 'right attribute';
+is $dom->tree->[5][1], ' root [
+ <!ELEMENT root (#PCDATA)>
+ <!ATTLIST root att CDATA #REQUIRED>
+]', 'right doctype';
+is $dom->at('root')->text, '<hello>world</hello>', 'right text';
+$dom = DOM::Tiny->new(<<EOF);
+<!doctype book
+SYSTEM "usr.dtd"
+[
+ <!ENTITY test "yeah">
+]>
+<foo />
+EOF
+is $dom->tree->[1][1], ' book
+SYSTEM "usr.dtd"
+[
+ <!ENTITY test "yeah">
+]', 'right doctype';
+ok !$dom->xml, 'XML mode not detected';
+is $dom->at('foo'), '<foo></foo>', 'right element';
+$dom = DOM::Tiny->new(<<EOF);
+<?xml version="1.0" encoding = 'utf-8'?>
+<!DOCTYPE foo [
+ <!ELEMENT foo ANY>
+ <!ATTLIST foo xml:lang CDATA #IMPLIED>
+ <!ENTITY % e SYSTEM "myentities.ent">
+ %myentities;
+] >
+<foo xml:lang="de">Check!</fOo>
+EOF
+ok $dom->xml, 'XML mode detected';
+is $dom->tree->[3][1], ' foo [
+ <!ELEMENT foo ANY>
+ <!ATTLIST foo xml:lang CDATA #IMPLIED>
+ <!ENTITY % e SYSTEM "myentities.ent">
+ %myentities;
+] ', 'right doctype';
+is $dom->at('foo')->attr->{'xml:lang'}, 'de', 'right attribute';
+is $dom->at('foo')->text, 'Check!', 'right text';
+$dom = DOM::Tiny->new(<<EOF);
+<!DOCTYPE TESTSUITE PUBLIC "my.dtd" 'mhhh' [
+ <!ELEMENT foo ANY>
+ <!ATTLIST foo bar ENTITY 'true'>
+ <!ENTITY system_entities SYSTEM 'systems.xml'>
+ <!ENTITY leertaste ' '>
+ <!-- This is a comment -->
+ <!NOTATION hmmm SYSTEM "hmmm">
+] >
+<?check for-nothing?>
+<foo bar='false'>&leertaste;!!!</foo>
+EOF
+is $dom->tree->[1][1], ' TESTSUITE PUBLIC "my.dtd" \'mhhh\' [
+ <!ELEMENT foo ANY>
+ <!ATTLIST foo bar ENTITY \'true\'>
+ <!ENTITY system_entities SYSTEM \'systems.xml\'>
+ <!ENTITY leertaste \' \'>
+ <!-- This is a comment -->
+ <!NOTATION hmmm SYSTEM "hmmm">
+] ', 'right doctype';
+is $dom->at('foo')->attr('bar'), 'false', 'right attribute';
+
+# Broken "font" block and useless end tags
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head><title>Test</title></head>
+ <body>
+ <table>
+ <tr><td><font>test</td></font></tr>
+ </tr>
+ </table>
+ </body>
+</html>
+EOF
+is $dom->at('html > head > title')->text, 'Test', 'right text';
+is $dom->at('html body table tr td > font')->text, 'test', 'right text';
+
+# Different broken "font" block
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head><title>Test</title></head>
+ <body>
+ <font>
+ <table>
+ <tr>
+ <td>test1<br></td></font>
+ <td>test2<br>
+ </table>
+ </body>
+</html>
+EOF
+is $dom->at('html > head > title')->text, 'Test', 'right text';
+is $dom->find('html > body > font > table > tr > td')->[0]->text, 'test1',
+ 'right text';
+is $dom->find('html > body > font > table > tr > td')->[1]->text, 'test2',
+ 'right text';
+
+# Broken "font" and "div" blocks
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head><title>Test</title></head>
+ <body>
+ <font>
+ <div>test1<br>
+ <div>test2<br></font>
+ </div>
+ </body>
+</html>
+EOF
+is $dom->at('html head title')->text, 'Test', 'right text';
+is $dom->at('html body font > div')->text, 'test1', 'right text';
+is $dom->at('html body font > div > div')->text, 'test2', 'right text';
+
+# Broken "div" blocks
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head><title>Test</title></head>
+ <body>
+ <div>
+ <table>
+ <tr><td><div>test</td></div></tr>
+ </div>
+ </table>
+ </body>
+</html>
+EOF
+is $dom->at('html head title')->text, 'Test', 'right text';
+is $dom->at('html body div table tr td > div')->text, 'test', 'right text';
+
+# And another broken "font" block
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head><title>Test</title></head>
+ <body>
+ <table>
+ <tr>
+ <td><font><br>te<br>st<br>1</td></font>
+ <td>x1<td><img>tes<br>t2</td>
+ <td>x2<td><font>t<br>est3</font></td>
+ </tr>
+ </table>
+ </body>
+</html>
+EOF
+is $dom->at('html > head > title')->text, 'Test', 'right text';
+is $dom->find('html body table tr > td > font')->[0]->text, 'te st 1',
+ 'right text';
+is $dom->find('html body table tr > td')->[1]->text, 'x1', 'right text';
+is $dom->find('html body table tr > td')->[2]->text, 'tes t2', 'right text';
+is $dom->find('html body table tr > td')->[3]->text, 'x2', 'right text';
+is $dom->find('html body table tr > td')->[5], undef, 'no result';
+is $dom->find('html body table tr > td')->size, 5, 'right number of elements';
+is $dom->find('html body table tr > td > font')->[1]->text, 't est3',
+ 'right text';
+is $dom->find('html body table tr > td > font')->[2], undef, 'no result';
+is $dom->find('html body table tr > td > font')->size, 2,
+ 'right number of elements';
+is $dom, <<EOF, 'right result';
+<html>
+ <head><title>Test</title></head>
+ <body>
+ <table>
+ <tr>
+ <td><font><br>te<br>st<br>1</font></td>
+ <td>x1</td><td><img>tes<br>t2</td>
+ <td>x2</td><td><font>t<br>est3</font></td>
+ </tr>
+ </table>
+ </body>
+</html>
+EOF
+
+# A collection of wonderful screwups
+$dom = DOM::Tiny->new(<<'EOF');
+<!DOCTYPE html>
+<html lang="en">
+ <head><title>Wonderful Screwups</title></head>
+ <body id="screw-up">
+ <div>
+ <div class="ewww">
+ <a href="/test" target='_blank'><img src="/test.png"></a>
+ <a href='/real bad' screwup: http://localhost/bad' target='_blank'>
+ <img src="/test2.png">
+ </div>
+ </mt:If>
+ </div>
+ <b>>la<>la<<>>la<</b>
+ </body>
+</html>
+EOF
+is $dom->at('#screw-up > b')->text, '>la<>la<<>>la<', 'right text';
+is $dom->at('#screw-up .ewww > a > img')->attr('src'), '/test.png',
+ 'right attribute';
+is $dom->find('#screw-up .ewww > a > img')->[1]->attr('src'), '/test2.png',
+ 'right attribute';
+is $dom->find('#screw-up .ewww > a > img')->[2], undef, 'no result';
+is $dom->find('#screw-up .ewww > a > img')->size, 2, 'right number of elements';
+
+# Broken "br" tag
+$dom = DOM::Tiny->new('<br< abc abc abc abc abc abc abc abc<p>Test</p>');
+is $dom->at('p')->text, 'Test', 'right text';
+
+# Modifying an XML document
+$dom = DOM::Tiny->new(<<'EOF');
+<?xml version='1.0' encoding='UTF-8'?>
+<XMLTest />
+EOF
+ok $dom->xml, 'XML mode detected';
+$dom->at('XMLTest')->content('<Element />');
+my $element = $dom->at('Element');
+is $element->tag, 'Element', 'right tag';
+ok $element->xml, 'XML mode active';
+$element = $dom->at('XMLTest')->children->[0];
+is $element->tag, 'Element', 'right child';
+is $element->parent->tag, 'XMLTest', 'right parent';
+ok $element->root->xml, 'XML mode active';
+$dom->replace('<XMLTest2 /><XMLTest3 just="works" />');
+ok $dom->xml, 'XML mode active';
+$dom->at('XMLTest2')->{foo} = undef;
+is $dom, '<XMLTest2 foo="foo" /><XMLTest3 just="works" />', 'right result';
+
+# Ensure HTML semantics
+ok !DOM::Tiny->new->xml(undef)->parse('<?xml version="1.0"?>')->xml,
+ 'XML mode not detected';
+$dom
+ = DOM::Tiny->new->xml(0)->parse('<?xml version="1.0"?><br><div>Test</div>');
+is $dom->at('div:root')->text, 'Test', 'right text';
+
+# Ensure XML semantics
+ok !!DOM::Tiny->new->xml(1)->parse('<foo />')->xml, 'XML mode active';
+$dom = DOM::Tiny->new(<<'EOF');
+<?xml version='1.0' encoding='UTF-8'?>
+<script>
+ <table>
+ <td>
+ <tr><thead>foo<thead></tr>
+ </td>
+ <td>
+ <tr><thead>bar<thead></tr>
+ </td>
+ </table>
+</script>
+EOF
+is $dom->find('table > td > tr > thead')->[0]->text, 'foo', 'right text';
+is $dom->find('script > table > td > tr > thead')->[1]->text, 'bar',
+ 'right text';
+is $dom->find('table > td > tr > thead')->[2], undef, 'no result';
+is $dom->find('table > td > tr > thead')->size, 2, 'right number of elements';
+
+# Ensure XML semantics again
+$dom = DOM::Tiny->new->xml(1)->parse(<<'EOF');
+<table>
+ <td>
+ <tr><thead>foo<thead></tr>
+ </td>
+ <td>
+ <tr><thead>bar<thead></tr>
+ </td>
+</table>
+EOF
+is $dom->find('table > td > tr > thead')->[0]->text, 'foo', 'right text';
+is $dom->find('table > td > tr > thead')->[1]->text, 'bar', 'right text';
+is $dom->find('table > td > tr > thead')->[2], undef, 'no result';
+is $dom->find('table > td > tr > thead')->size, 2, 'right number of elements';
+
+# Nested tables
+$dom = DOM::Tiny->new(<<'EOF');
+<table id="foo">
+ <tr>
+ <td>
+ <table id="bar">
+ <tr>
+ <td>baz</td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+</table>
+EOF
+is $dom->find('#foo > tr > td > #bar > tr >td')->[0]->text, 'baz', 'right text';
+is $dom->find('table > tr > td > table > tr >td')->[0]->text, 'baz',
+ 'right text';
+
+# Nested find
+$dom->parse(<<EOF);
+<c>
+ <a>foo</a>
+ <b>
+ <a>bar</a>
+ <c>
+ <a>baz</a>
+ <d>
+ <a>yada</a>
+ </d>
+ </c>
+ </b>
+</c>
+EOF
+my @results;
+$dom->find('b')->each(
+ sub {
+ $_->find('a')->each(sub { push @results, $_->text });
+ }
+);
+is_deeply \@results, [qw(bar baz yada)], 'right results';
+@results = ();
+$dom->find('a')->each(sub { push @results, $_->text });
+is_deeply \@results, [qw(foo bar baz yada)], 'right results';
+@results = ();
+$dom->find('b')->each(
+ sub {
+ $_->find('c a')->each(sub { push @results, $_->text });
+ }
+);
+is_deeply \@results, [qw(baz yada)], 'right results';
+is $dom->at('b')->at('a')->text, 'bar', 'right text';
+is $dom->at('c > b > a')->text, 'bar', 'right text';
+is $dom->at('b')->at('c > b > a'), undef, 'no result';
+
+# Direct hash access to attributes in XML mode
+$dom = DOM::Tiny->new->xml(1)->parse(<<EOF);
+<a id="one">
+ <B class="two" test>
+ foo
+ <c id="three">bar</c>
+ <c ID="four">baz</c>
+ </B>
+</a>
+EOF
+ok $dom->xml, 'XML mode active';
+is $dom->at('a')->{id}, 'one', 'right attribute';
+is_deeply [sort keys %{$dom->at('a')}], ['id'], 'right attributes';
+is $dom->at('a')->at('B')->text, 'foo', 'right text';
+is $dom->at('B')->{class}, 'two', 'right attribute';
+is_deeply [sort keys %{$dom->at('a B')}], [qw(class test)], 'right attributes';
+is $dom->find('a B c')->[0]->text, 'bar', 'right text';
+is $dom->find('a B c')->[0]{id}, 'three', 'right attribute';
+is_deeply [sort keys %{$dom->find('a B c')->[0]}], ['id'], 'right attributes';
+is $dom->find('a B c')->[1]->text, 'baz', 'right text';
+is $dom->find('a B c')->[1]{ID}, 'four', 'right attribute';
+is_deeply [sort keys %{$dom->find('a B c')->[1]}], ['ID'], 'right attributes';
+is $dom->find('a B c')->[2], undef, 'no result';
+is $dom->find('a B c')->size, 2, 'right number of elements';
+@results = ();
+$dom->find('a B c')->each(sub { push @results, $_->text });
+is_deeply \@results, [qw(bar baz)], 'right results';
+is $dom->find('a B c')->join("\n"),
+ qq{<c id="three">bar</c>\n<c ID="four">baz</c>}, 'right result';
+is_deeply [keys %$dom], [], 'root has no attributes';
+is $dom->find('#nothing')->join, '', 'no result';
+
+# Direct hash access to attributes in HTML mode
+$dom = DOM::Tiny->new(<<EOF);
+<a id="one">
+ <B class="two" test>
+ foo
+ <c id="three">bar</c>
+ <c ID="four">baz</c>
+ </B>
+</a>
+EOF
+ok !$dom->xml, 'XML mode not active';
+is $dom->at('a')->{id}, 'one', 'right attribute';
+is_deeply [sort keys %{$dom->at('a')}], ['id'], 'right attributes';
+is $dom->at('a')->at('b')->text, 'foo', 'right text';
+is $dom->at('b')->{class}, 'two', 'right attribute';
+is_deeply [sort keys %{$dom->at('a b')}], [qw(class test)], 'right attributes';
+is $dom->find('a b c')->[0]->text, 'bar', 'right text';
+is $dom->find('a b c')->[0]{id}, 'three', 'right attribute';
+is_deeply [sort keys %{$dom->find('a b c')->[0]}], ['id'], 'right attributes';
+is $dom->find('a b c')->[1]->text, 'baz', 'right text';
+is $dom->find('a b c')->[1]{id}, 'four', 'right attribute';
+is_deeply [sort keys %{$dom->find('a b c')->[1]}], ['id'], 'right attributes';
+is $dom->find('a b c')->[2], undef, 'no result';
+is $dom->find('a b c')->size, 2, 'right number of elements';
+@results = ();
+$dom->find('a b c')->each(sub { push @results, $_->text });
+is_deeply \@results, [qw(bar baz)], 'right results';
+is $dom->find('a b c')->join("\n"),
+ qq{<c id="three">bar</c>\n<c id="four">baz</c>}, 'right result';
+is_deeply [keys %$dom], [], 'root has no attributes';
+is $dom->find('#nothing')->join, '', 'no result';
+
+# Append and prepend content
+$dom = DOM::Tiny->new('<a><b>Test<c /></b></a>');
+$dom->at('b')->append_content('<d />');
+is $dom->children->[0]->tag, 'a', 'right tag';
+is $dom->all_text, 'Test', 'right text';
+is $dom->at('c')->parent->tag, 'b', 'right tag';
+is $dom->at('d')->parent->tag, 'b', 'right tag';
+$dom->at('b')->prepend_content('<e>DOM</e>');
+is $dom->at('e')->parent->tag, 'b', 'right tag';
+is $dom->all_text, 'DOM Test', 'right text';
+
+# Wrap elements
+$dom = DOM::Tiny->new('<a>Test</a>');
+is $dom->wrap('<b></b>')->type, 'root', 'right type';
+is "$dom", '<b><a>Test</a></b>', 'right result';
+is $dom->at('b')->strip->at('a')->wrap('A')->tag, 'a', 'right tag';
+is "$dom", '<a>Test</a>', 'right result';
+is $dom->at('a')->wrap('<b></b>')->tag, 'a', 'right tag';
+is "$dom", '<b><a>Test</a></b>', 'right result';
+is $dom->at('a')->wrap('C<c><d>D</d><e>E</e></c>F')->parent->tag, 'd',
+ 'right tag';
+is "$dom", '<b>C<c><d>D<a>Test</a></d><e>E</e></c>F</b>', 'right result';
+
+# Wrap content
+$dom = DOM::Tiny->new('<a>Test</a>');
+is $dom->at('a')->wrap_content('A')->tag, 'a', 'right tag';
+is "$dom", '<a>Test</a>', 'right result';
+is $dom->wrap_content('<b></b>')->type, 'root', 'right type';
+is "$dom", '<b><a>Test</a></b>', 'right result';
+is $dom->at('b')->strip->at('a')->tag('e:a')->wrap_content('1<b c="d"></b>')
+ ->tag, 'e:a', 'right tag';
+is "$dom", '<e:a>1<b c="d">Test</b></e:a>', 'right result';
+is $dom->at('a')->wrap_content('C<c><d>D</d><e>E</e></c>F')->parent->type,
+ 'root', 'right type';
+is "$dom", '<e:a>C<c><d>D1<b c="d">Test</b></d><e>E</e></c>F</e:a>',
+ 'right result';
+
+# Broken "div" in "td"
+$dom = DOM::Tiny->new(<<EOF);
+<table>
+ <tr>
+ <td><div id="A"></td>
+ <td><div id="B"></td>
+ </tr>
+</table>
+EOF
+is $dom->find('table tr td')->[0]->at('div')->{id}, 'A', 'right attribute';
+is $dom->find('table tr td')->[1]->at('div')->{id}, 'B', 'right attribute';
+is $dom->find('table tr td')->[2], undef, 'no result';
+is $dom->find('table tr td')->size, 2, 'right number of elements';
+is "$dom", <<EOF, 'right result';
+<table>
+ <tr>
+ <td><div id="A"></div></td>
+ <td><div id="B"></div></td>
+ </tr>
+</table>
+EOF
+
+# Preformatted text
+$dom = DOM::Tiny->new(<<EOF);
+<div>
+ looks
+ <pre><code>like
+ it
+ really</code>
+ </pre>
+ works
+</div>
+EOF
+is $dom->text, '', 'no text';
+is $dom->text(0), "\n", 'right text';
+is $dom->all_text, "looks like\n it\n really\n works", 'right text';
+is $dom->all_text(0), "\n looks\n like\n it\n really\n \n works\n\n",
+ 'right text';
+is $dom->at('div')->text, 'looks works', 'right text';
+is $dom->at('div')->text(0), "\n looks\n \n works\n", 'right text';
+is $dom->at('div')->all_text, "looks like\n it\n really\n works",
+ 'right text';
+is $dom->at('div')->all_text(0),
+ "\n looks\n like\n it\n really\n \n works\n", 'right text';
+is $dom->at('div pre')->text, "\n ", 'right text';
+is $dom->at('div pre')->text(0), "\n ", 'right text';
+is $dom->at('div pre')->all_text, "like\n it\n really\n ", 'right text';
+is $dom->at('div pre')->all_text(0), "like\n it\n really\n ", 'right text';
+is $dom->at('div pre code')->text, "like\n it\n really", 'right text';
+is $dom->at('div pre code')->text(0), "like\n it\n really", 'right text';
+is $dom->at('div pre code')->all_text, "like\n it\n really", 'right text';
+is $dom->at('div pre code')->all_text(0), "like\n it\n really",
+ 'right text';
+
+# Form values
+$dom = DOM::Tiny->new(<<EOF);
+<form action="/foo">
+ <p>Test</p>
+ <input type="text" name="a" value="A" />
+ <input type="checkbox" checked name="b" value="B">
+ <input type="radio" checked name="c" value="C">
+ <select multiple name="f">
+ <option value="F">G</option>
+ <optgroup>
+ <option>H</option>
+ <option selected>I</option>
+ </optgroup>
+ <option value="J" selected>K</option>
+ </select>
+ <select name="n"><option>N</option></select>
+ <select multiple name="q"><option>Q</option></select>
+ <select name="d">
+ <option selected>R</option>
+ <option selected>D</option>
+ </select>
+ <textarea name="m">M</textarea>
+ <button name="o" value="O">No!</button>
+ <input type="submit" name="p" value="P" />
+</form>
+EOF
+is $dom->at('p')->val, undef, 'no value';
+is $dom->at('input')->val, 'A', 'right value';
+is $dom->at('input:checked')->val, 'B', 'right value';
+is $dom->at('input:checked[type=radio]')->val, 'C', 'right value';
+is_deeply $dom->at('select')->val, ['I', 'J'], 'right values';
+is $dom->at('select option')->val, 'F', 'right value';
+is $dom->at('select optgroup option:not([selected])')->val, 'H', 'right value';
+is $dom->find('select')->[1]->at('option')->val, 'N', 'right value';
+is $dom->find('select')->[1]->val, undef, 'no value';
+is_deeply $dom->find('select')->[2]->val, undef, 'no value';
+is $dom->find('select')->[2]->at('option')->val, 'Q', 'right value';
+is_deeply $dom->find('select')->last->val, 'D', 'right value';
+is_deeply $dom->find('select')->last->at('option')->val, 'R', 'right value';
+is $dom->at('textarea')->val, 'M', 'right value';
+is $dom->at('button')->val, 'O', 'right value';
+is $dom->find('form input')->last->val, 'P', 'right value';
+
+# PoCo example with whitespace sensitive text
+$dom = DOM::Tiny->new(<<EOF);
+<?xml version="1.0" encoding="UTF-8"?>
+<response>
+ <entry>
+ <id>1286823</id>
+ <displayName>Homer Simpson</displayName>
+ <addresses>
+ <type>home</type>
+ <formatted><![CDATA[742 Evergreen Terrace
+Springfield, VT 12345 USA]]></formatted>
+ </addresses>
+ </entry>
+ <entry>
+ <id>1286822</id>
+ <displayName>Marge Simpson</displayName>
+ <addresses>
+ <type>home</type>
+ <formatted>742 Evergreen Terrace
+Springfield, VT 12345 USA</formatted>
+ </addresses>
+ </entry>
+</response>
+EOF
+is $dom->find('entry')->[0]->at('displayName')->text, 'Homer Simpson',
+ 'right text';
+is $dom->find('entry')->[0]->at('id')->text, '1286823', 'right text';
+is $dom->find('entry')->[0]->at('addresses')->children('type')->[0]->text,
+ 'home', 'right text';
+is $dom->find('entry')->[0]->at('addresses formatted')->text,
+ "742 Evergreen Terrace\nSpringfield, VT 12345 USA", 'right text';
+is $dom->find('entry')->[0]->at('addresses formatted')->text(0),
+ "742 Evergreen Terrace\nSpringfield, VT 12345 USA", 'right text';
+is $dom->find('entry')->[1]->at('displayName')->text, 'Marge Simpson',
+ 'right text';
+is $dom->find('entry')->[1]->at('id')->text, '1286822', 'right text';
+is $dom->find('entry')->[1]->at('addresses')->children('type')->[0]->text,
+ 'home', 'right text';
+is $dom->find('entry')->[1]->at('addresses formatted')->text,
+ '742 Evergreen Terrace Springfield, VT 12345 USA', 'right text';
+is $dom->find('entry')->[1]->at('addresses formatted')->text(0),
+ "742 Evergreen Terrace\nSpringfield, VT 12345 USA", 'right text';
+is $dom->find('entry')->[2], undef, 'no result';
+is $dom->find('entry')->size, 2, 'right number of elements';
+
+# Find attribute with hyphen in name and value
+$dom = DOM::Tiny->new(<<EOF);
+<html>
+ <head><meta http-equiv="content-type" content="text/html"></head>
+</html>
+EOF
+is $dom->find('[http-equiv]')->[0]{content}, 'text/html', 'right attribute';
+is $dom->find('[http-equiv]')->[1], undef, 'no result';
+is $dom->find('[http-equiv="content-type"]')->[0]{content}, 'text/html',
+ 'right attribute';
+is $dom->find('[http-equiv="content-type"]')->[1], undef, 'no result';
+is $dom->find('[http-equiv^="content-"]')->[0]{content}, 'text/html',
+ 'right attribute';
+is $dom->find('[http-equiv^="content-"]')->[1], undef, 'no result';
+is $dom->find('head > [http-equiv$="-type"]')->[0]{content}, 'text/html',
+ 'right attribute';
+is $dom->find('head > [http-equiv$="-type"]')->[1], undef, 'no result';
+
+# Find "0" attribute value
+$dom = DOM::Tiny->new(<<EOF);
+<a accesskey="0">Zero</a>
+<a accesskey="1">O&gTn>e</a>
+EOF
+is $dom->find('a[accesskey]')->[0]->text, 'Zero', 'right text';
+is $dom->find('a[accesskey]')->[1]->text, 'O&gTn>e', 'right text';
+is $dom->find('a[accesskey]')->[2], undef, 'no result';
+is $dom->find('a[accesskey=0]')->[0]->text, 'Zero', 'right text';
+is $dom->find('a[accesskey=0]')->[1], undef, 'no result';
+is $dom->find('a[accesskey^=0]')->[0]->text, 'Zero', 'right text';
+is $dom->find('a[accesskey^=0]')->[1], undef, 'no result';
+is $dom->find('a[accesskey$=0]')->[0]->text, 'Zero', 'right text';
+is $dom->find('a[accesskey$=0]')->[1], undef, 'no result';
+is $dom->find('a[accesskey~=0]')->[0]->text, 'Zero', 'right text';
+is $dom->find('a[accesskey~=0]')->[1], undef, 'no result';
+is $dom->find('a[accesskey*=0]')->[0]->text, 'Zero', 'right text';
+is $dom->find('a[accesskey*=0]')->[1], undef, 'no result';
+is $dom->find('a[accesskey=1]')->[0]->text, 'O&gTn>e', 'right text';
+is $dom->find('a[accesskey=1]')->[1], undef, 'no result';
+is $dom->find('a[accesskey^=1]')->[0]->text, 'O&gTn>e', 'right text';
+is $dom->find('a[accesskey^=1]')->[1], undef, 'no result';
+is $dom->find('a[accesskey$=1]')->[0]->text, 'O&gTn>e', 'right text';
+is $dom->find('a[accesskey$=1]')->[1], undef, 'no result';
+is $dom->find('a[accesskey~=1]')->[0]->text, 'O&gTn>e', 'right text';
+is $dom->find('a[accesskey~=1]')->[1], undef, 'no result';
+is $dom->find('a[accesskey*=1]')->[0]->text, 'O&gTn>e', 'right text';
+is $dom->find('a[accesskey*=1]')->[1], undef, 'no result';
+is $dom->at('a[accesskey*="."]'), undef, 'no result';
+
+# Empty attribute value
+$dom = DOM::Tiny->new(<<EOF);
+<foo bar=>
+ test
+</foo>
+<bar>after</bar>
+EOF
+is $dom->tree->[0], 'root', 'right type';
+is $dom->tree->[1][0], 'tag', 'right type';
+is $dom->tree->[1][1], 'foo', 'right tag';
+is_deeply $dom->tree->[1][2], {bar => ''}, 'right attributes';
+is $dom->tree->[1][4][0], 'text', 'right type';
+is $dom->tree->[1][4][1], "\n test\n", 'right text';
+is $dom->tree->[3][0], 'tag', 'right type';
+is $dom->tree->[3][1], 'bar', 'right tag';
+is $dom->tree->[3][4][0], 'text', 'right type';
+is $dom->tree->[3][4][1], 'after', 'right text';
+is "$dom", <<EOF, 'right result';
+<foo bar="">
+ test
+</foo>
+<bar>after</bar>
+EOF
+
+# Case-insensitive attribute values
+$dom = DOM::Tiny->new(<<EOF);
+<p class="foo">A</p>
+<p class="foo bAr">B</p>
+<p class="FOO">C</p>
+EOF
+is $dom->find('.foo')->map('text')->join(','), 'A,B', 'right result';
+is $dom->find('.FOO')->map('text')->join(','), 'C', 'right result';
+is $dom->find('[class=foo]')->map('text')->join(','), 'A', 'right result';
+is $dom->find('[class=foo i]')->map('text')->join(','), 'A,C', 'right result';
+is $dom->find('[class="foo" i]')->map('text')->join(','), 'A,C', 'right result';
+is $dom->find('[class="foo bar"]')->size, 0, 'no results';
+is $dom->find('[class="foo bar" i]')->map('text')->join(','), 'B',
+ 'right result';
+is $dom->find('[class~=foo]')->map('text')->join(','), 'A,B', 'right result';
+is $dom->find('[class~=foo i]')->map('text')->join(','), 'A,B,C',
+ 'right result';
+is $dom->find('[class*=f]')->map('text')->join(','), 'A,B', 'right result';
+is $dom->find('[class*=f i]')->map('text')->join(','), 'A,B,C', 'right result';
+is $dom->find('[class^=F]')->map('text')->join(','), 'C', 'right result';
+is $dom->find('[class^=F i]')->map('text')->join(','), 'A,B,C', 'right result';
+is $dom->find('[class$=O]')->map('text')->join(','), 'C', 'right result';
+is $dom->find('[class$=O i]')->map('text')->join(','), 'A,C', 'right result';
+
+# Nested description lists
+$dom = DOM::Tiny->new(<<EOF);
+<dl>
+ <dt>A</dt>
+ <DD>
+ <dl>
+ <dt>B
+ <dd>C
+ </dl>
+ </dd>
+</dl>
+EOF
+is $dom->find('dl > dd > dl > dt')->[0]->text, 'B', 'right text';
+is $dom->find('dl > dd > dl > dd')->[0]->text, 'C', 'right text';
+is $dom->find('dl > dt')->[0]->text, 'A', 'right text';
+
+# Nested lists
+$dom = DOM::Tiny->new(<<EOF);
+<div>
+ <ul>
+ <li>
+ A
+ <ul>
+ <li>B</li>
+ C
+ </ul>
+ </li>
+ </ul>
+</div>
+EOF
+is $dom->find('div > ul > li')->[0]->text, 'A', 'right text';
+is $dom->find('div > ul > li')->[1], undef, 'no result';
+is $dom->find('div > ul li')->[0]->text, 'A', 'right text';
+is $dom->find('div > ul li')->[1]->text, 'B', 'right text';
+is $dom->find('div > ul li')->[2], undef, 'no result';
+is $dom->find('div > ul ul')->[0]->text, 'C', 'right text';
+is $dom->find('div > ul ul')->[1], undef, 'no result';
+
+# Unusual order
+$dom
+ = DOM::Tiny->new('<a href="http://example.com" id="foo" class="bar">Ok!</a>');
+is $dom->at('a:not([href$=foo])[href^=h]')->text, 'Ok!', 'right text';
+is $dom->at('a:not([href$=example.com])[href^=h]'), undef, 'no result';
+is $dom->at('a[href^=h]#foo.bar')->text, 'Ok!', 'right text';
+is $dom->at('a[href^=h]#foo.baz'), undef, 'no result';
+is $dom->at('a[href^=h]#foo:not(b)')->text, 'Ok!', 'right text';
+is $dom->at('a[href^=h]#foo:not(a)'), undef, 'no result';
+is $dom->at('[href^=h].bar:not(b)[href$=m]#foo')->text, 'Ok!', 'right text';
+is $dom->at('[href^=h].bar:not(b)[href$=m]#bar'), undef, 'no result';
+is $dom->at(':not(b)#foo#foo')->text, 'Ok!', 'right text';
+is $dom->at(':not(b)#foo#bar'), undef, 'no result';
+is $dom->at(':not([href^=h]#foo#bar)')->text, 'Ok!', 'right text';
+is $dom->at(':not([href^=h]#foo#foo)'), undef, 'no result';
+
+# Slash between attributes
+$dom = DOM::Tiny->new('<input /type=checkbox / value="/a/" checked/><br/>');
+is_deeply $dom->at('input')->attr,
+ {type => 'checkbox', value => '/a/', checked => undef}, 'right attributes';
+is "$dom", '<input checked type="checkbox" value="/a/"><br>', 'right result';
+
+# Dot and hash in class and id attributes
+$dom = DOM::Tiny->new('<p class="a#b.c">A</p><p id="a#b.c">B</p>');
+is $dom->at('p.a\#b\.c')->text, 'A', 'right text';
+is $dom->at(':not(p.a\#b\.c)')->text, 'B', 'right text';
+is $dom->at('p#a\#b\.c')->text, 'B', 'right text';
+is $dom->at(':not(p#a\#b\.c)')->text, 'A', 'right text';
+
+# Extra whitespace
+$dom = DOM::Tiny->new('< span>a< /span><b >b</b><span >c</ span>');
+is $dom->at('span')->text, 'a', 'right text';
+is $dom->at('span + b')->text, 'b', 'right text';
+is $dom->at('b + span')->text, 'c', 'right text';
+is "$dom", '<span>a</span><b>b</b><span>c</span>', 'right result';
+
+# Selectors with leading and trailing whitespace
+$dom = DOM::Tiny->new('<div id=foo><b>works</b></div>');
+is $dom->at(' div b ')->text, 'works', 'right text';
+is $dom->at(' :not( #foo ) ')->text, 'works', 'right text';
+
+# "0"
+$dom = DOM::Tiny->new('0');
+is "$dom", '0', 'right result';
+$dom->append_content('☃');
+is "$dom", '0☃', 'right result';
+is $dom->parse('<!DOCTYPE 0>'), '<!DOCTYPE 0>', 'successful roundtrip';
+is $dom->parse('<!--0-->'), '<!--0-->', 'successful roundtrip';
+is $dom->parse('<![CDATA[0]]>'), '<![CDATA[0]]>', 'successful roundtrip';
+is $dom->parse('<?0?>'), '<?0?>', 'successful roundtrip';
+
+# Not self-closing
+$dom = DOM::Tiny->new('<div />< div ><pre />test</div >123');
+is $dom->at('div > div > pre')->text, 'test', 'right text';
+is "$dom", '<div><div><pre>test</pre></div>123</div>', 'right result';
+$dom = DOM::Tiny->new('<p /><svg><circle /><circle /></svg>');
+is $dom->find('p > svg > circle')->size, 2, 'two circles';
+is "$dom", '<p><svg><circle></circle><circle></circle></svg></p>',
+ 'right result';
+
+# "image"
+$dom = DOM::Tiny->new('<image src="foo.png">test');
+is $dom->at('img')->{src}, 'foo.png', 'right attribute';
+is "$dom", '<img src="foo.png">test', 'right result';
+
+# "title"
+$dom = DOM::Tiny->new('<title> <p>test<</title>');
+is $dom->at('title')->text, ' <p>test<', 'right text';
+is "$dom", '<title> <p>test<</title>', 'right result';
+
+# "textarea"
+$dom = DOM::Tiny->new('<textarea id="a"> <p>test<</textarea>');
+is $dom->at('textarea#a')->text, ' <p>test<', 'right text';
+is "$dom", '<textarea id="a"> <p>test<</textarea>', 'right result';
+
+# Comments
+$dom = DOM::Tiny->new(<<EOF);
+<!-- HTML5 -->
+<!-- bad idea -- HTML5 -->
+<!-- HTML4 -- >
+<!-- bad idea -- HTML4 -- >
+EOF
+is $dom->tree->[1][1], ' HTML5 ', 'right comment';
+is $dom->tree->[3][1], ' bad idea -- HTML5 ', 'right comment';
+is $dom->tree->[5][1], ' HTML4 ', 'right comment';
+is $dom->tree->[7][1], ' bad idea -- HTML4 ', 'right comment';
+
+# Huge number of attributes
+$dom = DOM::Tiny->new('<div ' . ('a=b ' x 32768) . '>Test</div>');
+is $dom->at('div[a=b]')->text, 'Test', 'right text';
+
+# Huge number of nested tags
+my $huge = ('<a>' x 100) . 'works' . ('</a>' x 100);
+$dom = DOM::Tiny->new($huge);
+is $dom->all_text, 'works', 'right text';
+is "$dom", $huge, 'right result';
+
+done_testing();