better example text
[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
1644a3ee 156sub tap { 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
4c89d5f3 216 return () unless my $tree = $self->_parent;
d6512b50 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 {
4c89d5f3 272 return () unless my $tree = shift;
d6512b50 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
63873d67 484=head1 SELECTORS
485
486L<DOM::Tiny> uses a CSS selector engine based on L<Mojo::DOM::CSS>. All CSS
487selectors that make sense for a standalone parser are supported.
488
489=head2 *
490
491Any element.
492
493 my $all = $dom->find('*');
494
495=head2 E
496
497An element of type C<E>.
498
499 my $title = $dom->at('title');
500
501=head2 E[foo]
502
503An C<E> element with a C<foo> attribute.
504
505 my $links = $dom->find('a[href]');
506
507=head2 E[foo="bar"]
508
509An C<E> element whose C<foo> attribute value is exactly equal to C<bar>.
510
511 my $case_sensitive = $dom->find('input[type="hidden"]');
512 my $case_sensitive = $dom->find('input[type=hidden]');
513
514=head2 E[foo="bar" i]
515
516An C<E> element whose C<foo> attribute value is exactly equal to any
517(ASCII-range) case-permutation of C<bar>. Note that this selector is
518EXPERIMENTAL and might change without warning!
519
520 my $case_insensitive = $dom->find('input[type="hidden" i]');
521 my $case_insensitive = $dom->find('input[type=hidden i]');
522 my $case_insensitive = $dom->find('input[class~="foo" i]');
523
524This selector is part of
525L<Selectors Level 4|http://dev.w3.org/csswg/selectors-4>, which is still a work
526in progress.
527
528=head2 E[foo~="bar"]
529
530An C<E> element whose C<foo> attribute value is a list of whitespace-separated
531values, one of which is exactly equal to C<bar>.
532
533 my $foo = $dom->find('input[class~="foo"]');
534 my $foo = $dom->find('input[class~=foo]');
535
536=head2 E[foo^="bar"]
537
538An C<E> element whose C<foo> attribute value begins exactly with the string
539C<bar>.
540
541 my $begins_with = $dom->find('input[name^="f"]');
542 my $begins_with = $dom->find('input[name^=f]');
543
544=head2 E[foo$="bar"]
545
546An C<E> element whose C<foo> attribute value ends exactly with the string
547C<bar>.
548
549 my $ends_with = $dom->find('input[name$="o"]');
550 my $ends_with = $dom->find('input[name$=o]');
551
552=head2 E[foo*="bar"]
553
554An C<E> element whose C<foo> attribute value contains the substring C<bar>.
555
556 my $contains = $dom->find('input[name*="fo"]');
557 my $contains = $dom->find('input[name*=fo]');
558
559=head2 E:root
560
561An C<E> element, root of the document.
562
563 my $root = $dom->at(':root');
564
565=head2 E:nth-child(n)
566
567An C<E> element, the C<n-th> child of its parent.
568
569 my $third = $dom->find('div:nth-child(3)');
570 my $odd = $dom->find('div:nth-child(odd)');
571 my $even = $dom->find('div:nth-child(even)');
572 my $top3 = $dom->find('div:nth-child(-n+3)');
573
574=head2 E:nth-last-child(n)
575
576An C<E> element, the C<n-th> child of its parent, counting from the last one.
577
578 my $third = $dom->find('div:nth-last-child(3)');
579 my $odd = $dom->find('div:nth-last-child(odd)');
580 my $even = $dom->find('div:nth-last-child(even)');
581 my $bottom3 = $dom->find('div:nth-last-child(-n+3)');
582
583=head2 E:nth-of-type(n)
584
585An C<E> element, the C<n-th> sibling of its type.
586
587 my $third = $dom->find('div:nth-of-type(3)');
588 my $odd = $dom->find('div:nth-of-type(odd)');
589 my $even = $dom->find('div:nth-of-type(even)');
590 my $top3 = $dom->find('div:nth-of-type(-n+3)');
591
592=head2 E:nth-last-of-type(n)
593
594An C<E> element, the C<n-th> sibling of its type, counting from the last one.
595
596 my $third = $dom->find('div:nth-last-of-type(3)');
597 my $odd = $dom->find('div:nth-last-of-type(odd)');
598 my $even = $dom->find('div:nth-last-of-type(even)');
599 my $bottom3 = $dom->find('div:nth-last-of-type(-n+3)');
600
601=head2 E:first-child
602
603An C<E> element, first child of its parent.
604
605 my $first = $dom->find('div p:first-child');
606
607=head2 E:last-child
608
609An C<E> element, last child of its parent.
610
611 my $last = $dom->find('div p:last-child');
612
613=head2 E:first-of-type
614
615An C<E> element, first sibling of its type.
616
617 my $first = $dom->find('div p:first-of-type');
618
619=head2 E:last-of-type
620
621An C<E> element, last sibling of its type.
622
623 my $last = $dom->find('div p:last-of-type');
624
625=head2 E:only-child
626
627An C<E> element, only child of its parent.
628
629 my $lonely = $dom->find('div p:only-child');
630
631=head2 E:only-of-type
632
633An C<E> element, only sibling of its type.
634
635 my $lonely = $dom->find('div p:only-of-type');
636
637=head2 E:empty
638
639An C<E> element that has no children (including text nodes).
640
641 my $empty = $dom->find(':empty');
642
643=head2 E:checked
644
645A user interface element C<E> which is checked (for instance a radio-button or
646checkbox).
647
648 my $input = $dom->find(':checked');
649
650=head2 E.warning
651
652An C<E> element whose class is "warning".
653
654 my $warning = $dom->find('div.warning');
655
656=head2 E#myid
657
658An C<E> element with C<ID> equal to "myid".
659
660 my $foo = $dom->at('div#foo');
661
662=head2 E:not(s)
663
664An C<E> element that does not match simple selector C<s>.
665
666 my $others = $dom->find('div p:not(:first-child)');
667
668=head2 E F
669
670An C<F> element descendant of an C<E> element.
671
672 my $headlines = $dom->find('div h1');
673
674=head2 E E<gt> F
675
676An C<F> element child of an C<E> element.
677
678 my $headlines = $dom->find('html > body > div > h1');
679
680=head2 E + F
681
682An C<F> element immediately preceded by an C<E> element.
683
684 my $second = $dom->find('h1 + h2');
685
686=head2 E ~ F
687
688An C<F> element preceded by an C<E> element.
689
690 my $second = $dom->find('h1 ~ h2');
691
692=head2 E, F, G
693
694Elements of type C<E>, C<F> and C<G>.
695
696 my $headlines = $dom->find('h1, h2, h3');
697
698=head2 E[foo=bar][bar=baz]
699
700An C<E> element whose attributes match all following attribute selectors.
701
702 my $links = $dom->find('a[foo^=b][foo$=ar]');
703
9a5f1e3f 704=head1 OPERATORS
705
706L<DOM::Tiny> overloads the following operators.
707
708=head2 array
709
710 my @nodes = @$dom;
711
712Alias for L</"child_nodes">.
713
714 # "<!-- Test -->"
715 $dom->parse('<!-- Test --><b>123</b>')->[0];
716
717=head2 bool
718
719 my $bool = !!$dom;
720
721Always true.
722
723=head2 hash
724
725 my %attrs = %$dom;
726
727Alias for L</"attr">.
728
729 # "test"
730 $dom->parse('<div id="test">Test</div>')->at('div')->{id};
731
732=head2 stringify
733
734 my $str = "$dom";
735
736Alias for L</"to_string">.
737
d6512b50 738=head1 METHODS
739
740L<DOM::Tiny> implements the following methods.
741
3793c28f 742=head2 new
743
744 my $dom = DOM::Tiny->new;
745 my $dom = DOM::Tiny->new('<foo bar="baz">I ♥ DOM::Tiny!</foo>');
746
747Construct a new scalar-based L<DOM::Tiny> object and L</"parse"> HTML/XML
748fragment if necessary.
749
d6512b50 750=head2 all_text
751
752 my $trimmed = $dom->all_text;
753 my $untrimmed = $dom->all_text(0);
754
755Extract text content from all descendant nodes of this element, smart
756whitespace trimming is enabled by default.
757
758 # "foo bar baz"
759 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text;
760
761 # "foo\nbarbaz\n"
762 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text(0);
763
764=head2 ancestors
765
766 my $collection = $dom->ancestors;
767 my $collection = $dom->ancestors('div ~ p');
768
769Find all ancestor elements of this node matching the CSS selector and return a
8563f527 770L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 771objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 772
773 # List tag names of ancestor elements
774 say $dom->ancestors->map('tag')->join("\n");
775
776=head2 append
777
778 $dom = $dom->append('<p>I ♥ DOM::Tiny!</p>');
779
780Append HTML/XML fragment to this node.
781
782 # "<div><h1>Test</h1><h2>123</h2></div>"
783 $dom->parse('<div><h1>Test</h1></div>')
784 ->at('h1')->append('<h2>123</h2>')->root;
785
786 # "<p>Test 123</p>"
787 $dom->parse('<p>Test</p>')->at('p')
788 ->child_nodes->first->append(' 123')->root;
789
790=head2 append_content
791
792 $dom = $dom->append_content('<p>I ♥ DOM::Tiny!</p>');
793
794Append HTML/XML fragment (for C<root> and C<tag> nodes) or raw content to this
795node's content.
796
797 # "<div><h1>Test123</h1></div>"
798 $dom->parse('<div><h1>Test</h1></div>')
799 ->at('h1')->append_content('123')->root;
800
801 # "<!-- Test 123 --><br>"
802 $dom->parse('<!-- Test --><br>')
803 ->child_nodes->first->append_content('123 ')->root;
804
805 # "<p>Test<i>123</i></p>"
806 $dom->parse('<p>Test</p>')->at('p')->append_content('<i>123</i>')->root;
807
808=head2 at
809
810 my $result = $dom->at('div ~ p');
811
812Find first descendant element of this element matching the CSS selector and
813return it as a L<DOM::Tiny> object or return C<undef> if none could be found.
9a5f1e3f 814All selectors listed in L</"SELECTORS"> are supported.
d6512b50 815
816 # Find first element with "svg" namespace definition
817 my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};
818
819=head2 attr
820
821 my $hash = $dom->attr;
822 my $foo = $dom->attr('foo');
823 $dom = $dom->attr({foo => 'bar'});
824 $dom = $dom->attr(foo => 'bar');
825
826This element's attributes.
827
828 # Remove an attribute
829 delete $dom->attr->{id};
830
831 # Attribute without value
832 $dom->attr(selected => undef);
833
834 # List id attributes
835 say $dom->find('*')->map(attr => 'id')->compact->join("\n");
836
837=head2 child_nodes
838
839 my $collection = $dom->child_nodes;
840
8563f527 841Return a L<collection|/"COLLECTION METHODS"> containing all child nodes of this
d6512b50 842element as L<DOM::Tiny> objects.
843
844 # "<p><b>123</b></p>"
845 $dom->parse('<p>Test<b>123</b></p>')->at('p')->child_nodes->first->remove;
846
847 # "<!DOCTYPE html>"
848 $dom->parse('<!DOCTYPE html><b>123</b>')->child_nodes->first;
849
850 # " Test "
851 $dom->parse('<b>123</b><!-- Test -->')->child_nodes->last->content;
852
853=head2 children
854
855 my $collection = $dom->children;
856 my $collection = $dom->children('div ~ p');
857
858Find all child elements of this element matching the CSS selector and return a
8563f527 859L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 860objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 861
862 # Show tag name of random child element
863 say $dom->children->shuffle->first->tag;
864
865=head2 content
866
867 my $str = $dom->content;
868 $dom = $dom->content('<p>I ♥ DOM::Tiny!</p>');
869
870Return this node's content or replace it with HTML/XML fragment (for C<root>
871and C<tag> nodes) or raw content.
872
873 # "<b>Test</b>"
874 $dom->parse('<div><b>Test</b></div>')->at('div')->content;
875
876 # "<div><h1>123</h1></div>"
877 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('123')->root;
878
879 # "<p><i>123</i></p>"
880 $dom->parse('<p>Test</p>')->at('p')->content('<i>123</i>')->root;
881
882 # "<div><h1></h1></div>"
883 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('')->root;
884
885 # " Test "
886 $dom->parse('<!-- Test --><br>')->child_nodes->first->content;
887
888 # "<div><!-- 123 -->456</div>"
889 $dom->parse('<div><!-- Test -->456</div>')
890 ->at('div')->child_nodes->first->content(' 123 ')->root;
891
892=head2 descendant_nodes
893
894 my $collection = $dom->descendant_nodes;
895
8563f527 896Return a L<collection|/"COLLECTION METHODS"> containing all descendant nodes of
d6512b50 897this element as L<DOM::Tiny> objects.
898
899 # "<p><b>123</b></p>"
900 $dom->parse('<p><!-- Test --><b>123<!-- 456 --></b></p>')
901 ->descendant_nodes->grep(sub { $_->type eq 'comment' })
902 ->map('remove')->first;
903
904 # "<p><b>test</b>test</p>"
905 $dom->parse('<p><b>123</b>456</p>')
906 ->at('p')->descendant_nodes->grep(sub { $_->type eq 'text' })
907 ->map(content => 'test')->first->root;
908
909=head2 find
910
911 my $collection = $dom->find('div ~ p');
912
913Find all descendant elements of this element matching the CSS selector and
8563f527 914return a L<collection|/"COLLECTION METHODS"> containing these elements as
9a5f1e3f 915L<DOM::Tiny> objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 916
917 # Find a specific element and extract information
918 my $id = $dom->find('div')->[23]{id};
919
920 # Extract information from multiple elements
921 my @headers = $dom->find('h1, h2, h3')->map('text')->each;
922
923 # Count all the different tags
924 my $hash = $dom->find('*')->reduce(sub { $a->{$b->tag}++; $a }, {});
925
926 # Find elements with a class that contains dots
927 my @divs = $dom->find('div.foo\.bar')->each;
928
929=head2 following
930
931 my $collection = $dom->following;
932 my $collection = $dom->following('div ~ p');
933
934Find all sibling elements after this node matching the CSS selector and return
8563f527 935a L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 936objects. All selectors listen in L</"SELECTORS"> are supported.
d6512b50 937
938 # List tags of sibling elements after this node
939 say $dom->following->map('tag')->join("\n");
940
941=head2 following_nodes
942
943 my $collection = $dom->following_nodes;
944
8563f527 945Return a L<collection|/"COLLECTION METHODS"> containing all sibling nodes after
d6512b50 946this node as L<DOM::Tiny> objects.
947
948 # "C"
949 $dom->parse('<p>A</p><!-- B -->C')->at('p')->following_nodes->last->content;
950
951=head2 matches
952
953 my $bool = $dom->matches('div ~ p');
954
9a5f1e3f 955Check if this element matches the CSS selector. All selectors listed in
956L</"SELECTORS"> are supported.
d6512b50 957
958 # True
959 $dom->parse('<p class="a">A</p>')->at('p')->matches('.a');
960 $dom->parse('<p class="a">A</p>')->at('p')->matches('p[class]');
961
962 # False
963 $dom->parse('<p class="a">A</p>')->at('p')->matches('.b');
964 $dom->parse('<p class="a">A</p>')->at('p')->matches('p[id]');
965
966=head2 namespace
967
968 my $namespace = $dom->namespace;
969
970Find this element's namespace or return C<undef> if none could be found.
971
972 # Find namespace for an element with namespace prefix
973 my $namespace = $dom->at('svg > svg\:circle')->namespace;
974
975 # Find namespace for an element that may or may not have a namespace prefix
976 my $namespace = $dom->at('svg > circle')->namespace;
977
d6512b50 978=head2 next
979
980 my $sibling = $dom->next;
981
982Return L<DOM::Tiny> object for next sibling element or C<undef> if there are no
983more siblings.
984
985 # "<h2>123</h2>"
986 $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;
987
988=head2 next_node
989
990 my $sibling = $dom->next_node;
991
992Return L<DOM::Tiny> object for next sibling node or C<undef> if there are no
993more siblings.
994
995 # "456"
996 $dom->parse('<p><b>123</b><!-- Test -->456</p>')
997 ->at('b')->next_node->next_node;
998
999 # " Test "
1000 $dom->parse('<p><b>123</b><!-- Test -->456</p>')
1001 ->at('b')->next_node->content;
1002
1003=head2 parent
1004
1005 my $parent = $dom->parent;
1006
1007Return L<DOM::Tiny> object for parent of this node or C<undef> if this node has
1008no parent.
1009
1010=head2 parse
1011
1012 $dom = $dom->parse('<foo bar="baz">I ♥ DOM::Tiny!</foo>');
1013
9a5f1e3f 1014Parse HTML/XML fragment.
d6512b50 1015
1016 # Parse XML
1017 my $dom = DOM::Tiny->new->xml(1)->parse($xml);
1018
1019=head2 preceding
1020
1021 my $collection = $dom->preceding;
1022 my $collection = $dom->preceding('div ~ p');
1023
1024Find all sibling elements before this node matching the CSS selector and return
8563f527 1025a L<collection|/"COLLECTION METHODS"> containing these elements as L<DOM::Tiny>
9a5f1e3f 1026objects. All selectors listed in L</"SELECTORS"> are supported.
d6512b50 1027
1028 # List tags of sibling elements before this node
1029 say $dom->preceding->map('tag')->join("\n");
1030
1031=head2 preceding_nodes
1032
1033 my $collection = $dom->preceding_nodes;
1034
8563f527 1035Return a L<collection|/"COLLECTION METHODS"> containing all sibling nodes
1036before this node as L<DOM::Tiny> objects.
d6512b50 1037
1038 # "A"
1039 $dom->parse('A<!-- B --><p>C</p>')->at('p')->preceding_nodes->first->content;
1040
1041=head2 prepend
1042
1043 $dom = $dom->prepend('<p>I ♥ DOM::Tiny!</p>');
1044
1045Prepend HTML/XML fragment to this node.
1046
1047 # "<div><h1>Test</h1><h2>123</h2></div>"
1048 $dom->parse('<div><h2>123</h2></div>')
1049 ->at('h2')->prepend('<h1>Test</h1>')->root;
1050
1051 # "<p>Test 123</p>"
1052 $dom->parse('<p>123</p>')
1053 ->at('p')->child_nodes->first->prepend('Test ')->root;
1054
1055=head2 prepend_content
1056
1057 $dom = $dom->prepend_content('<p>I ♥ DOM::Tiny!</p>');
1058
1059Prepend HTML/XML fragment (for C<root> and C<tag> nodes) or raw content to this
1060node's content.
1061
1062 # "<div><h2>Test123</h2></div>"
1063 $dom->parse('<div><h2>123</h2></div>')
1064 ->at('h2')->prepend_content('Test')->root;
1065
1066 # "<!-- Test 123 --><br>"
1067 $dom->parse('<!-- 123 --><br>')
1068 ->child_nodes->first->prepend_content(' Test')->root;
1069
1070 # "<p><i>123</i>Test</p>"
1071 $dom->parse('<p>Test</p>')->at('p')->prepend_content('<i>123</i>')->root;
1072
1073=head2 previous
1074
1075 my $sibling = $dom->previous;
1076
1077Return L<DOM::Tiny> object for previous sibling element or C<undef> if there
1078are no more siblings.
1079
1080 # "<h1>Test</h1>"
1081 $dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h2')->previous;
1082
1083=head2 previous_node
1084
1085 my $sibling = $dom->previous_node;
1086
1087Return L<DOM::Tiny> object for previous sibling node or C<undef> if there are
1088no more siblings.
1089
1090 # "123"
1091 $dom->parse('<p>123<!-- Test --><b>456</b></p>')
1092 ->at('b')->previous_node->previous_node;
1093
1094 # " Test "
1095 $dom->parse('<p>123<!-- Test --><b>456</b></p>')
1096 ->at('b')->previous_node->content;
1097
1098=head2 remove
1099
1100 my $parent = $dom->remove;
1101
1102Remove this node and return L</"root"> (for C<root> nodes) or L</"parent">.
1103
1104 # "<div></div>"
1105 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->remove;
1106
1107 # "<p><b>456</b></p>"
1108 $dom->parse('<p>123<b>456</b></p>')
1109 ->at('p')->child_nodes->first->remove->root;
1110
1111=head2 replace
1112
1113 my $parent = $dom->replace('<div>I ♥ DOM::Tiny!</div>');
1114
1115Replace this node with HTML/XML fragment and return L</"root"> (for C<root>
1116nodes) or L</"parent">.
1117
1118 # "<div><h2>123</h2></div>"
1119 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->replace('<h2>123</h2>');
1120
1121 # "<p><b>123</b></p>"
1122 $dom->parse('<p>Test</p>')
1123 ->at('p')->child_nodes->[0]->replace('<b>123</b>')->root;
1124
1125=head2 root
1126
1127 my $root = $dom->root;
1128
1129Return L<DOM::Tiny> object for C<root> node.
1130
1131=head2 strip
1132
1133 my $parent = $dom->strip;
1134
1135Remove this element while preserving its content and return L</"parent">.
1136
1137 # "<div>Test</div>"
1138 $dom->parse('<div><h1>Test</h1></div>')->at('h1')->strip;
1139
1140=head2 tag
1141
1142 my $tag = $dom->tag;
1143 $dom = $dom->tag('div');
1144
1145This element's tag name.
1146
1147 # List tag names of child elements
1148 say $dom->children->map('tag')->join("\n");
1149
1150=head2 tap
1151
1152 $dom = $dom->tap(sub {...});
1153
e99ef07d 1154Equivalent to L<Mojo::Base/"tap">.
d6512b50 1155
1156=head2 text
1157
1158 my $trimmed = $dom->text;
1159 my $untrimmed = $dom->text(0);
1160
1161Extract text content from this element only (not including child elements),
1162smart whitespace trimming is enabled by default.
1163
1164 # "foo baz"
1165 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text;
1166
1167 # "foo\nbaz\n"
1168 $dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text(0);
1169
1170=head2 to_string
1171
1172 my $str = $dom->to_string;
1173
1174Render this node and its content to HTML/XML.
1175
1176 # "<b>Test</b>"
1177 $dom->parse('<div><b>Test</b></div>')->at('div b')->to_string;
1178
1179=head2 tree
1180
1181 my $tree = $dom->tree;
1182 $dom = $dom->tree(['root']);
1183
1184Document Object Model. Note that this structure should only be used very
1185carefully since it is very dynamic.
1186
1187=head2 type
1188
1189 my $type = $dom->type;
1190
1191This node's type, usually C<cdata>, C<comment>, C<doctype>, C<pi>, C<raw>,
1192C<root>, C<tag> or C<text>.
1193
1194 # "cdata"
1195 $dom->parse('<![CDATA[Test]]>')->child_nodes->first->type;
1196
1197 # "comment"
1198 $dom->parse('<!-- Test -->')->child_nodes->first->type;
1199
1200 # "doctype"
1201 $dom->parse('<!DOCTYPE html>')->child_nodes->first->type;
1202
1203 # "pi"
1204 $dom->parse('<?xml version="1.0"?>')->child_nodes->first->type;
1205
1206 # "raw"
1207 $dom->parse('<title>Test</title>')->at('title')->child_nodes->first->type;
1208
1209 # "root"
1210 $dom->parse('<p>Test</p>')->type;
1211
1212 # "tag"
1213 $dom->parse('<p>Test</p>')->at('p')->type;
1214
1215 # "text"
1216 $dom->parse('<p>Test</p>')->at('p')->child_nodes->first->type;
1217
1218=head2 val
1219
1220 my $value = $dom->val;
1221
1222Extract value from form element (such as C<button>, C<input>, C<option>,
1223C<select> and C<textarea>) or return C<undef> if this element has no value. In
1224the case of C<select> with C<multiple> attribute, find C<option> elements with
1225C<selected> attribute and return an array reference with all values or C<undef>
1226if none could be found.
1227
1228 # "a"
1229 $dom->parse('<input name="test" value="a">')->at('input')->val;
1230
1231 # "b"
1232 $dom->parse('<textarea>b</textarea>')->at('textarea')->val;
1233
1234 # "c"
1235 $dom->parse('<option value="c">Test</option>')->at('option')->val;
1236
1237 # "d"
1238 $dom->parse('<select><option selected>d</option></select>')
1239 ->at('select')->val;
1240
1241 # "e"
1242 $dom->parse('<select multiple><option selected>e</option></select>')
1243 ->at('select')->val->[0];
1244
1245=head2 wrap
1246
1247 $dom = $dom->wrap('<div></div>');
1248
1249Wrap HTML/XML fragment around this node, placing it as the last child of the
1250first innermost element.
1251
1252 # "<p>123<b>Test</b></p>"
1253 $dom->parse('<b>Test</b>')->at('b')->wrap('<p>123</p>')->root;
1254
1255 # "<div><p><b>Test</b></p>123</div>"
1256 $dom->parse('<b>Test</b>')->at('b')->wrap('<div><p></p>123</div>')->root;
1257
1258 # "<p><b>Test</b></p><p>123</p>"
1259 $dom->parse('<b>Test</b>')->at('b')->wrap('<p></p><p>123</p>')->root;
1260
1261 # "<p><b>Test</b></p>"
1262 $dom->parse('<p>Test</p>')->at('p')->child_nodes->first->wrap('<b>')->root;
1263
1264=head2 wrap_content
1265
1266 $dom = $dom->wrap_content('<div></div>');
1267
1268Wrap HTML/XML fragment around this node's content, placing it as the last
1269children of the first innermost element.
1270
1271 # "<p><b>123Test</b></p>"
1272 $dom->parse('<p>Test<p>')->at('p')->wrap_content('<b>123</b>')->root;
1273
1274 # "<p><b>Test</b></p><p>123</p>"
1275 $dom->parse('<b>Test</b>')->wrap_content('<p></p><p>123</p>');
1276
1277=head2 xml
1278
1279 my $bool = $dom->xml;
1280 $dom = $dom->xml($bool);
1281
1282Disable HTML semantics in parser and activate case-sensitivity, defaults to
1283auto detection based on processing instructions.
1284
78ba4051 1285=head1 COLLECTION METHODS
1286
9a5f1e3f 1287Some L<DOM::Tiny> methods return an array-based collection object based on
1288L<Mojo::Collection>, which can either be accessed directly as an array
1289reference, or with the following methods.
78ba4051 1290
1291 # Chain methods
1292 $collection->map(sub { ucfirst })->shuffle->each(sub {
1293 my ($word, $num) = @_;
1294 say "$num: $word";
1295 });
1296
1297 # Access array directly to manipulate collection
1298 $collection->[23] += 100;
1299 say for @$collection;
1300
1301=head2 compact
1302
1303 my $new = $collection->compact;
1304
1305Create a new collection with all elements that are defined and not an empty
1306string.
1307
1308 # $collection contains (0, 1, undef, 2, '', 3)
1309 $collection->compact->join(', '); # "0, 1, 2, 3"
1310
1311=head2 each
1312
1313 my @elements = $collection->each;
1314 $collection = $collection->each(sub {...});
1315
1316Evaluate callback for each element in collection or return all elements as a
1317list if none has been provided. The element will be the first argument passed
1318to the callback and is also available as C<$_>.
1319
1320 # Make a numbered list
1321 $collection->each(sub {
1322 my ($e, $num) = @_;
1323 say "$num: $e";
1324 });
1325
1326=head2 first
1327
1328 my $first = $collection->first;
1329 my $first = $collection->first(qr/foo/);
1330 my $first = $collection->first(sub {...});
1331 my $first = $collection->first($method);
1332 my $first = $collection->first($method, @args);
1333
1334Evaluate regular expression/callback for, or call method on, each element in
1335collection and return the first one that matched the regular expression, or for
1336which the callback/method returned true. The element will be the first argument
1337passed to the callback and is also available as C<$_>.
1338
1339 # Longer version
1340 my $first = $collection->first(sub { $_->$method(@args) });
1341
c7bc4d31 1342 # Find first value that contains the word "tiny"
1343 my $interesting = $collection->first(qr/tiny/i);
78ba4051 1344
1345 # Find first value that is greater than 5
1346 my $greater = $collection->first(sub { $_ > 5 });
1347
1348=head2 flatten
1349
1350 my $new = $collection->flatten;
1351
1352Flatten nested collections/arrays recursively and create a new collection with
1353all elements.
1354
1355 # $collection contains (1, [2, [3, 4], 5, [6]], 7)
1356 $collection->flatten->join(', '); # "1, 2, 3, 4, 5, 6, 7"
1357
1358=head2 grep
1359
1360 my $new = $collection->grep(qr/foo/);
1361 my $new = $collection->grep(sub {...});
1362 my $new = $collection->grep($method);
1363 my $new = $collection->grep($method, @args);
1364
1365Evaluate regular expression/callback for, or call method on, each element in
1366collection and create a new collection with all elements that matched the
1367regular expression, or for which the callback/method returned true. The element
1368will be the first argument passed to the callback and is also available as
1369C<$_>.
1370
1371 # Longer version
1372 my $new = $collection->grep(sub { $_->$method(@args) });
1373
c7bc4d31 1374 # Find all values that contain the word "tiny"
1375 my $interesting = $collection->grep(qr/tiny/i);
78ba4051 1376
1377 # Find all values that are greater than 5
1378 my $greater = $collection->grep(sub { $_ > 5 });
1379
1380=head2 join
1381
1382 my $stream = $collection->join;
1383 my $stream = $collection->join("\n");
1384
1385Turn collection into string.
1386
1387 # Join all values with commas
1388 $collection->join(', ');
1389
1390=head2 last
1391
1392 my $last = $collection->last;
1393
1394Return the last element in collection.
1395
1396=head2 map
1397
1398 my $new = $collection->map(sub {...});
1399 my $new = $collection->map($method);
1400 my $new = $collection->map($method, @args);
1401
1402Evaluate callback for, or call method on, each element in collection and create
1403a new collection from the results. The element will be the first argument
1404passed to the callback and is also available as C<$_>.
1405
1406 # Longer version
1407 my $new = $collection->map(sub { $_->$method(@args) });
1408
c7bc4d31 1409 # Append the word "tiny" to all values
1410 my $domified = $collection->map(sub { $_ . 'tiny' });
78ba4051 1411
1412=head2 reduce
1413
1414 my $result = $collection->reduce(sub {...});
1415 my $result = $collection->reduce(sub {...}, $initial);
1416
1417Reduce elements in collection with callback, the first element will be used as
1418initial value if none has been provided.
1419
1420 # Calculate the sum of all values
1421 my $sum = $collection->reduce(sub { $a + $b });
1422
1423 # Count how often each value occurs in collection
1424 my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {});
1425
1426=head2 reverse
1427
1428 my $new = $collection->reverse;
1429
1430Create a new collection with all elements in reverse order.
1431
1432=head2 slice
1433
1434 my $new = $collection->slice(4 .. 7);
1435
1436Create a new collection with all selected elements.
1437
1438 # $collection contains ('A', 'B', 'C', 'D', 'E')
1439 $collection->slice(1, 2, 4)->join(' '); # "B C E"
1440
1441=head2 shuffle
1442
1443 my $new = $collection->shuffle;
1444
1445Create a new collection with all elements in random order.
1446
1447=head2 size
1448
1449 my $size = $collection->size;
1450
1451Number of elements in collection.
1452
1453=head2 sort
1454
1455 my $new = $collection->sort;
1456 my $new = $collection->sort(sub {...});
1457
1458Sort elements based on return value of callback and create a new collection
1459from the results.
1460
1461 # Sort values case-insensitive
1462 my $case_insensitive = $collection->sort(sub { uc($a) cmp uc($b) });
1463
1464=head2 tap
1465
1466 $collection = $collection->tap(sub {...});
1467
1468Equivalent to L<Mojo::Base/"tap">.
1469
1470=head2 to_array
1471
1472 my $array = $collection->to_array;
1473
1474Turn collection into array reference.
1475
1476=head2 uniq
1477
1478 my $new = $collection->uniq;
1479 my $new = $collection->uniq(sub {...});
1480 my $new = $collection->uniq($method);
1481 my $new = $collection->uniq($method, @args);
1482
1483Create a new collection without duplicate elements, using the string
1484representation of either the elements or the return value of the
1485callback/method.
1486
1487 # Longer version
1488 my $new = $collection->uniq(sub { $_->$method(@args) });
1489
1490 # $collection contains ('foo', 'bar', 'bar', 'baz')
1491 $collection->uniq->join(' '); # "foo bar baz"
1492
1493 # $collection contains ([1, 2], [2, 1], [3, 2])
1494 $collection->uniq(sub{ $_->[1] })->to_array; # "[[1, 2], [2, 1]]"
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
31877452 1514L<Mojo::DOM>, L<HTML::TreeBuilder>, L<XML::LibXML>, L<XML::Twig>, L<XML::Smart>
9a5f1e3f 1515
1516=for Pod::Coverage TO_JSON
1517
1518=cut