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