| blib/lib/Mojo/DOM58.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 234 | 234 | 100.0 |
| branch | 123 | 126 | 97.6 |
| condition | 59 | 66 | 89.3 |
| subroutine | 78 | 78 | 100.0 |
| pod | 43 | 44 | 97.7 |
| total | 537 | 548 | 97.9 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Mojo::DOM58; | ||||||
| 2 | |||||||
| 3 | 2 | 2 | 140385 | use strict; | |||
| 2 | 13 | ||||||
| 2 | 62 | ||||||
| 4 | 2 | 2 | 11 | use warnings; | |||
| 2 | 4 | ||||||
| 2 | 231 | ||||||
| 5 | |||||||
| 6 | use overload | ||||||
| 7 | 4 | 4 | 13 | '@{}' => sub { shift->child_nodes }, | |||
| 8 | 95 | 95 | 241 | '%{}' => sub { shift->attr }, | |||
| 9 | 32 | 32 | 3876 | bool => sub {1}, | |||
| 10 | 129 | 129 | 16226 | '""' => sub { shift->to_string }, | |||
| 11 | 2 | 2 | 2508 | fallback => 1; | |||
| 2 | 2081 | ||||||
| 2 | 24 | ||||||
| 12 | |||||||
| 13 | 2 | 2 | 217 | use Exporter 'import'; | |||
| 2 | 4 | ||||||
| 2 | 58 | ||||||
| 14 | 2 | 2 | 435 | use Mojo::DOM58::_Collection; | |||
| 2 | 5 | ||||||
| 2 | 67 | ||||||
| 15 | 2 | 2 | 1036 | use Mojo::DOM58::_CSS; | |||
| 2 | 6 | ||||||
| 2 | 80 | ||||||
| 16 | 2 | 2 | 982 | use Mojo::DOM58::_HTML 'tag_to_html'; | |||
| 2 | 6 | ||||||
| 2 | 148 | ||||||
| 17 | 2 | 2 | 15 | use Scalar::Util qw(blessed weaken); | |||
| 2 | 4 | ||||||
| 2 | 99 | ||||||
| 18 | 2 | 2 | 1615 | use Storable 'dclone'; | |||
| 2 | 6527 | ||||||
| 2 | 8074 | ||||||
| 19 | |||||||
| 20 | our $VERSION = '3.000'; | ||||||
| 21 | |||||||
| 22 | our @EXPORT_OK = 'tag_to_html'; | ||||||
| 23 | |||||||
| 24 | sub new { | ||||||
| 25 | 2128 | 2128 | 1 | 344339 | my $class = shift; | ||
| 26 | 2128 | 66 | 5373 | my $self = bless \Mojo::DOM58::_HTML->new, ref $class || $class; | |||
| 27 | 2128 | 100 | 6281 | return @_ ? $self->parse(@_) : $self; | |||
| 28 | } | ||||||
| 29 | |||||||
| 30 | sub new_tag { | ||||||
| 31 | 11 | 11 | 1 | 2924 | my $self = shift; | ||
| 32 | 11 | 28 | my $new = $self->new; | ||||
| 33 | 11 | 46 | $$new->tag(@_); | ||||
| 34 | 11 | 100 | 35 | $$new->xml($$self->xml) if ref $self; | |||
| 35 | 11 | 37 | return $new; | ||||
| 36 | } | ||||||
| 37 | |||||||
| 38 | 1 | 1 | 0 | 92 | sub TO_JSON { ${shift()}->render } | ||
| 1 | 6 | ||||||
| 39 | |||||||
| 40 | 25 | 25 | 1 | 82 | sub all_text { _text(_nodes($_[0]->tree), $_[0]->xml, 1) } | ||
| 41 | |||||||
| 42 | 15 | 15 | 1 | 53 | sub ancestors { _select($_[0]->_collect([_ancestors($_[0]->tree)]), $_[1]) } | ||
| 43 | |||||||
| 44 | 9 | 9 | 1 | 40 | sub append { shift->_add(1, @_) } | ||
| 45 | 13 | 13 | 1 | 45 | sub append_content { shift->_content(1, 0, @_) } | ||
| 46 | |||||||
| 47 | sub at { | ||||||
| 48 | 612 | 612 | 1 | 2064 | my $self = shift; | ||
| 49 | 612 | 100 | 1719 | return undef unless my $result = $self->_css->select_one(@_); | |||
| 50 | 564 | 3357 | return $self->_build($result, $self->xml); | ||||
| 51 | } | ||||||
| 52 | |||||||
| 53 | sub attr { | ||||||
| 54 | 168 | 168 | 1 | 285 | my $self = shift; | ||
| 55 | |||||||
| 56 | # Hash | ||||||
| 57 | 168 | 331 | my $tree = $self->tree; | ||||
| 58 | 168 | 100 | 438 | my $attrs = $tree->[0] ne 'tag' ? {} : $tree->[2]; | |||
| 59 | 168 | 100 | 859 | return $attrs unless @_; | |||
| 60 | |||||||
| 61 | # Get | ||||||
| 62 | 40 | 100 | 100 | 268 | return $attrs->{$_[0]} unless @_ > 1 || ref $_[0]; | ||
| 63 | |||||||
| 64 | # Set | ||||||
| 65 | 4 | 100 | 15 | my $values = ref $_[0] ? $_[0] : {@_}; | |||
| 66 | 4 | 18 | @$attrs{keys %$values} = values %$values; | ||||
| 67 | |||||||
| 68 | 4 | 17 | return $self; | ||||
| 69 | } | ||||||
| 70 | |||||||
| 71 | 59 | 59 | 1 | 152 | sub child_nodes { $_[0]->_collect(_nodes($_[0]->tree)) } | ||
| 72 | |||||||
| 73 | 13 | 13 | 1 | 61 | sub children { _select($_[0]->_collect(_nodes($_[0]->tree, 1)), $_[1]) } | ||
| 74 | |||||||
| 75 | sub content { | ||||||
| 76 | 55 | 55 | 1 | 107 | my $self = shift; | ||
| 77 | |||||||
| 78 | 55 | 132 | my $type = $self->type; | ||||
| 79 | 55 | 100 | 100 | 239 | if ($type eq 'root' || $type eq 'tag') { | ||
| 80 | 24 | 100 | 89 | return $self->_content(0, 1, @_) if @_; | |||
| 81 | 7 | 15 | my $html = Mojo::DOM58::_HTML->new(xml => $self->xml); | ||||
| 82 | 7 | 19 | return join '', map { $html->tree($_)->render } @{_nodes($self->tree)}; | ||||
| 12 | 28 | ||||||
| 7 | 15 | ||||||
| 83 | } | ||||||
| 84 | |||||||
| 85 | 31 | 100 | 80 | return $self->tree->[1] unless @_; | |||
| 86 | 3 | 9 | $self->tree->[1] = shift; | ||||
| 87 | 3 | 11 | return $self; | ||||
| 88 | } | ||||||
| 89 | |||||||
| 90 | 13 | 13 | 1 | 37 | sub descendant_nodes { $_[0]->_collect(_all(_nodes($_[0]->tree))) } | ||
| 91 | |||||||
| 92 | sub find { | ||||||
| 93 | 427 | 427 | 1 | 2767 | my $self = shift; | ||
| 94 | 427 | 1205 | return $self->_collect($self->_css->select(@_)); | ||||
| 95 | } | ||||||
| 96 | |||||||
| 97 | 8 | 8 | 1 | 28 | sub following { _select($_[0]->_collect(_siblings($_[0]->tree, 1, 1)), $_[1]) } | ||
| 98 | 7 | 7 | 1 | 21 | sub following_nodes { $_[0]->_collect(_siblings($_[0]->tree, 0, 1)) } | ||
| 99 | |||||||
| 100 | 44 | 44 | 1 | 106 | sub matches { shift->_css->matches(@_) } | ||
| 101 | |||||||
| 102 | sub namespace { | ||||||
| 103 | 18 | 18 | 1 | 40 | my $self = shift; | ||
| 104 | |||||||
| 105 | 18 | 100 | 39 | return undef if (my $tree = $self->tree)->[0] ne 'tag'; | |||
| 106 | |||||||
| 107 | # Extract namespace prefix and search parents | ||||||
| 108 | 16 | 100 | 188 | my $ns = $tree->[1] =~ /^(.*?):/ ? "xmlns:$1" : undef; | |||
| 109 | 16 | 43 | for my $node ($tree, _ancestors($tree)) { | ||||
| 110 | |||||||
| 111 | # Namespace for prefix | ||||||
| 112 | 35 | 50 | my $attrs = $node->[2]; | ||||
| 113 | 35 | 100 | 100 | 87 | if ($ns) { $_ eq $ns and return $attrs->{$_} for keys %$attrs } | ||
| 13 | 100 | 75 | |||||
| 114 | |||||||
| 115 | # Namespace attribute | ||||||
| 116 | 10 | 54 | elsif (defined $attrs->{xmlns}) { return $attrs->{xmlns} } | ||||
| 117 | } | ||||||
| 118 | |||||||
| 119 | 1 | 5 | return undef; | ||||
| 120 | } | ||||||
| 121 | |||||||
| 122 | 13 | 13 | 1 | 32 | sub next { $_[0]->_maybe(_siblings($_[0]->tree, 1, 1, 0)) } | ||
| 123 | 5 | 5 | 1 | 13 | sub next_node { $_[0]->_maybe(_siblings($_[0]->tree, 0, 1, 0)) } | ||
| 124 | |||||||
| 125 | sub parent { | ||||||
| 126 | 48 | 48 | 1 | 93 | my $self = shift; | ||
| 127 | 48 | 50 | 98 | return undef if (my $tree = $self->tree)->[0] eq 'root'; | |||
| 128 | 48 | 119 | return $self->_build(_parent($tree), $self->xml); | ||||
| 129 | } | ||||||
| 130 | |||||||
| 131 | 166 | 50 | 166 | 1 | 401 | sub parse { ${$_[0]}->parse($_[1]) and return $_[0] } | |
| 166 | 879 | ||||||
| 132 | |||||||
| 133 | 5 | 5 | 1 | 14 | 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 | 37 | sub prepend { shift->_add(0, @_) } | ||
| 137 | 6 | 6 | 1 | 22 | sub prepend_content { shift->_content(0, 0, @_) } | ||
| 138 | |||||||
| 139 | 7 | 7 | 1 | 22 | sub previous { $_[0]->_maybe(_siblings($_[0]->tree, 1, 0, -1)) } | ||
| 140 | 5 | 5 | 1 | 13 | sub previous_node { $_[0]->_maybe(_siblings($_[0]->tree, 0, 0, -1)) } | ||
| 141 | |||||||
| 142 | 6 | 6 | 1 | 21 | sub remove { shift->replace('') } | ||
| 143 | |||||||
| 144 | sub replace { | ||||||
| 145 | 24 | 24 | 1 | 65 | my ($self, $new) = @_; | ||
| 146 | 24 | 100 | 51 | return $self->parse($new) if (my $tree = $self->tree)->[0] eq 'root'; | |||
| 147 | 16 | 50 | return $self->_replace(_parent($tree), $tree, _nodes($self->_parse($new))); | ||||
| 148 | } | ||||||
| 149 | |||||||
| 150 | sub root { | ||||||
| 151 | 12 | 12 | 1 | 28 | my $self = shift; | ||
| 152 | 12 | 100 | 28 | return $self unless my $tree = _ancestors($self->tree, 1); | |||
| 153 | 9 | 41 | return $self->_build($tree, $self->xml); | ||||
| 154 | } | ||||||
| 155 | |||||||
| 156 | sub selector { | ||||||
| 157 | 13 | 100 | 13 | 1 | 39 | return undef unless (my $tree = shift->tree)->[0] eq 'tag'; | |
| 158 | return join ' > ', | ||||||
| 159 | 11 | 47 | reverse map { $_->[1] . ':nth-child(' . (@{_siblings($_, 1)} + 1) . ')' } | ||||
| 31 | 68 | ||||||
| 31 | 73 | ||||||
| 160 | $tree, _ancestors($tree); | ||||||
| 161 | } | ||||||
| 162 | |||||||
| 163 | sub strip { | ||||||
| 164 | 9 | 9 | 1 | 18 | my $self = shift; | ||
| 165 | 9 | 100 | 21 | return $self if (my $tree = $self->tree)->[0] ne 'tag'; | |||
| 166 | 7 | 25 | return $self->_replace($tree->[3], $tree, _nodes($tree)); | ||||
| 167 | } | ||||||
| 168 | |||||||
| 169 | sub tag { | ||||||
| 170 | 96 | 96 | 1 | 227 | my ($self, $tag) = @_; | ||
| 171 | 96 | 100 | 178 | return undef if (my $tree = $self->tree)->[0] ne 'tag'; | |||
| 172 | 94 | 100 | 480 | return $tree->[1] unless $tag; | |||
| 173 | 1 | 5 | $tree->[1] = $tag; | ||||
| 174 | 1 | 4 | return $self; | ||||
| 175 | } | ||||||
| 176 | |||||||
| 177 | 1 | 1 | 1 | 7 | sub tap { Mojo::DOM58::_Collection::tap(@_) } | ||
| 178 | |||||||
| 179 | 722 | 722 | 1 | 1933 | sub text { _text(_nodes(shift->tree), 0, 0) } | ||
| 180 | |||||||
| 181 | 139 | 139 | 1 | 237 | sub to_string { ${shift()}->render } | ||
| 139 | 564 | ||||||
| 182 | |||||||
| 183 | 4596 | 100 | 50 | 4596 | 1 | 8886 | sub tree { @_ > 1 ? (${$_[0]}->tree($_[1]) and return $_[0]) : ${$_[0]}->tree } |
| 2641 | 8807 | ||||||
| 184 | |||||||
| 185 | 78 | 78 | 1 | 153 | sub type { shift->tree->[0] } | ||
| 186 | |||||||
| 187 | sub val { | ||||||
| 188 | 27 | 27 | 1 | 42 | my $self = shift; | ||
| 189 | |||||||
| 190 | # "option" | ||||||
| 191 | 27 | 100 | 59 | 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 | 50 | my $type = $self->{type} || ''; | |||
| 196 | 17 | 100 | 100 | 69 | 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 | 14 | ->grep(sub { !$_->ancestors('optgroup[disabled]')->size })->map('val'); | |||
| 6 | 17 | ||||||
| 205 | 5 | 100 | 33 | return exists $self->{multiple} ? $v->size ? $v->to_array : undef : $v->last; | |||
| 100 | |||||||
| 206 | } | ||||||
| 207 | |||||||
| 208 | 1 | 1 | 1 | 1289 | sub with_roles { Mojo::DOM58::_Collection::with_roles(@_) } | ||
| 209 | |||||||
| 210 | 9 | 9 | 1 | 38 | sub wrap { shift->_wrap(0, @_) } | ||
| 211 | 7 | 7 | 1 | 27 | sub wrap_content { shift->_wrap(1, @_) } | ||
| 212 | |||||||
| 213 | 3275 | 100 | 50 | 3275 | 1 | 6796 | sub xml { @_ > 1 ? (${$_[0]}->xml($_[1]) and return $_[0]) : ${$_[0]}->xml } |
| 1309 | 4419 | ||||||
| 214 | |||||||
| 215 | sub _add { | ||||||
| 216 | 20 | 20 | 51 | my ($self, $offset, $new) = @_; | |||
| 217 | |||||||
| 218 | 20 | 100 | 46 | return $self if (my $tree = $self->tree)->[0] eq 'root'; | |||
| 219 | |||||||
| 220 | 16 | 55 | my $parent = _parent($tree); | ||||
| 221 | splice @$parent, _offset($parent, $tree) + $offset, 0, | ||||||
| 222 | 16 | 48 | @{_link($parent, _nodes($self->_parse($new)))}; | ||||
| 16 | 51 | ||||||
| 223 | |||||||
| 224 | 16 | 85 | return $self; | ||||
| 225 | } | ||||||
| 226 | |||||||
| 227 | sub _all { | ||||||
| 228 | 21 | 21 | 31 | my $nodes = shift; | |||
| 229 | 21 | 100 | 38 | @$nodes = map { $_->[0] eq 'tag' ? ($_, @{_all(_nodes($_))}) : ($_) } @$nodes; | |||
| 60 | 125 | ||||||
| 8 | 13 | ||||||
| 230 | 21 | 52 | return $nodes; | ||||
| 231 | } | ||||||
| 232 | |||||||
| 233 | sub _ancestors { | ||||||
| 234 | 54 | 54 | 136 | my ($tree, $root) = @_; | |||
| 235 | |||||||
| 236 | 54 | 100 | 131 | return () unless $tree = _parent($tree); | |||
| 237 | 51 | 92 | my @ancestors; | ||||
| 238 | 51 | 66 | 84 | do { push @ancestors, $tree } | |||
| 137 | 488 | ||||||
| 239 | while ($tree->[0] eq 'tag') && ($tree = $tree->[3]); | ||||||
| 240 | 51 | 100 | 591 | return $root ? $ancestors[-1] : @ancestors[0 .. $#ancestors - 1]; | |||
| 241 | } | ||||||
| 242 | |||||||
| 243 | 1955 | 1955 | 4217 | sub _build { shift->new->tree(shift)->xml(shift) } | |||
| 244 | |||||||
| 245 | sub _collect { | ||||||
| 246 | 553 | 50 | 553 | 1760 | my ($self, $nodes) = (shift, shift || []); | ||
| 247 | 553 | 1251 | my $xml = $self->xml; | ||||
| 248 | 553 | 1302 | return Mojo::DOM58::_Collection->new(map { $self->_build($_, $xml) } @$nodes); | ||||
| 1314 | 2460 | ||||||
| 249 | } | ||||||
| 250 | |||||||
| 251 | sub _content { | ||||||
| 252 | 36 | 36 | 89 | my ($self, $start, $offset, $new) = @_; | |||
| 253 | |||||||
| 254 | 36 | 80 | my $tree = $self->tree; | ||||
| 255 | 36 | 100 | 100 | 171 | unless ($tree->[0] eq 'root' || $tree->[0] eq 'tag') { | ||
| 256 | 2 | 7 | my $old = $self->content; | ||||
| 257 | 2 | 100 | 10 | return $self->content($start ? $old . $new : $new . $old); | |||
| 258 | } | ||||||
| 259 | |||||||
| 260 | 34 | 100 | 116 | $start = $start ? ($#$tree + 1) : _start($tree); | |||
| 261 | 34 | 100 | 81 | $offset = $offset ? $#$tree : 0; | |||
| 262 | 34 | 57 | splice @$tree, $start, $offset, @{_link($tree, _nodes($self->_parse($new)))}; | ||||
| 34 | 89 | ||||||
| 263 | |||||||
| 264 | 34 | 156 | return $self; | ||||
| 265 | } | ||||||
| 266 | |||||||
| 267 | 1083 | 1083 | 2412 | sub _css { Mojo::DOM58::_CSS->new(tree => shift->tree) } | |||
| 268 | |||||||
| 269 | 1 | 1 | 9 | sub _fragment { _link(my $r = ['root', @_], [@_]); $r } | |||
| 1 | 5 | ||||||
| 270 | |||||||
| 271 | sub _link { | ||||||
| 272 | 98 | 98 | 210 | my ($parent, $children) = @_; | |||
| 273 | |||||||
| 274 | # Link parent to children | ||||||
| 275 | 98 | 187 | for my $node (@$children) { | ||||
| 276 | 102 | 100 | 232 | my $offset = $node->[0] eq 'tag' ? 3 : 2; | |||
| 277 | 102 | 196 | $node->[$offset] = $parent; | ||||
| 278 | 102 | 293 | weaken $node->[$offset]; | ||||
| 279 | } | ||||||
| 280 | |||||||
| 281 | 98 | 321 | return $children; | ||||
| 282 | } | ||||||
| 283 | |||||||
| 284 | 30 | 100 | 30 | 99 | sub _maybe { $_[1] ? $_[0]->_build($_[1], $_[0]->xml) : undef } | ||
| 285 | |||||||
| 286 | sub _nodes { | ||||||
| 287 | 1191 | 50 | 1191 | 2625 | return () unless my $tree = shift; | ||
| 288 | 1191 | 2628 | my @nodes = @$tree[_start($tree) .. $#$tree]; | ||||
| 289 | 1191 | 100 | 3667 | return shift() ? [grep { $_->[0] eq 'tag' } @nodes] : \@nodes; | |||
| 84 | 308 | ||||||
| 290 | } | ||||||
| 291 | |||||||
| 292 | sub _offset { | ||||||
| 293 | 46 | 46 | 100 | my ($parent, $child) = @_; | |||
| 294 | 46 | 97 | my $i = _start($parent); | ||||
| 295 | 46 | 100 | 300 | $_ eq $child ? last : $i++ for @$parent[$i .. $#$parent]; | |||
| 296 | 46 | 118 | return $i; | ||||
| 297 | } | ||||||
| 298 | |||||||
| 299 | 223 | 100 | 223 | 779 | sub _parent { $_[0]->[$_[0][0] eq 'tag' ? 3 : 2] } | ||
| 300 | |||||||
| 301 | sub _parse { | ||||||
| 302 | 80 | 80 | 187 | my ($self, $input) = @_; | |||
| 303 | 80 | 100 | 66 | 489 | return Mojo::DOM58::_HTML->new(xml => $self->xml)->parse($input)->tree | ||
| 304 | unless blessed $input && $input->isa('Mojo::DOM58'); | ||||||
| 305 | 21 | 117 | my $tree = dclone $input->tree; | ||||
| 306 | 21 | 100 | 132 | return $tree->[0] eq 'root' ? $tree : _fragment($tree); | |||
| 307 | } | ||||||
| 308 | |||||||
| 309 | sub _replace { | ||||||
| 310 | 30 | 30 | 75 | my ($self, $parent, $child, $nodes) = @_; | |||
| 311 | 30 | 85 | splice @$parent, _offset($parent, $child), 1, @{_link($parent, $nodes)}; | ||||
| 30 | 81 | ||||||
| 312 | 30 | 95 | return $self->parent; | ||||
| 313 | } | ||||||
| 314 | |||||||
| 315 | 41 | 100 | 41 | 297 | sub _select { $_[1] ? $_[0]->grep(matches => $_[1]) : $_[0] } | ||
| 316 | |||||||
| 317 | sub _siblings { | ||||||
| 318 | 88 | 88 | 184 | my ($tree, $tags, $tail, $i) = @_; | |||
| 319 | |||||||
| 320 | 88 | 100 | 211 | return defined $i ? undef : [] if $tree->[0] eq 'root'; | |||
| 100 | |||||||
| 321 | |||||||
| 322 | 82 | 146 | my $nodes = _nodes(_parent($tree)); | ||||
| 323 | 82 | 129 | my $match = -1; | ||||
| 324 | 82 | 66 | 649 | defined($match++) and $_ eq $tree and last for @$nodes; | |||
| 100 | |||||||
| 325 | |||||||
| 326 | 82 | 100 | 161 | if ($tail) { splice @$nodes, 0, $match + 1 } | |||
| 30 | 61 | ||||||
| 327 | 52 | 125 | else { splice @$nodes, $match, ($#$nodes + 1) - $match } | ||||
| 328 | |||||||
| 329 | 82 | 100 | 205 | @$nodes = grep { $_->[0] eq 'tag' } @$nodes if $tags; | |||
| 171 | 350 | ||||||
| 330 | |||||||
| 331 | 82 | 100 | 100 | 368 | return defined $i ? $i == -1 && !@$nodes ? undef : $nodes->[$i] : $nodes; | ||
| 100 | |||||||
| 332 | } | ||||||
| 333 | |||||||
| 334 | 1264 | 100 | 1264 | 4422 | sub _start { $_[0][0] eq 'root' ? 1 : 4 } | ||
| 335 | |||||||
| 336 | sub _text { | ||||||
| 337 | 747 | 747 | 1433 | my ($nodes, $xml, $all) = @_; | |||
| 338 | |||||||
| 339 | 747 | 1305 | my $text = ''; | ||||
| 340 | 747 | 1925 | while (my $node = shift @$nodes) { | ||||
| 341 | 1076 | 1756 | my $type = $node->[0]; | ||||
| 342 | |||||||
| 343 | # Text | ||||||
| 344 | 1076 | 100 | 100 | 3887 | if ($type eq 'text' || $type eq 'cdata' || $type eq 'raw') { | ||
| 100 | 100 | ||||||
| 100 | |||||||
| 345 | 867 | 2537 | $text .= $node->[1]; | ||||
| 346 | } | ||||||
| 347 | |||||||
| 348 | # Nested tag | ||||||
| 349 | elsif ($type eq 'tag' && $all) { | ||||||
| 350 | 152 | 100 | 100 | 573 | unshift @$nodes, @{_nodes($node)} if $xml || ($node->[1] ne 'script' && $node->[1] ne 'style'); | ||
| 140 | 100 | 217 | |||||
| 351 | } | ||||||
| 352 | } | ||||||
| 353 | |||||||
| 354 | 747 | 3648 | return $text; | ||||
| 355 | } | ||||||
| 356 | |||||||
| 357 | sub _wrap { | ||||||
| 358 | 16 | 16 | 43 | my ($self, $content, $new) = @_; | |||
| 359 | |||||||
| 360 | 16 | 100 | 100 | 33 | return $self if (my $tree = $self->tree)->[0] eq 'root' && !$content; | ||
| 361 | 15 | 100 | 100 | 98 | return $self if $tree->[0] ne 'root' && $tree->[0] ne 'tag' && $content; | ||
| 100 | |||||||
| 362 | |||||||
| 363 | # Find innermost tag | ||||||
| 364 | 14 | 22 | my $current; | ||||
| 365 | 14 | 44 | my $first = $new = $self->_parse($new); | ||||
| 366 | 14 | 49 | $current = $first while $first = _nodes($first, 1)->[0]; | ||||
| 367 | 14 | 100 | 48 | return $self unless $current; | |||
| 368 | |||||||
| 369 | # Wrap content | ||||||
| 370 | 12 | 100 | 32 | if ($content) { | |||
| 371 | 5 | 9 | push @$current, @{_link($current, _nodes($tree))}; | ||||
| 5 | 14 | ||||||
| 372 | 5 | 16 | splice @$tree, _start($tree), $#$tree, @{_link($tree, _nodes($new))}; | ||||
| 5 | 14 | ||||||
| 373 | 5 | 26 | return $self; | ||||
| 374 | } | ||||||
| 375 | |||||||
| 376 | # Wrap element | ||||||
| 377 | 7 | 29 | $self->_replace(_parent($tree), $tree, _nodes($new)); | ||||
| 378 | 7 | 23 | push @$current, @{_link($current, [$tree])}; | ||||
| 7 | 22 | ||||||
| 379 | 7 | 37 | return $self; | ||||
| 380 | } | ||||||
| 381 | |||||||
| 382 | 1; | ||||||
| 383 | |||||||
| 384 | =encoding utf8 | ||||||
| 385 | |||||||
| 386 | =head1 NAME | ||||||
| 387 | |||||||
| 388 | Mojo::DOM58 - Minimalistic HTML/XML DOM parser with CSS selectors | ||||||
| 389 | |||||||
| 390 | =head1 SYNOPSIS | ||||||
| 391 | |||||||
| 392 | use Mojo::DOM58; | ||||||
| 393 | |||||||
| 394 | # Parse | ||||||
| 395 | my $dom = Mojo::DOM58->new(' Test 123 |
||||||
| 396 | |||||||
| 397 | # Find | ||||||
| 398 | say $dom->at('#b')->text; | ||||||
| 399 | say $dom->find('p')->map('text')->join("\n"); | ||||||
| 400 | say $dom->find('[id]')->map(attr => 'id')->join("\n"); | ||||||
| 401 | |||||||
| 402 | # Iterate | ||||||
| 403 | $dom->find('p[id]')->reverse->each(sub { say $_->{id} }); | ||||||
| 404 | |||||||
| 405 | # Loop | ||||||
| 406 | for my $e ($dom->find('p[id]')->each) { | ||||||
| 407 | say $e->{id}, ':', $e->text; | ||||||
| 408 | } | ||||||
| 409 | |||||||
| 410 | # Modify | ||||||
| 411 | $dom->find('div p')->last->append(' 456 '); |
||||||
| 412 | $dom->at('#c')->prepend($dom->new_tag('p', id => 'd', '789')); | ||||||
| 413 | $dom->find(':not(p)')->map('strip'); | ||||||
| 414 | |||||||
| 415 | # Render | ||||||
| 416 | say "$dom"; | ||||||
| 417 | |||||||
| 418 | =head1 DESCRIPTION | ||||||
| 419 | |||||||
| 420 | L |
||||||
| 421 | on L |
||||||
| 422 | and L |
||||||
| 423 | matching based on L |
||||||
| 424 | even try to interpret broken HTML and XML, so you should not use it for | ||||||
| 425 | validation. | ||||||
| 426 | |||||||
| 427 | =head1 FORK INFO | ||||||
| 428 | |||||||
| 429 | L |
||||||
| 430 | closely compatible with upstream. It differs only in the standalone format and | ||||||
| 431 | compatibility with Perl 5.8. Any bugs or patches not related to these changes | ||||||
| 432 | should be reported directly to the L |
||||||
| 433 | |||||||
| 434 | This release of L |
||||||
| 435 | L |
||||||
| 436 | |||||||
| 437 | =head1 NODES AND ELEMENTS | ||||||
| 438 | |||||||
| 439 | When we parse an HTML/XML fragment, it gets turned into a tree of nodes. | ||||||
| 440 | |||||||
| 441 | |||||||
| 442 | |||||||
| 443 | |
||||||
| 444 | World! | ||||||
| 445 | |||||||
| 446 | |||||||
| 447 | There are currently eight different kinds of nodes, C |
||||||
| 448 | C |
||||||
| 449 | the type C |
||||||
| 450 | |||||||
| 451 | root | ||||||
| 452 | |- doctype (html) | ||||||
| 453 | +- tag (html) | ||||||
| 454 | |- tag (head) | ||||||
| 455 | | +- tag (title) | ||||||
| 456 | | +- raw (Hello) | ||||||
| 457 | +- tag (body) | ||||||
| 458 | +- text (World!) | ||||||
| 459 | |||||||
| 460 | While all node types are represented as L |
||||||
| 461 | L"attr"> and L"namespace"> only apply to elements. | ||||||
| 462 | |||||||
| 463 | =head1 CASE-SENSITIVITY | ||||||
| 464 | |||||||
| 465 | L |
||||||
| 466 | names are lowercased and selectors need to be lowercase as well. | ||||||
| 467 | |||||||
| 468 | # HTML semantics | ||||||
| 469 | my $dom = Mojo::DOM58->new(' Hi! '); |
||||||
| 470 | say $dom->at('p[id]')->text; | ||||||
| 471 | |||||||
| 472 | If an XML declaration is found, the parser will automatically switch into XML | ||||||
| 473 | mode and everything becomes case-sensitive. | ||||||
| 474 | |||||||
| 475 | # XML semantics | ||||||
| 476 | my $dom = Mojo::DOM58->new(' Hi! '); |
||||||
| 477 | say $dom->at('P[ID]')->text; | ||||||
| 478 | |||||||
| 479 | HTML or XML semantics can also be forced with the L"xml"> method. | ||||||
| 480 | |||||||
| 481 | # Force HTML semantics | ||||||
| 482 | my $dom = Mojo::DOM58->new->xml(0)->parse(' Hi! '); |
||||||
| 483 | say $dom->at('p[id]')->text; | ||||||
| 484 | |||||||
| 485 | # Force XML semantics | ||||||
| 486 | my $dom = Mojo::DOM58->new->xml(1)->parse(' Hi! '); |
||||||
| 487 | say $dom->at('P[ID]')->text; | ||||||
| 488 | |||||||
| 489 | =head1 SELECTORS | ||||||
| 490 | |||||||
| 491 | L |
||||||
| 492 | selectors that make sense for a standalone parser are supported. | ||||||
| 493 | |||||||
| 494 | =over | ||||||
| 495 | |||||||
| 496 | =item Z<>* | ||||||
| 497 | |||||||
| 498 | Any element. | ||||||
| 499 | |||||||
| 500 | my $all = $dom->find('*'); | ||||||
| 501 | |||||||
| 502 | =item E | ||||||
| 503 | |||||||
| 504 | An element of type C |
||||||
| 505 | |||||||
| 506 | my $title = $dom->at('title'); | ||||||
| 507 | |||||||
| 508 | =item E[foo] | ||||||
| 509 | |||||||
| 510 | An C |
||||||
| 511 | |||||||
| 512 | my $links = $dom->find('a[href]'); | ||||||
| 513 | |||||||
| 514 | =item E[foo="bar"] | ||||||
| 515 | |||||||
| 516 | An C |
||||||
| 517 | |||||||
| 518 | my $case_sensitive = $dom->find('input[type="hidden"]'); | ||||||
| 519 | my $case_sensitive = $dom->find('input[type=hidden]'); | ||||||
| 520 | |||||||
| 521 | =item E[foo="bar" i] | ||||||
| 522 | |||||||
| 523 | An C |
||||||
| 524 | (ASCII-range) case-permutation of C |
||||||
| 525 | B |
||||||
| 526 | |||||||
| 527 | my $case_insensitive = $dom->find('input[type="hidden" i]'); | ||||||
| 528 | my $case_insensitive = $dom->find('input[type=hidden i]'); | ||||||
| 529 | my $case_insensitive = $dom->find('input[class~="foo" i]'); | ||||||
| 530 | |||||||
| 531 | This selector is part of | ||||||
| 532 | L |
||||||
| 533 | in progress. | ||||||
| 534 | |||||||
| 535 | =item E[foo="bar" s] | ||||||
| 536 | |||||||
| 537 | An C |
||||||
| 538 | equal to C |
||||||
| 539 | without warning! | ||||||
| 540 | |||||||
| 541 | my $case_sensitive = $dom->find('input[type="hidden" s]'); | ||||||
| 542 | |||||||
| 543 | This selector is part of | ||||||
| 544 | L |
||||||
| 545 | in progress. | ||||||
| 546 | |||||||
| 547 | =item E[foo~="bar"] | ||||||
| 548 | |||||||
| 549 | An C |
||||||
| 550 | values, one of which is exactly equal to C |
||||||
| 551 | |||||||
| 552 | my $foo = $dom->find('input[class~="foo"]'); | ||||||
| 553 | my $foo = $dom->find('input[class~=foo]'); | ||||||
| 554 | |||||||
| 555 | =item E[foo^="bar"] | ||||||
| 556 | |||||||
| 557 | An C |
||||||
| 558 | C |
||||||
| 559 | |||||||
| 560 | my $begins_with = $dom->find('input[name^="f"]'); | ||||||
| 561 | my $begins_with = $dom->find('input[name^=f]'); | ||||||
| 562 | |||||||
| 563 | =item E[foo$="bar"] | ||||||
| 564 | |||||||
| 565 | An C |
||||||
| 566 | C |
||||||
| 567 | |||||||
| 568 | my $ends_with = $dom->find('input[name$="o"]'); | ||||||
| 569 | my $ends_with = $dom->find('input[name$=o]'); | ||||||
| 570 | |||||||
| 571 | =item E[foo*="bar"] | ||||||
| 572 | |||||||
| 573 | An C |
||||||
| 574 | |||||||
| 575 | my $contains = $dom->find('input[name*="fo"]'); | ||||||
| 576 | my $contains = $dom->find('input[name*=fo]'); | ||||||
| 577 | |||||||
| 578 | =item E[foo|="en"] | ||||||
| 579 | |||||||
| 580 | An C |
||||||
| 581 | beginning (from the left) with C |
||||||
| 582 | |||||||
| 583 | my $english = $dom->find('link[hreflang|=en]'); | ||||||
| 584 | |||||||
| 585 | =item E:root | ||||||
| 586 | |||||||
| 587 | An C |
||||||
| 588 | |||||||
| 589 | my $root = $dom->at(':root'); | ||||||
| 590 | |||||||
| 591 | =item E:nth-child(n) | ||||||
| 592 | |||||||
| 593 | An C |
||||||
| 594 | |||||||
| 595 | my $third = $dom->find('div:nth-child(3)'); | ||||||
| 596 | my $odd = $dom->find('div:nth-child(odd)'); | ||||||
| 597 | my $even = $dom->find('div:nth-child(even)'); | ||||||
| 598 | my $top3 = $dom->find('div:nth-child(-n+3)'); | ||||||
| 599 | |||||||
| 600 | =item E:nth-last-child(n) | ||||||
| 601 | |||||||
| 602 | An C |
||||||
| 603 | |||||||
| 604 | my $third = $dom->find('div:nth-last-child(3)'); | ||||||
| 605 | my $odd = $dom->find('div:nth-last-child(odd)'); | ||||||
| 606 | my $even = $dom->find('div:nth-last-child(even)'); | ||||||
| 607 | my $bottom3 = $dom->find('div:nth-last-child(-n+3)'); | ||||||
| 608 | |||||||
| 609 | =item E:nth-of-type(n) | ||||||
| 610 | |||||||
| 611 | An C |
||||||
| 612 | |||||||
| 613 | my $third = $dom->find('div:nth-of-type(3)'); | ||||||
| 614 | my $odd = $dom->find('div:nth-of-type(odd)'); | ||||||
| 615 | my $even = $dom->find('div:nth-of-type(even)'); | ||||||
| 616 | my $top3 = $dom->find('div:nth-of-type(-n+3)'); | ||||||
| 617 | |||||||
| 618 | =item E:nth-last-of-type(n) | ||||||
| 619 | |||||||
| 620 | An C |
||||||
| 621 | |||||||
| 622 | my $third = $dom->find('div:nth-last-of-type(3)'); | ||||||
| 623 | my $odd = $dom->find('div:nth-last-of-type(odd)'); | ||||||
| 624 | my $even = $dom->find('div:nth-last-of-type(even)'); | ||||||
| 625 | my $bottom3 = $dom->find('div:nth-last-of-type(-n+3)'); | ||||||
| 626 | |||||||
| 627 | =item E:first-child | ||||||
| 628 | |||||||
| 629 | An C |
||||||
| 630 | |||||||
| 631 | my $first = $dom->find('div p:first-child'); | ||||||
| 632 | |||||||
| 633 | =item E:last-child | ||||||
| 634 | |||||||
| 635 | An C |
||||||
| 636 | |||||||
| 637 | my $last = $dom->find('div p:last-child'); | ||||||
| 638 | |||||||
| 639 | =item E:first-of-type | ||||||
| 640 | |||||||
| 641 | An C |
||||||
| 642 | |||||||
| 643 | my $first = $dom->find('div p:first-of-type'); | ||||||
| 644 | |||||||
| 645 | =item E:last-of-type | ||||||
| 646 | |||||||
| 647 | An C |
||||||
| 648 | |||||||
| 649 | my $last = $dom->find('div p:last-of-type'); | ||||||
| 650 | |||||||
| 651 | =item E:only-child | ||||||
| 652 | |||||||
| 653 | An C |
||||||
| 654 | |||||||
| 655 | my $lonely = $dom->find('div p:only-child'); | ||||||
| 656 | |||||||
| 657 | =item E:only-of-type | ||||||
| 658 | |||||||
| 659 | An C |
||||||
| 660 | |||||||
| 661 | my $lonely = $dom->find('div p:only-of-type'); | ||||||
| 662 | |||||||
| 663 | =item E:empty | ||||||
| 664 | |||||||
| 665 | An C |
||||||
| 666 | |||||||
| 667 | my $empty = $dom->find(':empty'); | ||||||
| 668 | |||||||
| 669 | =item E:any-link | ||||||
| 670 | |||||||
| 671 | Alias for L"E:link">. Note that this selector is B |
||||||
| 672 | change without warning! This selector is part of | ||||||
| 673 | L |
||||||
| 674 | work in progress. | ||||||
| 675 | |||||||
| 676 | =item E:link | ||||||
| 677 | |||||||
| 678 | An C |
||||||
| 679 | not yet visited (C<:link>) or already visited (C<:visited>). Note that | ||||||
| 680 | L |
||||||
| 681 | C<:visited> yield exactly the same results. | ||||||
| 682 | |||||||
| 683 | my $links = $dom->find(':any-link'); | ||||||
| 684 | my $links = $dom->find(':link'); | ||||||
| 685 | my $links = $dom->find(':visited'); | ||||||
| 686 | |||||||
| 687 | =item E:visited | ||||||
| 688 | |||||||
| 689 | Alias for L"E:link">. | ||||||
| 690 | |||||||
| 691 | =item E:scope | ||||||
| 692 | |||||||
| 693 | An C |
||||||
| 694 | without warning! | ||||||
| 695 | |||||||
| 696 | my $scoped = $dom->find('a:not(:scope > a)'); | ||||||
| 697 | my $scoped = $dom->find('div :scope p'); | ||||||
| 698 | my $scoped = $dom->find('~ p'); | ||||||
| 699 | |||||||
| 700 | This selector is part of L |
||||||
| 701 | |||||||
| 702 | =item E:checked | ||||||
| 703 | |||||||
| 704 | A user interface element C |
||||||
| 705 | checkbox). | ||||||
| 706 | |||||||
| 707 | my $input = $dom->find(':checked'); | ||||||
| 708 | |||||||
| 709 | =item E.warning | ||||||
| 710 | |||||||
| 711 | An C |
||||||
| 712 | |||||||
| 713 | my $warning = $dom->find('div.warning'); | ||||||
| 714 | |||||||
| 715 | =item E#myid | ||||||
| 716 | |||||||
| 717 | An C |
||||||
| 718 | |||||||
| 719 | my $foo = $dom->at('div#foo'); | ||||||
| 720 | |||||||
| 721 | =item E:not(s1, s2) | ||||||
| 722 | |||||||
| 723 | An C |
||||||
| 724 | selector C |
||||||
| 725 | might change without warning! | ||||||
| 726 | |||||||
| 727 | my $others = $dom->find('div p:not(:first-child, :last-child)'); | ||||||
| 728 | |||||||
| 729 | Support for compound selectors was added as part of | ||||||
| 730 | L |
||||||
| 731 | in progress. | ||||||
| 732 | |||||||
| 733 | =item E:is(s1, s2) | ||||||
| 734 | |||||||
| 735 | An C |
||||||
| 736 | C |
||||||
| 737 | |||||||
| 738 | my $headers = $dom->find(':is(section, article, aside, nav) h1'); | ||||||
| 739 | |||||||
| 740 | This selector is part of | ||||||
| 741 | L |
||||||
| 742 | in progress. | ||||||
| 743 | |||||||
| 744 | =item E:has(rs1, rs2) | ||||||
| 745 | |||||||
| 746 | An C |
||||||
| 747 | match an element. Note that this selector is B |
||||||
| 748 | |||||||
| 749 | my $link = $dom->find('a:has(> img)'); | ||||||
| 750 | |||||||
| 751 | This selector is part of L |
||||||
| 752 | Also be aware that this feature is currently marked C |
||||||
| 753 | completely. | ||||||
| 754 | |||||||
| 755 | =item A|E | ||||||
| 756 | |||||||
| 757 | An C |
||||||
| 758 | L |
||||||
| 759 | Key/value pairs passed to selector methods are used to declare namespace | ||||||
| 760 | aliases. | ||||||
| 761 | |||||||
| 762 | my $elem = $dom->find('lq|elem', lq => 'http://example.com/q-markup'); | ||||||
| 763 | |||||||
| 764 | Using an empty alias searches for an element that belongs to no namespace. | ||||||
| 765 | |||||||
| 766 | my $div = $dom->find('|div'); | ||||||
| 767 | |||||||
| 768 | =item E F | ||||||
| 769 | |||||||
| 770 | An C |
||||||
| 771 | |||||||
| 772 | my $headlines = $dom->find('div h1'); | ||||||
| 773 | |||||||
| 774 | =item E E |
||||||
| 775 | |||||||
| 776 | An C |
||||||
| 777 | |||||||
| 778 | my $headlines = $dom->find('html > body > div > h1'); | ||||||
| 779 | |||||||
| 780 | =item E + F | ||||||
| 781 | |||||||
| 782 | An C |
||||||
| 783 | |||||||
| 784 | my $second = $dom->find('h1 + h2'); | ||||||
| 785 | |||||||
| 786 | =item E ~ F | ||||||
| 787 | |||||||
| 788 | An C |
||||||
| 789 | |||||||
| 790 | my $second = $dom->find('h1 ~ h2'); | ||||||
| 791 | |||||||
| 792 | =item E, F, G | ||||||
| 793 | |||||||
| 794 | Elements of type C |
||||||
| 795 | |||||||
| 796 | my $headlines = $dom->find('h1, h2, h3'); | ||||||
| 797 | |||||||
| 798 | =item E[foo=bar][bar=baz] | ||||||
| 799 | |||||||
| 800 | An C |
||||||
| 801 | |||||||
| 802 | my $links = $dom->find('a[foo^=b][foo$=ar]'); | ||||||
| 803 | |||||||
| 804 | =back | ||||||
| 805 | |||||||
| 806 | =head1 OPERATORS | ||||||
| 807 | |||||||
| 808 | L |
||||||
| 809 | |||||||
| 810 | =head2 array | ||||||
| 811 | |||||||
| 812 | my @nodes = @$dom; | ||||||
| 813 | |||||||
| 814 | Alias for L"child_nodes">. | ||||||
| 815 | |||||||
| 816 | # "" | ||||||
| 817 | $dom->parse('123')->[0]; | ||||||
| 818 | |||||||
| 819 | =head2 bool | ||||||
| 820 | |||||||
| 821 | my $bool = !!$dom; | ||||||
| 822 | |||||||
| 823 | Always true. | ||||||
| 824 | |||||||
| 825 | =head2 hash | ||||||
| 826 | |||||||
| 827 | my %attrs = %$dom; | ||||||
| 828 | |||||||
| 829 | Alias for L"attr">. | ||||||
| 830 | |||||||
| 831 | # "test" | ||||||
| 832 | $dom->parse(' Test ')->at('div')->{id}; |
||||||
| 833 | |||||||
| 834 | =head2 stringify | ||||||
| 835 | |||||||
| 836 | my $str = "$dom"; | ||||||
| 837 | |||||||
| 838 | Alias for L"to_string">. | ||||||
| 839 | |||||||
| 840 | =head1 FUNCTIONS | ||||||
| 841 | |||||||
| 842 | L |
||||||
| 843 | individually. | ||||||
| 844 | |||||||
| 845 | =head2 tag_to_html | ||||||
| 846 | |||||||
| 847 | my $str = tag_to_html 'div', id => 'foo', 'safe content'; | ||||||
| 848 | |||||||
| 849 | Generate HTML/XML tag and render it right away. This is a significantly faster | ||||||
| 850 | alternative to L"new_tag"> for template systems that have to generate a lot | ||||||
| 851 | of tags. | ||||||
| 852 | |||||||
| 853 | =head1 METHODS | ||||||
| 854 | |||||||
| 855 | L |
||||||
| 856 | |||||||
| 857 | =head2 new | ||||||
| 858 | |||||||
| 859 | my $dom = Mojo::DOM58->new; | ||||||
| 860 | my $dom = Mojo::DOM58->new(' |
||||||
| 861 | |||||||
| 862 | Construct a new scalar-based L |
||||||
| 863 | fragment if necessary. | ||||||
| 864 | |||||||
| 865 | =head2 new_tag | ||||||
| 866 | |||||||
| 867 | my $tag = Mojo::DOM58->new_tag('div'); | ||||||
| 868 | my $tag = $dom->new_tag('div'); | ||||||
| 869 | my $tag = $dom->new_tag('div', id => 'foo', hidden => undef); | ||||||
| 870 | my $tag = $dom->new_tag('div', 'safe content'); | ||||||
| 871 | my $tag = $dom->new_tag('div', id => 'foo', 'safe content'); | ||||||
| 872 | my $tag = $dom->new_tag('div', data => {mojo => 'rocks'}, 'safe content'); | ||||||
| 873 | my $tag = $dom->new_tag('div', id => 'foo', sub { 'unsafe content' }); | ||||||
| 874 | |||||||
| 875 | Construct a new L |
||||||
| 876 | attributes and content. The C attribute may contain a hash reference with | ||||||
| 877 | key/value pairs to generate attributes from. | ||||||
| 878 | |||||||
| 879 | # " " |
||||||
| 880 | $dom->new_tag('br'); | ||||||
| 881 | |||||||
| 882 | # "" | ||||||
| 883 | $dom->new_tag('div'); | ||||||
| 884 | |||||||
| 885 | # "" | ||||||
| 886 | $dom->new_tag('div', id => 'foo', hidden => undef); | ||||||
| 887 | |||||||
| 888 | # " test & 123 " |
||||||
| 889 | $dom->new_tag('div', 'test & 123'); | ||||||
| 890 | |||||||
| 891 | # " test & 123 " |
||||||
| 892 | $dom->new_tag('div', id => 'foo', 'test & 123'); | ||||||
| 893 | |||||||
| 894 | # " test & 123 "" |
||||||
| 895 | $dom->new_tag('div', data => {foo => 1, Bar => 'test'}, 'test & 123'); | ||||||
| 896 | |||||||
| 897 | # " test & 123 " |
||||||
| 898 | $dom->new_tag('div', id => 'foo', sub { 'test & 123' }); | ||||||
| 899 | |||||||
| 900 | # " HelloMojo! " |
||||||
| 901 | $dom->parse(' Hello ')->at('div') |
||||||
| 902 | ->append_content($dom->new_tag('b', 'Mojo!'))->root; | ||||||
| 903 | |||||||
| 904 | =head2 all_text | ||||||
| 905 | |||||||
| 906 | my $text = $dom->all_text; | ||||||
| 907 | |||||||
| 908 | Extract text content from all descendant nodes of this element. For HTML documents C |