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