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