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