| 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 |