make DOM::Tiny::HTML and DOM::Tiny::CSS private also
[catagits/DOM-Tiny.git] / lib / DOM / Tiny.pm
CommitLineData
a292be34 1package DOM::Tiny;
2
3use strict;
4use warnings;
5
d6512b50 6use overload
7 '@{}' => sub { shift->child_nodes },
8 '%{}' => sub { shift->attr },
9 bool => sub {1},
10 '""' => sub { shift->to_string },
11 fallback => 1;
12
13use Carp 'croak';
78ba4051 14use DOM::Tiny::_Collection;
9a5f1e3f 15use DOM::Tiny::_CSS;
16use DOM::Tiny::_HTML;
d6512b50 17use Scalar::Util qw(blessed weaken);
18
a292be34 19our $VERSION = '0.001';
20
3793c28f 21sub new {
22 my $class = shift;
9a5f1e3f 23 my $self = bless \DOM::Tiny::_HTML->new, ref $class || $class;
3793c28f 24 return @_ ? $self->parse(@_) : $self;
25}
26
78ba4051 27sub TO_JSON { shift->_delegate('render') }
28
d6512b50 29sub all_text { shift->_all_text(1, @_) }
30
31sub ancestors { _select($_[0]->_collect($_[0]->_ancestors), $_[1]) }
32
33sub append { shift->_add(1, @_) }
34sub append_content { shift->_content(1, 0, @_) }
35
36sub at {
37 my $self = shift;
38 return undef unless my $result = $self->_css->select_one(@_);
39 return $self->_build($result, $self->xml);
40}
41
42sub attr {
43 my $self = shift;
44
45 # Hash
46 my $tree = $self->tree;
47 my $attrs = $tree->[0] ne 'tag' ? {} : $tree->[2];
48 return $attrs unless @_;
49
50 # Get
51 return $attrs->{$_[0]} unless @_ > 1 || ref $_[0];
52
53 # Set
54 my $values = ref $_[0] ? $_[0] : {@_};
55 @$attrs{keys %$values} = values %$values;
56
57 return $self;
58}
59
60sub child_nodes { $_[0]->_collect(_nodes($_[0]->tree)) }
61
62sub children { _select($_[0]->_collect(_nodes($_[0]->tree, 1)), $_[1]) }
63
64sub content {
65 my $self = shift;
66
67 my $type = $self->type;
68 if ($type eq 'root' || $type eq 'tag') {
69 return $self->_content(0, 1, @_) if @_;
9a5f1e3f 70 my $html = DOM::Tiny::_HTML->new(xml => $self->xml);
d6512b50 71 return join '', map { $html->tree($_)->render } _nodes($self->tree);
72 }
73
74 return $self->tree->[1] unless @_;
75 $self->tree->[1] = shift;
76 return $self;
77}
78
79sub descendant_nodes { $_[0]->_collect(_all(_nodes($_[0]->tree))) }
80
81sub find { $_[0]->_collect(@{$_[0]->_css->select($_[1])}) }
82
83sub following { _select($_[0]->_collect(@{$_[0]->_siblings(1)->[1]}), $_[1]) }
84sub following_nodes { $_[0]->_collect(@{$_[0]->_siblings->[1]}) }
85
86sub matches { shift->_css->matches(@_) }
87
88sub namespace {
89 my $self = shift;
90
91 return undef if (my $tree = $self->tree)->[0] ne 'tag';
92
93 # Extract namespace prefix and search parents
94 my $ns = $tree->[1] =~ /^(.*?):/ ? "xmlns:$1" : undef;
95 for my $node ($tree, $self->_ancestors) {
96
97 # Namespace for prefix
98 my $attrs = $node->[2];
99 if ($ns) { $_ eq $ns and return $attrs->{$_} for keys %$attrs }
100
101 # Namespace attribute
102 elsif (defined $attrs->{xmlns}) { return $attrs->{xmlns} }
103 }
104
105 return undef;
106}
107
d6512b50 108sub next { $_[0]->_maybe($_[0]->_siblings(1, 0)->[1]) }
109sub next_node { $_[0]->_maybe($_[0]->_siblings(0, 0)->[1]) }
110
111sub parent {
112 my $self = shift;
113 return undef if $self->tree->[0] eq 'root';
114 return $self->_build($self->_parent, $self->xml);
115}
116
117sub parse { shift->_delegate(parse => @_) }
118
119sub preceding { _select($_[0]->_collect(@{$_[0]->_siblings(1)->[0]}), $_[1]) }
120sub preceding_nodes { $_[0]->_collect(@{$_[0]->_siblings->[0]}) }
121
122sub prepend { shift->_add(0, @_) }
123sub prepend_content { shift->_content(0, 0, @_) }
124
125sub previous { $_[0]->_maybe($_[0]->_siblings(1, -1)->[0]) }
126sub previous_node { $_[0]->_maybe($_[0]->_siblings(0, -1)->[0]) }
127
128sub remove { shift->replace('') }
129
130sub replace {
131 my ($self, $new) = @_;
132 return $self->parse($new) if (my $tree = $self->tree)->[0] eq 'root';
133 return $self->_replace($self->_parent, $tree, _nodes($self->_parse($new)));
134}
135
136sub root {
137 my $self = shift;
138 return $self unless my $tree = $self->_ancestors(1);
139 return $self->_build($tree, $self->xml);
140}
141
142sub strip {
143 my $self = shift;
144 return $self if (my $tree = $self->tree)->[0] ne 'tag';
145 return $self->_replace($tree->[3], $tree, _nodes($tree));
146}
147
148sub tag {
149 my ($self, $tag) = @_;
150 return undef if (my $tree = $self->tree)->[0] ne 'tag';
151 return $tree->[1] unless $tag;
152 $tree->[1] = $tag;
153 return $self;
154}
155
78ba4051 156sub tap { shift->DOM::Tiny::_Collection::tap(@_) }
d6512b50 157
158sub text { shift->_all_text(0, @_) }
159
160sub to_string { shift->_delegate('render') }
161
162sub tree { shift->_delegate(tree => @_) }
163
164sub type { shift->tree->[0] }
165
166sub val {
167 my $self = shift;
168
169 # "option"
91880340 170 return $self->{value} // $self->text if (my $tag = $self->tag) eq 'option';
d6512b50 171
172 # "textarea", "input" or "button"
173 return $tag eq 'textarea' ? $self->text : $self->{value} if $tag ne 'select';
174
175 # "select"
176 my $v = $self->find('option:checked')->map('val');
177 return exists $self->{multiple} ? $v->size ? $v->to_array : undef : $v->last;
178}
179
180sub wrap { shift->_wrap(0, @_) }
181sub wrap_content { shift->_wrap(1, @_) }
182
183sub xml { shift->_delegate(xml => @_) }
184
185sub _add {
186 my ($self, $offset, $new) = @_;
187
188 return $self if (my $tree = $self->tree)->[0] eq 'root';
189
190 my $parent = $self->_parent;
191 splice @$parent, _offset($parent, $tree) + $offset, 0,
192 _link($parent, _nodes($self->_parse($new)));
193
194 return $self;
195}
196
197sub _all {
198 map { $_->[0] eq 'tag' ? ($_, _all(_nodes($_))) : ($_) } @_;
199}
200
201sub _all_text {
202 my ($self, $recurse, $trim) = @_;
203
204 # Detect "pre" tag
205 my $tree = $self->tree;
206 $trim = 1 unless defined $trim;
207 map { $_->[1] eq 'pre' and $trim = 0 } $self->_ancestors, $tree
208 if $trim && $tree->[0] ne 'root';
209
210 return _text([_nodes($tree)], $recurse, $trim);
211}
212
213sub _ancestors {
214 my ($self, $root) = @_;
215
216 return unless my $tree = $self->_parent;
217 my @ancestors;
218 do { push @ancestors, $tree }
219 while ($tree->[0] eq 'tag') && ($tree = $tree->[3]);
220 return $root ? $ancestors[-1] : @ancestors[0 .. $#ancestors - 1];
221}
222
223sub _build { shift->new->tree(shift)->xml(shift) }
224
225sub _collect {
226 my $self = shift;
227 my $xml = $self->xml;
78ba4051 228 return DOM::Tiny::_Collection->new(map { $self->_build($_, $xml) } @_);
d6512b50 229}
230
231sub _content {
232 my ($self, $start, $offset, $new) = @_;
233
234 my $tree = $self->tree;
235 unless ($tree->[0] eq 'root' || $tree->[0] eq 'tag') {
236 my $old = $self->content;
237 return $self->content($start ? "$old$new" : "$new$old");
238 }
239
240 $start = $start ? ($#$tree + 1) : _start($tree);
241 $offset = $offset ? $#$tree : 0;
242 splice @$tree, $start, $offset, _link($tree, _nodes($self->_parse($new)));
243
244 return $self;
245}
246
9a5f1e3f 247sub _css { DOM::Tiny::_CSS->new(tree => shift->tree) }
d6512b50 248
249sub _delegate {
250 my ($self, $method) = (shift, shift);
251 return $$self->$method unless @_;
252 $$self->$method(@_);
253 return $self;
254}
255
256sub _link {
257 my ($parent, @children) = @_;
258
259 # Link parent to children
260 for my $node (@children) {
261 my $offset = $node->[0] eq 'tag' ? 3 : 2;
262 $node->[$offset] = $parent;
263 weaken $node->[$offset];
264 }
265
266 return @children;
267}
268
269sub _maybe { $_[1] ? $_[0]->_build($_[1], $_[0]->xml) : undef }
270
271sub _nodes {
272 return unless my $tree = shift;
273 my @nodes = @$tree[_start($tree) .. $#$tree];
274 return shift() ? grep { $_->[0] eq 'tag' } @nodes : @nodes;
275}
276
277sub _offset {
278 my ($parent, $child) = @_;
279 my $i = _start($parent);
280 $_ eq $child ? last : $i++ for @$parent[$i .. $#$parent];
281 return $i;
282}
283
284sub _parent { $_[0]->tree->[$_[0]->type eq 'tag' ? 3 : 2] }
285
9a5f1e3f 286sub _parse { DOM::Tiny::_HTML->new(xml => shift->xml)->parse(shift)->tree }
d6512b50 287
288sub _replace {
927f1351 289 my ($self, $parent, $child, @nodes) = @_;
290 splice @$parent, _offset($parent, $child), 1, _link($parent, @nodes);
d6512b50 291 return $self->parent;
292}
293
294sub _select {
295 my ($collection, $selector) = @_;
296 return $collection unless $selector;
297 return $collection->new(grep { $_->matches($selector) } @$collection);
298}
299
300sub _siblings {
301 my ($self, $tags, $i) = @_;
302
303 return [] unless my $parent = $self->parent;
304
305 my $tree = $self->tree;
306 my (@before, @after, $match);
307 for my $node (_nodes($parent->tree)) {
308 ++$match and next if !$match && $node eq $tree;
309 next if $tags && $node->[0] ne 'tag';
310 $match ? push @after, $node : push @before, $node;
311 }
312
313 return defined $i ? [$before[$i], $after[$i]] : [\@before, \@after];
314}
315
316sub _squish {
317 my $str = shift;
318 $str =~ s/^\s+//;
319 $str =~ s/\s+$//;
320 $str =~ s/\s+/ /g;
321 return $str;
322}
323
324sub _start { $_[0][0] eq 'root' ? 1 : 4 }
325
326sub _text {
327 my ($nodes, $recurse, $trim) = @_;
328
329 # Merge successive text nodes
330 my $i = 0;
331 while (my $next = $nodes->[$i + 1]) {
332 ++$i and next unless $nodes->[$i][0] eq 'text' && $next->[0] eq 'text';
333 splice @$nodes, $i, 2, ['text', $nodes->[$i][1] . $next->[1]];
334 }
335
336 my $text = '';
337 for my $node (@$nodes) {
338 my $type = $node->[0];
339
340 # Text
341 my $chunk = '';
342 if ($type eq 'text') { $chunk = $trim ? _squish $node->[1] : $node->[1] }
343
344 # CDATA or raw text
345 elsif ($type eq 'cdata' || $type eq 'raw') { $chunk = $node->[1] }
346
347 # Nested tag
348 elsif ($type eq 'tag' && $recurse) {
349 no warnings 'recursion';
350 $chunk = _text([_nodes($node)], 1, $node->[1] eq 'pre' ? 0 : $trim);
351 }
352
353 # Add leading whitespace if punctuation allows it
354 $chunk = " $chunk" if $text =~ /\S\z/ && $chunk =~ /^[^.!?,;:\s]+/;
355
356 # Trim whitespace blocks
357 $text .= $chunk if $chunk =~ /\S+/ || !$trim;
358 }
359
360 return $text;
361}
362
363sub _wrap {
364 my ($self, $content, $new) = @_;
365
366 $content = 1 if (my $tree = $self->tree)->[0] eq 'root';
367 $content = 0 if $tree->[0] ne 'root' && $tree->[0] ne 'tag';
368
369 # Find innermost tag
370 my $current;
371 my $first = $new = $self->_parse($new);
372 $current = $first while $first = (_nodes($first, 1))[0];
373 return $self unless $current;
374
375 # Wrap content
376 if ($content) {
377 push @$current, _link($current, _nodes($tree));
378 splice @$tree, _start($tree), $#$tree, _link($tree, _nodes($new));
379 return $self;
380 }
381
382 # Wrap element
383 $self->_replace($self->_parent, $tree, _nodes($new));
384 push @$current, _link($current, $tree);
385 return $self;
386}
387
a292be34 3881;
389
d6512b50 390=encoding utf8
391
a292be34 392=head1 NAME
393
d6512b50 394DOM::Tiny - Minimalistic HTML/XML DOM parser with CSS selectors
a292be34 395
396=head1 SYNOPSIS
397
d6512b50 398 use DOM::Tiny;
399
400 # Parse
401 my $dom = DOM::Tiny->new('<div><p id="a">Test</p><p id="b">123</p></div>');
402
403 # Find
404 say $dom->at('#b')->text;
405 say $dom->find('p')->map('text')->join("\n");
406 say $dom->find('[id]')->map(attr => 'id')->join("\n");
407
408 # Iterate
409 $dom->find('p[id]')->reverse->each(sub { say $_->{id} });
410
411 # Loop
412 for my $e ($dom->find('p[id]')->each) {
413 say $e->{id}, ':', $e->text;
414 }
415
416 # Modify
417 $dom->find('div p')->last->append('<p id="c">456</p>');
418 $dom->find(':not(p)')->map('strip');
419
420 # Render
421 say "$dom";
422
a292be34 423=head1 DESCRIPTION
424
9a5f1e3f 425L<DOM::Tiny> is a minimalistic and relaxed pure-perl HTML/XML DOM parser based
426on L<Mojo::DOM>. It supports the L<HTML Living Standard|https://html.spec.whatwg.org/>
427and L<Extensible Markup Language (XML) 1.0|http://www.w3.org/TR/xml/>, and
428matching based on L<CSS3 selectors|http://www.w3.org/TR/selectors/>. It will
429even try to interpret broken HTML and XML, so you should not use it for
5a70ee9d 430validation.
d6512b50 431
432=head1 NODES AND ELEMENTS
433
434When we parse an HTML/XML fragment, it gets turned into a tree of nodes.
435
436 <!DOCTYPE html>
437 <html>
438 <head><title>Hello</title></head>
439 <body>World!</body>
440 </html>
441
442There are currently eight different kinds of nodes, C<cdata>, C<comment>,
443C<doctype>, C<pi>, C<raw>, C<root>, C<tag> and C<text>. Elements are nodes of
444the type C<tag>.
445
446 root
447 |- doctype (html)
448 +- tag (html)
449 |- tag (head)
450 | +- tag (title)
451 | +- raw (Hello)
452 +- tag (body)
453 +- text (World!)
454
455While all node types are represented as L<DOM::Tiny> objects, some methods like
456L</"attr"> and L</"namespace"> only apply to elements.
457
458=head1 CASE-SENSITIVITY
459
460L<DOM::Tiny> defaults to HTML semantics, that means all tags and attribute
461names are lowercased and selectors need to be lowercase as well.
462
463 # HTML semantics
464 my $dom = DOM::Tiny->new('<P ID="greeting">Hi!</P>');
465 say $dom->at('p[id]')->text;
466
467If XML processing instructions are found, the parser will automatically switch
468into XML mode and everything becomes case-sensitive.
469
470 # XML semantics
471 my $dom = DOM::Tiny->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
472 say $dom->at('P[ID]')->text;
473
474XML detection can also be disabled with the L</"xml"> method.
475
476 # Force XML semantics
477 my $dom = DOM::Tiny->new->xml(1)->parse('<P ID="greeting">Hi!</P>');
478 say $dom->at('P[ID]')->text;
479
480 # Force HTML semantics
481 my $dom = DOM::Tiny->new->xml(0)->parse('<P ID="greeting">Hi!</P>');
482 say $dom->at('p[id]')->text;
483
9a5f1e3f 484=head1 OPERATORS
485
486L<DOM::Tiny> overloads the following operators.
487
488=head2 array
489
490 my @nodes = @$dom;
491
492Alias for L</"child_nodes">.
493
494 # "<!-- Test -->"
495 $dom->parse('<!-- Test --><b>123</b>')->[0];
496
497=head2 bool
498
499 my $bool = !!$dom;
500
501Always true.
502
503=head2 hash
504
505 my %attrs = %$dom;
506
507Alias for L</"attr">.
508
509 # "test"
510 $dom->parse('<div id="test">Test</div>')->at('div')->{id};
511
512=head2 stringify
513
514 my $str = "$dom";
515
516Alias for L</"to_string">.
517
d6512b50 518=head1 METHODS
519
520L<DOM::Tiny> implements the following methods.
521
3793c28f 522=head2 new
523
524 my $dom = DOM::Tiny->new;
525 my $dom = DOM::Tiny->new('<foo bar="baz">I ♥ DOM::Tiny!</foo>');
526
527Construct a new scalar-based L<DOM::Tiny> object and L</"parse"> HTML/XML
528fragment if necessary.
529
d6512b50 530=head2 all_text
531
532 my $trimmed = $dom->all_text;
533 my $untrimmed = $dom->all_text(0);
534
535Extract text content from all descendant nodes of this element, smart
536whitespace trimming is enabled by default.
537
538 # "foo bar baz"
539 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text;
540
541 # "foo\nbarbaz\n"
542 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text(0);
543
544=head2 ancestors
545
546 my $collection = $dom->ancestors;
547 my $collection = $dom->ancestors('div ~ p');
548
549Find all ancestor elements of this node matching the CSS selector and return a
8563f527 550L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 551objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 552
553 # List tag names of ancestor elements
554 say $dom->ancestors->map('tag')->join("\n");
555
556=head2 append
557
558 $dom = $dom->append('<p>I ♥ DOM::Tiny!</p>');
559
560Append HTML/XML fragment to this node.
561
562 # "<div><h1>Test</h1><h2>123</h2></div>"
563 $dom->parse('<div><h1>Test</h1></div>')
564 ->at('h1')->append('<h2>123</h2>')->root;
565
566 # "<p>Test 123</p>"
567 $dom->parse('<p>Test</p>')->at('p')
568 ->child_nodes->first->append(' 123')->root;
569
570=head2 append_content
571
572 $dom = $dom->append_content('<p>I ♥ DOM::Tiny!</p>');
573
574Append HTML/XML fragment (for C<root> and C<tag> nodes) or raw content to this
575node's content.
576
577 # "<div><h1>Test123</h1></div>"
578 $dom->parse('<div><h1>Test</h1></div>')
579 ->at('h1')->append_content('123')->root;
580
581 # "<!-- Test 123 --><br>"
582 $dom->parse('<!-- Test --><br>')
583 ->child_nodes->first->append_content('123 ')->root;
584
585 # "<p>Test<i>123</i></p>"
586 $dom->parse('<p>Test</p>')->at('p')->append_content('<i>123</i>')->root;
587
588=head2 at
589
590 my $result = $dom->at('div ~ p');
591
592Find first descendant element of this element matching the CSS selector and
593return it as a L<DOM::Tiny> object or return C<undef> if none could be found.
9a5f1e3f 594All selectors listed in L</"SELECTORS"> are supported.
d6512b50 595
596 # Find first element with "svg" namespace definition
597 my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};
598
599=head2 attr
600
601 my $hash = $dom->attr;
602 my $foo = $dom->attr('foo');
603 $dom = $dom->attr({foo => 'bar'});
604 $dom = $dom->attr(foo => 'bar');
605
606This element's attributes.
607
608 # Remove an attribute
609 delete $dom->attr->{id};
610
611 # Attribute without value
612 $dom->attr(selected => undef);
613
614 # List id attributes
615 say $dom->find('*')->map(attr => 'id')->compact->join("\n");
616
617=head2 child_nodes
618
619 my $collection = $dom->child_nodes;
620
8563f527 621Return a L<collection|/"COLLECTION METHODS"> containing all child nodes of this
d6512b50 622element as L<DOM::Tiny> objects.
623
624 # "<p><b>123</b></p>"
625 $dom->parse('<p>Test<b>123</b></p>')->at('p')->child_nodes->first->remove;
626
627 # "<!DOCTYPE html>"
628 $dom->parse('<!DOCTYPE html><b>123</b>')->child_nodes->first;
629
630 # " Test "
631 $dom->parse('<b>123</b><!-- Test -->')->child_nodes->last->content;
632
633=head2 children
634
635 my $collection = $dom->children;
636 my $collection = $dom->children('div ~ p');
637
638Find all child elements of this element matching the CSS selector and return a
8563f527 639L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 640objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 641
642 # Show tag name of random child element
643 say $dom->children->shuffle->first->tag;
644
645=head2 content
646
647 my $str = $dom->content;
648 $dom = $dom->content('<p>I ♥ DOM::Tiny!</p>');
649
650Return this node's content or replace it with HTML/XML fragment (for C<root>
651and C<tag> nodes) or raw content.
652
653 # "<b>Test</b>"
654 $dom->parse('<div><b>Test</b></div>')->at('div')->content;
655
656 # "<div><h1>123</h1></div>"
657 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('123')->root;
658
659 # "<p><i>123</i></p>"
660 $dom->parse('<p>Test</p>')->at('p')->content('<i>123</i>')->root;
661
662 # "<div><h1></h1></div>"
663 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('')->root;
664
665 # " Test "
666 $dom->parse('<!-- Test --><br>')->child_nodes->first->content;
667
668 # "<div><!-- 123 -->456</div>"
669 $dom->parse('<div><!-- Test -->456</div>')
670 ->at('div')->child_nodes->first->content(' 123 ')->root;
671
672=head2 descendant_nodes
673
674 my $collection = $dom->descendant_nodes;
675
8563f527 676Return a L<collection|/"COLLECTION METHODS"> containing all descendant nodes of
d6512b50 677this element as L<DOM::Tiny> objects.
678
679 # "<p><b>123</b></p>"
680 $dom->parse('<p><!-- Test --><b>123<!-- 456 --></b></p>')
681 ->descendant_nodes->grep(sub { $_->type eq 'comment' })
682 ->map('remove')->first;
683
684 # "<p><b>test</b>test</p>"
685 $dom->parse('<p><b>123</b>456</p>')
686 ->at('p')->descendant_nodes->grep(sub { $_->type eq 'text' })
687 ->map(content => 'test')->first->root;
688
689=head2 find
690
691 my $collection = $dom->find('div ~ p');
692
693Find all descendant elements of this element matching the CSS selector and
8563f527 694return a L<collection|/"COLLECTION METHODS"> containing these elements as
9a5f1e3f 695L<DOM::Tiny> objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 696
697 # Find a specific element and extract information
698 my $id = $dom->find('div')->[23]{id};
699
700 # Extract information from multiple elements
701 my @headers = $dom->find('h1, h2, h3')->map('text')->each;
702
703 # Count all the different tags
704 my $hash = $dom->find('*')->reduce(sub { $a->{$b->tag}++; $a }, {});
705
706 # Find elements with a class that contains dots
707 my @divs = $dom->find('div.foo\.bar')->each;
708
709=head2 following
710
711 my $collection = $dom->following;
712 my $collection = $dom->following('div ~ p');
713
714Find all sibling elements after this node matching the CSS selector and return
8563f527 715a L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 716objects. All selectors listen in L</"SELECTORS"> are supported.
d6512b50 717
718 # List tags of sibling elements after this node
719 say $dom->following->map('tag')->join("\n");
720
721=head2 following_nodes
722
723 my $collection = $dom->following_nodes;
724
8563f527 725Return a L<collection|/"COLLECTION METHODS"> containing all sibling nodes after
d6512b50 726this node as L<DOM::Tiny> objects.
727
728 # "C"
729 $dom->parse('<p>A</p><!-- B -->C')->at('p')->following_nodes->last->content;
730
731=head2 matches
732
733 my $bool = $dom->matches('div ~ p');
734
9a5f1e3f 735Check if this element matches the CSS selector. All selectors listed in
736L</"SELECTORS"> are supported.
d6512b50 737
738 # True
739 $dom->parse('<p class="a">A</p>')->at('p')->matches('.a');
740 $dom->parse('<p class="a">A</p>')->at('p')->matches('p[class]');
741
742 # False
743 $dom->parse('<p class="a">A</p>')->at('p')->matches('.b');
744 $dom->parse('<p class="a">A</p>')->at('p')->matches('p[id]');
745
746=head2 namespace
747
748 my $namespace = $dom->namespace;
749
750Find this element's namespace or return C<undef> if none could be found.
751
752 # Find namespace for an element with namespace prefix
753 my $namespace = $dom->at('svg > svg\:circle')->namespace;
754
755 # Find namespace for an element that may or may not have a namespace prefix
756 my $namespace = $dom->at('svg > circle')->namespace;
757
d6512b50 758=head2 next
759
760 my $sibling = $dom->next;
761
762Return L<DOM::Tiny> object for next sibling element or C<undef> if there are no
763more siblings.
764
765 # "<h2>123</h2>"
766 $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;
767
768=head2 next_node
769
770 my $sibling = $dom->next_node;
771
772Return L<DOM::Tiny> object for next sibling node or C<undef> if there are no
773more siblings.
774
775 # "456"
776 $dom->parse('<p><b>123</b><!-- Test -->456</p>')
777 ->at('b')->next_node->next_node;
778
779 # " Test "
780 $dom->parse('<p><b>123</b><!-- Test -->456</p>')
781 ->at('b')->next_node->content;
782
783=head2 parent
784
785 my $parent = $dom->parent;
786
787Return L<DOM::Tiny> object for parent of this node or C<undef> if this node has
788no parent.
789
790=head2 parse
791
792 $dom = $dom->parse('<foo bar="baz">I ♥ DOM::Tiny!</foo>');
793
9a5f1e3f 794Parse HTML/XML fragment.
d6512b50 795
796 # Parse XML
797 my $dom = DOM::Tiny->new->xml(1)->parse($xml);
798
799=head2 preceding
800
801 my $collection = $dom->preceding;
802 my $collection = $dom->preceding('div ~ p');
803
804Find all sibling elements before this node matching the CSS selector and return
8563f527 805a L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 806objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 807
808 # List tags of sibling elements before this node
809 say $dom->preceding->map('tag')->join("\n");
810
811=head2 preceding_nodes
812
813 my $collection = $dom->preceding_nodes;
814
8563f527 815Return a L<collection|/"COLLECTION METHODS"> containing all sibling nodes
816before this node as L<DOM::Tiny> objects.
d6512b50 817
818 # "A"
819 $dom->parse('A<!-- B --><p>C</p>')->at('p')->preceding_nodes->first->content;
820
821=head2 prepend
822
823 $dom = $dom->prepend('<p>I ♥ DOM::Tiny!</p>');
824
825Prepend HTML/XML fragment to this node.
826
827 # "<div><h1>Test</h1><h2>123</h2></div>"
828 $dom->parse('<div><h2>123</h2></div>')
829 ->at('h2')->prepend('<h1>Test</h1>')->root;
830
831 # "<p>Test 123</p>"
832 $dom->parse('<p>123</p>')
833 ->at('p')->child_nodes->first->prepend('Test ')->root;
834
835=head2 prepend_content
836
837 $dom = $dom->prepend_content('<p>I ♥ DOM::Tiny!</p>');
838
839Prepend HTML/XML fragment (for C<root> and C<tag> nodes) or raw content to this
840node's content.
841
842 # "<div><h2>Test123</h2></div>"
843 $dom->parse('<div><h2>123</h2></div>')
844 ->at('h2')->prepend_content('Test')->root;
845
846 # "<!-- Test 123 --><br>"
847 $dom->parse('<!-- 123 --><br>')
848 ->child_nodes->first->prepend_content(' Test')->root;
849
850 # "<p><i>123</i>Test</p>"
851 $dom->parse('<p>Test</p>')->at('p')->prepend_content('<i>123</i>')->root;
852
853=head2 previous
854
855 my $sibling = $dom->previous;
856
857Return L<DOM::Tiny> object for previous sibling element or C<undef> if there
858are no more siblings.
859
860 # "<h1>Test</h1>"
861 $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h2')->previous;
862
863=head2 previous_node
864
865 my $sibling = $dom->previous_node;
866
867Return L<DOM::Tiny> object for previous sibling node or C<undef> if there are
868no more siblings.
869
870 # "123"
871 $dom->parse('<p>123<!-- Test --><b>456</b></p>')
872 ->at('b')->previous_node->previous_node;
873
874 # " Test "
875 $dom->parse('<p>123<!-- Test --><b>456</b></p>')
876 ->at('b')->previous_node->content;
877
878=head2 remove
879
880 my $parent = $dom->remove;
881
882Remove this node and return L</"root"> (for C<root> nodes) or L</"parent">.
883
884 # "<div></div>"
885 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->remove;
886
887 # "<p><b>456</b></p>"
888 $dom->parse('<p>123<b>456</b></p>')
889 ->at('p')->child_nodes->first->remove->root;
890
891=head2 replace
892
893 my $parent = $dom->replace('<div>I ♥ DOM::Tiny!</div>');
894
895Replace this node with HTML/XML fragment and return L</"root"> (for C<root>
896nodes) or L</"parent">.
897
898 # "<div><h2>123</h2></div>"
899 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->replace('<h2>123</h2>');
900
901 # "<p><b>123</b></p>"
902 $dom->parse('<p>Test</p>')
903 ->at('p')->child_nodes->[0]->replace('<b>123</b>')->root;
904
905=head2 root
906
907 my $root = $dom->root;
908
909Return L<DOM::Tiny> object for C<root> node.
910
911=head2 strip
912
913 my $parent = $dom->strip;
914
915Remove this element while preserving its content and return L</"parent">.
916
917 # "<div>Test</div>"
918 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->strip;
919
920=head2 tag
921
922 my $tag = $dom->tag;
923 $dom = $dom->tag('div');
924
925This element's tag name.
926
927 # List tag names of child elements
928 say $dom->children->map('tag')->join("\n");
929
930=head2 tap
931
932 $dom = $dom->tap(sub {...});
933
e99ef07d 934Equivalent to L<Mojo::Base/"tap">.
d6512b50 935
936=head2 text
937
938 my $trimmed = $dom->text;
939 my $untrimmed = $dom->text(0);
940
941Extract text content from this element only (not including child elements),
942smart whitespace trimming is enabled by default.
943
944 # "foo baz"
945 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text;
946
947 # "foo\nbaz\n"
948 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text(0);
949
950=head2 to_string
951
952 my $str = $dom->to_string;
953
954Render this node and its content to HTML/XML.
955
956 # "<b>Test</b>"
957 $dom->parse('<div><b>Test</b></div>')->at('div b')->to_string;
958
959=head2 tree
960
961 my $tree = $dom->tree;
962 $dom = $dom->tree(['root']);
963
964Document Object Model. Note that this structure should only be used very
965carefully since it is very dynamic.
966
967=head2 type
968
969 my $type = $dom->type;
970
971This node's type, usually C<cdata>, C<comment>, C<doctype>, C<pi>, C<raw>,
972C<root>, C<tag> or C<text>.
973
974 # "cdata"
975 $dom->parse('<![CDATA[Test]]>')->child_nodes->first->type;
976
977 # "comment"
978 $dom->parse('<!-- Test -->')->child_nodes->first->type;
979
980 # "doctype"
981 $dom->parse('<!DOCTYPE html>')->child_nodes->first->type;
982
983 # "pi"
984 $dom->parse('<?xml version="1.0"?>')->child_nodes->first->type;
985
986 # "raw"
987 $dom->parse('<title>Test</title>')->at('title')->child_nodes->first->type;
988
989 # "root"
990 $dom->parse('<p>Test</p>')->type;
991
992 # "tag"
993 $dom->parse('<p>Test</p>')->at('p')->type;
994
995 # "text"
996 $dom->parse('<p>Test</p>')->at('p')->child_nodes->first->type;
997
998=head2 val
999
1000 my $value = $dom->val;
1001
1002Extract value from form element (such as C<button>, C<input>, C<option>,
1003C<select> and C<textarea>) or return C<undef> if this element has no value. In
1004the case of C<select> with C<multiple> attribute, find C<option> elements with
1005C<selected> attribute and return an array reference with all values or C<undef>
1006if none could be found.
1007
1008 # "a"
1009 $dom->parse('<input name="test" value="a">')->at('input')->val;
1010
1011 # "b"
1012 $dom->parse('<textarea>b</textarea>')->at('textarea')->val;
1013
1014 # "c"
1015 $dom->parse('<option value="c">Test</option>')->at('option')->val;
1016
1017 # "d"
1018 $dom->parse('<select><option selected>d</option></select>')
1019 ->at('select')->val;
1020
1021 # "e"
1022 $dom->parse('<select multiple><option selected>e</option></select>')
1023 ->at('select')->val->[0];
1024
1025=head2 wrap
1026
1027 $dom = $dom->wrap('<div></div>');
1028
1029Wrap HTML/XML fragment around this node, placing it as the last child of the
1030first innermost element.
1031
1032 # "<p>123<b>Test</b></p>"
1033 $dom->parse('<b>Test</b>')->at('b')->wrap('<p>123</p>')->root;
1034
1035 # "<div><p><b>Test</b></p>123</div>"
1036 $dom->parse('<b>Test</b>')->at('b')->wrap('<div><p></p>123</div>')->root;
1037
1038 # "<p><b>Test</b></p><p>123</p>"
1039 $dom->parse('<b>Test</b>')->at('b')->wrap('<p></p><p>123</p>')->root;
1040
1041 # "<p><b>Test</b></p>"
1042 $dom->parse('<p>Test</p>')->at('p')->child_nodes->first->wrap('<b>')->root;
1043
1044=head2 wrap_content
1045
1046 $dom = $dom->wrap_content('<div></div>');
1047
1048Wrap HTML/XML fragment around this node's content, placing it as the last
1049children of the first innermost element.
1050
1051 # "<p><b>123Test</b></p>"
1052 $dom->parse('<p>Test<p>')->at('p')->wrap_content('<b>123</b>')->root;
1053
1054 # "<p><b>Test</b></p><p>123</p>"
1055 $dom->parse('<b>Test</b>')->wrap_content('<p></p><p>123</p>');
1056
1057=head2 xml
1058
1059 my $bool = $dom->xml;
1060 $dom = $dom->xml($bool);
1061
1062Disable HTML semantics in parser and activate case-sensitivity, defaults to
1063auto detection based on processing instructions.
1064
78ba4051 1065=head1 COLLECTION METHODS
1066
9a5f1e3f 1067Some L<DOM::Tiny> methods return an array-based collection object based on
1068L<Mojo::Collection>, which can either be accessed directly as an array
1069reference, or with the following methods.
78ba4051 1070
1071 # Chain methods
1072 $collection->map(sub { ucfirst })->shuffle->each(sub {
1073 my ($word, $num) = @_;
1074 say "$num: $word";
1075 });
1076
1077 # Access array directly to manipulate collection
1078 $collection->[23] += 100;
1079 say for @$collection;
1080
1081=head2 compact
1082
1083 my $new = $collection->compact;
1084
1085Create a new collection with all elements that are defined and not an empty
1086string.
1087
1088 # $collection contains (0, 1, undef, 2, '', 3)
1089 $collection->compact->join(', '); # "0, 1, 2, 3"
1090
1091=head2 each
1092
1093 my @elements = $collection->each;
1094 $collection = $collection->each(sub {...});
1095
1096Evaluate callback for each element in collection or return all elements as a
1097list if none has been provided. The element will be the first argument passed
1098to the callback and is also available as C<$_>.
1099
1100 # Make a numbered list
1101 $collection->each(sub {
1102 my ($e, $num) = @_;
1103 say "$num: $e";
1104 });
1105
1106=head2 first
1107
1108 my $first = $collection->first;
1109 my $first = $collection->first(qr/foo/);
1110 my $first = $collection->first(sub {...});
1111 my $first = $collection->first($method);
1112 my $first = $collection->first($method, @args);
1113
1114Evaluate regular expression/callback for, or call method on, each element in
1115collection and return the first one that matched the regular expression, or for
1116which the callback/method returned true. The element will be the first argument
1117passed to the callback and is also available as C<$_>.
1118
1119 # Longer version
1120 my $first = $collection->first(sub { $_->$method(@args) });
1121
1122 # Find first value that contains the word "dom"
1123 my $interesting = $collection->first(qr/dom/i);
1124
1125 # Find first value that is greater than 5
1126 my $greater = $collection->first(sub { $_ > 5 });
1127
1128=head2 flatten
1129
1130 my $new = $collection->flatten;
1131
1132Flatten nested collections/arrays recursively and create a new collection with
1133all elements.
1134
1135 # $collection contains (1, [2, [3, 4], 5, [6]], 7)
1136 $collection->flatten->join(', '); # "1, 2, 3, 4, 5, 6, 7"
1137
1138=head2 grep
1139
1140 my $new = $collection->grep(qr/foo/);
1141 my $new = $collection->grep(sub {...});
1142 my $new = $collection->grep($method);
1143 my $new = $collection->grep($method, @args);
1144
1145Evaluate regular expression/callback for, or call method on, each element in
1146collection and create a new collection with all elements that matched the
1147regular expression, or for which the callback/method returned true. The element
1148will be the first argument passed to the callback and is also available as
1149C<$_>.
1150
1151 # Longer version
1152 my $new = $collection->grep(sub { $_->$method(@args) });
1153
1154 # Find all values that contain the word "dom"
1155 my $interesting = $collection->grep(qr/dom/i);
1156
1157 # Find all values that are greater than 5
1158 my $greater = $collection->grep(sub { $_ > 5 });
1159
1160=head2 join
1161
1162 my $stream = $collection->join;
1163 my $stream = $collection->join("\n");
1164
1165Turn collection into string.
1166
1167 # Join all values with commas
1168 $collection->join(', ');
1169
1170=head2 last
1171
1172 my $last = $collection->last;
1173
1174Return the last element in collection.
1175
1176=head2 map
1177
1178 my $new = $collection->map(sub {...});
1179 my $new = $collection->map($method);
1180 my $new = $collection->map($method, @args);
1181
1182Evaluate callback for, or call method on, each element in collection and create
1183a new collection from the results. The element will be the first argument
1184passed to the callback and is also available as C<$_>.
1185
1186 # Longer version
1187 my $new = $collection->map(sub { $_->$method(@args) });
1188
1189 # Append the word "dom" to all values
1190 my $domified = $collection->map(sub { $_ . 'dom' });
1191
1192=head2 reduce
1193
1194 my $result = $collection->reduce(sub {...});
1195 my $result = $collection->reduce(sub {...}, $initial);
1196
1197Reduce elements in collection with callback, the first element will be used as
1198initial value if none has been provided.
1199
1200 # Calculate the sum of all values
1201 my $sum = $collection->reduce(sub { $a + $b });
1202
1203 # Count how often each value occurs in collection
1204 my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {});
1205
1206=head2 reverse
1207
1208 my $new = $collection->reverse;
1209
1210Create a new collection with all elements in reverse order.
1211
1212=head2 slice
1213
1214 my $new = $collection->slice(4 .. 7);
1215
1216Create a new collection with all selected elements.
1217
1218 # $collection contains ('A', 'B', 'C', 'D', 'E')
1219 $collection->slice(1, 2, 4)->join(' '); # "B C E"
1220
1221=head2 shuffle
1222
1223 my $new = $collection->shuffle;
1224
1225Create a new collection with all elements in random order.
1226
1227=head2 size
1228
1229 my $size = $collection->size;
1230
1231Number of elements in collection.
1232
1233=head2 sort
1234
1235 my $new = $collection->sort;
1236 my $new = $collection->sort(sub {...});
1237
1238Sort elements based on return value of callback and create a new collection
1239from the results.
1240
1241 # Sort values case-insensitive
1242 my $case_insensitive = $collection->sort(sub { uc($a) cmp uc($b) });
1243
1244=head2 tap
1245
1246 $collection = $collection->tap(sub {...});
1247
1248Equivalent to L<Mojo::Base/"tap">.
1249
1250=head2 to_array
1251
1252 my $array = $collection->to_array;
1253
1254Turn collection into array reference.
1255
1256=head2 uniq
1257
1258 my $new = $collection->uniq;
1259 my $new = $collection->uniq(sub {...});
1260 my $new = $collection->uniq($method);
1261 my $new = $collection->uniq($method, @args);
1262
1263Create a new collection without duplicate elements, using the string
1264representation of either the elements or the return value of the
1265callback/method.
1266
1267 # Longer version
1268 my $new = $collection->uniq(sub { $_->$method(@args) });
1269
1270 # $collection contains ('foo', 'bar', 'bar', 'baz')
1271 $collection->uniq->join(' '); # "foo bar baz"
1272
1273 # $collection contains ([1, 2], [2, 1], [3, 2])
1274 $collection->uniq(sub{ $_->[1] })->to_array; # "[[1, 2], [2, 1]]"
1275
9a5f1e3f 1276=head1 SELECTORS
1277
1278L<DOM::Tiny> uses a CSS selector engine based on L<Mojo::DOM::CSS>. All CSS
1279selectors that make sense for a standalone parser are supported.
1280
1281=head2 *
1282
1283Any element.
1284
1285 my $all = $dom->find('*');
1286
1287=head2 E
1288
1289An element of type C<E>.
1290
1291 my $title = $dom->at('title');
1292
1293=head2 E[foo]
1294
1295An C<E> element with a C<foo> attribute.
1296
1297 my $links = $dom->find('a[href]');
1298
1299=head2 E[foo="bar"]
1300
1301An C<E> element whose C<foo> attribute value is exactly equal to C<bar>.
1302
1303 my $case_sensitive = $dom->find('input[type="hidden"]');
1304 my $case_sensitive = $dom->find('input[type=hidden]');
1305
1306=head2 E[foo="bar" i]
1307
1308An C<E> element whose C<foo> attribute value is exactly equal to any
1309(ASCII-range) case-permutation of C<bar>. Note that this selector is
1310EXPERIMENTAL and might change without warning!
1311
1312 my $case_insensitive = $dom->find('input[type="hidden" i]');
1313 my $case_insensitive = $dom->find('input[type=hidden i]');
1314 my $case_insensitive = $dom->find('input[class~="foo" i]');
1315
1316This selector is part of
1317L<Selectors Level 4|http://dev.w3.org/csswg/selectors-4>, which is still a work
1318in progress.
1319
1320=head2 E[foo~="bar"]
1321
1322An C<E> element whose C<foo> attribute value is a list of whitespace-separated
1323values, one of which is exactly equal to C<bar>.
1324
1325 my $foo = $dom->find('input[class~="foo"]');
1326 my $foo = $dom->find('input[class~=foo]');
1327
1328=head2 E[foo^="bar"]
1329
1330An C<E> element whose C<foo> attribute value begins exactly with the string
1331C<bar>.
1332
1333 my $begins_with = $dom->find('input[name^="f"]');
1334 my $begins_with = $dom->find('input[name^=f]');
1335
1336=head2 E[foo$="bar"]
1337
1338An C<E> element whose C<foo> attribute value ends exactly with the string
1339C<bar>.
1340
1341 my $ends_with = $dom->find('input[name$="o"]');
1342 my $ends_with = $dom->find('input[name$=o]');
1343
1344=head2 E[foo*="bar"]
1345
1346An C<E> element whose C<foo> attribute value contains the substring C<bar>.
1347
1348 my $contains = $dom->find('input[name*="fo"]');
1349 my $contains = $dom->find('input[name*=fo]');
1350
1351=head2 E:root
1352
1353An C<E> element, root of the document.
1354
1355 my $root = $dom->at(':root');
1356
1357=head2 E:nth-child(n)
1358
1359An C<E> element, the C<n-th> child of its parent.
1360
1361 my $third = $dom->find('div:nth-child(3)');
1362 my $odd = $dom->find('div:nth-child(odd)');
1363 my $even = $dom->find('div:nth-child(even)');
1364 my $top3 = $dom->find('div:nth-child(-n+3)');
1365
1366=head2 E:nth-last-child(n)
1367
1368An C<E> element, the C<n-th> child of its parent, counting from the last one.
1369
1370 my $third = $dom->find('div:nth-last-child(3)');
1371 my $odd = $dom->find('div:nth-last-child(odd)');
1372 my $even = $dom->find('div:nth-last-child(even)');
1373 my $bottom3 = $dom->find('div:nth-last-child(-n+3)');
1374
1375=head2 E:nth-of-type(n)
1376
1377An C<E> element, the C<n-th> sibling of its type.
1378
1379 my $third = $dom->find('div:nth-of-type(3)');
1380 my $odd = $dom->find('div:nth-of-type(odd)');
1381 my $even = $dom->find('div:nth-of-type(even)');
1382 my $top3 = $dom->find('div:nth-of-type(-n+3)');
1383
1384=head2 E:nth-last-of-type(n)
1385
1386An C<E> element, the C<n-th> sibling of its type, counting from the last one.
1387
1388 my $third = $dom->find('div:nth-last-of-type(3)');
1389 my $odd = $dom->find('div:nth-last-of-type(odd)');
1390 my $even = $dom->find('div:nth-last-of-type(even)');
1391 my $bottom3 = $dom->find('div:nth-last-of-type(-n+3)');
1392
1393=head2 E:first-child
1394
1395An C<E> element, first child of its parent.
1396
1397 my $first = $dom->find('div p:first-child');
1398
1399=head2 E:last-child
1400
1401An C<E> element, last child of its parent.
1402
1403 my $last = $dom->find('div p:last-child');
1404
1405=head2 E:first-of-type
1406
1407An C<E> element, first sibling of its type.
1408
1409 my $first = $dom->find('div p:first-of-type');
1410
1411=head2 E:last-of-type
1412
1413An C<E> element, last sibling of its type.
1414
1415 my $last = $dom->find('div p:last-of-type');
1416
1417=head2 E:only-child
1418
1419An C<E> element, only child of its parent.
1420
1421 my $lonely = $dom->find('div p:only-child');
1422
1423=head2 E:only-of-type
1424
1425An C<E> element, only sibling of its type.
1426
1427 my $lonely = $dom->find('div p:only-of-type');
1428
1429=head2 E:empty
1430
1431An C<E> element that has no children (including text nodes).
1432
1433 my $empty = $dom->find(':empty');
1434
1435=head2 E:checked
1436
1437A user interface element C<E> which is checked (for instance a radio-button or
1438checkbox).
1439
1440 my $input = $dom->find(':checked');
1441
1442=head2 E.warning
1443
1444An C<E> element whose class is "warning".
1445
1446 my $warning = $dom->find('div.warning');
1447
1448=head2 E#myid
1449
1450An C<E> element with C<ID> equal to "myid".
1451
1452 my $foo = $dom->at('div#foo');
1453
1454=head2 E:not(s)
1455
1456An C<E> element that does not match simple selector C<s>.
1457
1458 my $others = $dom->find('div p:not(:first-child)');
1459
1460=head2 E F
1461
1462An C<F> element descendant of an C<E> element.
1463
1464 my $headlines = $dom->find('div h1');
1465
1466=head2 E E<gt> F
1467
1468An C<F> element child of an C<E> element.
1469
1470 my $headlines = $dom->find('html > body > div > h1');
1471
1472=head2 E + F
1473
1474An C<F> element immediately preceded by an C<E> element.
1475
1476 my $second = $dom->find('h1 + h2');
1477
1478=head2 E ~ F
1479
1480An C<F> element preceded by an C<E> element.
1481
1482 my $second = $dom->find('h1 ~ h2');
1483
1484=head2 E, F, G
1485
1486Elements of type C<E>, C<F> and C<G>.
1487
1488 my $headlines = $dom->find('h1, h2, h3');
1489
1490=head2 E[foo=bar][bar=baz]
1491
1492An C<E> element whose attributes match all following attribute selectors.
1493
1494 my $links = $dom->find('a[foo^=b][foo$=ar]');
1495
a292be34 1496=head1 BUGS
1497
1498Report any issues on the public bugtracker.
1499
1500=head1 AUTHOR
1501
1502Dan Book <dbook@cpan.org>
1503
1504=head1 COPYRIGHT AND LICENSE
1505
1506This software is Copyright (c) 2015 by Dan Book.
1507
1508This is free software, licensed under:
1509
1510 The Artistic License 2.0 (GPL Compatible)
1511
1512=head1 SEE ALSO
1513
d6512b50 1514L<Mojo::DOM>, L<XML::LibXML>, L<XML::Twig>, L<HTML::TreeBuilder>, L<XML::Smart>
9a5f1e3f 1515
1516=for Pod::Coverage TO_JSON
1517
1518=cut