| blib/lib/Mojo/DOM/HTML.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 129 | 132 | 97.7 |
| branch | 113 | 118 | 95.7 |
| condition | 48 | 52 | 92.3 |
| subroutine | 15 | 15 | 100.0 |
| pod | 4 | 4 | 100.0 |
| total | 309 | 321 | 96.2 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Mojo::DOM::HTML; | ||||||
| 2 | 65 | 65 | 492 | use Mojo::Base -base; | |||
| 65 | 154 | ||||||
| 65 | 618 | ||||||
| 3 | |||||||
| 4 | 65 | 65 | 472 | use Exporter qw(import); | |||
| 65 | 2036 | ||||||
| 65 | 4453 | ||||||
| 5 | 65 | 65 | 342 | use Mojo::Util qw(html_attr_unescape html_unescape xml_escape); | |||
| 65 | 134 | ||||||
| 65 | 4557 | ||||||
| 6 | 65 | 65 | 397 | use Scalar::Util qw(weaken); | |||
| 65 | 132 | ||||||
| 65 | 188841 | ||||||
| 7 | |||||||
| 8 | our @EXPORT_OK = ('tag_to_html'); | ||||||
| 9 | |||||||
| 10 | has tree => sub { ['root'] }; | ||||||
| 11 | has 'xml'; | ||||||
| 12 | |||||||
| 13 | my $ATTR_RE = qr/ | ||||||
| 14 | ([^<>=\s\/0-9.\-][^<>=\s\/]*|\/) # Key | ||||||
| 15 | (?: | ||||||
| 16 | \s*=\s* | ||||||
| 17 | (?s:(["'])(.*?)\g{-2}|([^>\s]*)) # Value | ||||||
| 18 | )? | ||||||
| 19 | \s* | ||||||
| 20 | /x; | ||||||
| 21 | my $TOKEN_RE = qr/ | ||||||
| 22 | ([^<]+)? # Text | ||||||
| 23 | (?: | ||||||
| 24 | <(?: | ||||||
| 25 | !(?: | ||||||
| 26 | DOCTYPE( | ||||||
| 27 | \s+\w+ # Doctype | ||||||
| 28 | (?:(?:\s+\w+)?(?:\s+(?:"[^"]*"|'[^']*'))+)? # External ID | ||||||
| 29 | (?:\s+\[.+?\])? # Int Subset | ||||||
| 30 | \s*) | ||||||
| 31 | | | ||||||
| 32 | --(.*?)(?:--!?|(?<=' if $type eq 'comment'; | ||||||
| 257 | |||||||
| 258 | # CDATA | ||||||
| 259 | 6 | 100 | 29 | return '[1] . ']]>' if $type eq 'cdata'; | |||
| 260 | |||||||
| 261 | # Processing instruction | ||||||
| 262 | 3 | 50 | 20 | return '' . $tree->[1] . '?>' if $type eq 'pi'; | |||
| 263 | |||||||
| 264 | # Everything else | ||||||
| 265 | 0 | 0 | return ''; | ||||
| 266 | } | ||||||
| 267 | |||||||
| 268 | sub _script_content { | ||||||
| 269 | 36 | 36 | 61 | my $html = shift; | |||
| 270 | 36 | 82 | my $start = pos $$html; | ||||
| 271 | |||||||
| 272 | 36 | 64 | my $state = 0; | ||||
| 273 | |||||||
| 274 | 36 | 55 | while (1) { | ||||
| 275 | 58 | 100 | 124 | if ($state == 0) { $$html =~ /\G[^<]*/gcs } | |||
| 49 | 176 | ||||||
| 276 | 9 | 27 | else { $$html =~ /\G[^<\-]*/gcs } | ||||
| 277 | |||||||
| 278 | 58 | 176 | my $p = pos $$html; | ||||
| 279 | 58 | 100 | 347 | return (substr($$html, $start), 0) if $p >= length $$html; | |||
| 280 | |||||||
| 281 | 57 | 100 | 119 | if ($state == 0) { | |||
| 100 | |||||||
| 282 | 48 | 100 | 210 | if ($$html =~ m!\G)!gcsi) { return (substr($$html, $start, $p - $start), 1) } | |||
| 33 | 100 | 394 | |||||
| 283 | 4 | 10 | elsif ($$html =~ /\G/gcs) { $state = 0 } | ||||
| 289 | 3 | 8 | elsif ($$html =~ m!\G])!gcsi) { $state = 2 } | ||||
| 290 | 0 | 0 | else { pos($$html) = $p + 1 } | ||||
| 291 | } | ||||||
| 292 | else { | ||||||
| 293 | 3 | 100 | 21 | if ($$html =~ m!\G)!gcsi) { $state = 1 } | |||
| 2 | 50 | 6 | |||||
| 294 | 1 | 3 | elsif ($$html =~ /\G-->/gcs) { $state = 0 } | ||||
| 295 | 0 | 0 | else { pos($$html) = $p + 1 } | ||||
| 296 | } | ||||||
| 297 | } | ||||||
| 298 | } | ||||||
| 299 | |||||||
| 300 | sub _start { | ||||||
| 301 | 2157 | 2157 | 4451 | my ($start, $attrs, $xml, $current) = @_; | |||
| 302 | |||||||
| 303 | # Autoclose optional HTML elements | ||||||
| 304 | 2157 | 100 | 100 | 12767 | if (!$xml && $$current->[0] ne 'root') { | ||
| 305 | 1728 | 100 | 5658 | if (my $end = $END{$start}) { _end($end, 0, $current) } | |||
| 566 | 100 | 1246 | |||||
| 306 | |||||||
| 307 | elsif (my $close = $CLOSE{$start}) { | ||||||
| 308 | 599 | 1237 | my ($allowed, $scope) = @$close; | ||||
| 309 | |||||||
| 310 | # Close allowed parent elements in scope | ||||||
| 311 | 599 | 913 | my $parent = $$current; | ||||
| 312 | 599 | 66 | 2725 | while ($parent->[0] ne 'root' && !$scope->{$parent->[1]}) { | |||
| 313 | 440 | 100 | 1097 | _end($parent->[1], 0, $current) if $allowed->{$parent->[1]}; | |||
| 314 | 440 | 2010 | $parent = $parent->[3]; | ||||
| 315 | } | ||||||
| 316 | } | ||||||
| 317 | } | ||||||
| 318 | |||||||
| 319 | # New tag | ||||||
| 320 | 2157 | 7900 | push @$$current, my $new = ['tag', $start, $attrs, $$current]; | ||||
| 321 | 2157 | 4240 | weaken $new->[3]; | ||||
| 322 | 2157 | 4014 | $$current = $new; | ||||
| 323 | } | ||||||
| 324 | |||||||
| 325 | sub _tag { | ||||||
| 326 | 965 | 965 | 2996 | my $tree = ['tag', shift, undef, undef]; | |||
| 327 | |||||||
| 328 | # Content | ||||||
| 329 | 965 | 100 | 4562 | push @$tree, ref $_[-1] eq 'CODE' ? ['raw', pop->()] : ['text', pop] if @_ % 2; | |||
| 100 | |||||||
| 330 | |||||||
| 331 | # Attributes | ||||||
| 332 | 965 | 4347 | my $attrs = $tree->[2] = {@_}; | ||||
| 333 | 965 | 100 | 100 | 4632 | return $tree unless exists $attrs->{data} && ref $attrs->{data} eq 'HASH'; | ||
| 334 | 5 | 15 | my $data = delete $attrs->{data}; | ||||
| 335 | 5 | 21 | @$attrs{map { y/_/-/; lc "data-$_" } keys %$data} = values %$data; | ||||
| 9 | 25 | ||||||
| 9 | 46 | ||||||
| 336 | 5 | 20 | return $tree; | ||||
| 337 | } | ||||||
| 338 | |||||||
| 339 | 1; | ||||||
| 340 | |||||||
| 341 | =encoding utf8 | ||||||
| 342 | |||||||
| 343 | =head1 NAME | ||||||
| 344 | |||||||
| 345 | Mojo::DOM::HTML - HTML/XML engine | ||||||
| 346 | |||||||
| 347 | =head1 SYNOPSIS | ||||||
| 348 | |||||||
| 349 | use Mojo::DOM::HTML; | ||||||
| 350 | |||||||
| 351 | # Turn HTML into DOM tree | ||||||
| 352 | my $html = Mojo::DOM::HTML->new; | ||||||
| 353 | $html->parse(' Test 123 |
||||||
| 354 | my $tree = $html->tree; | ||||||
| 355 | |||||||
| 356 | =head1 DESCRIPTION | ||||||
| 357 | |||||||
| 358 | L | ||||||
| 359 | Standard|https://html.spec.whatwg.org> and the L |
||||||
| 360 | |||||||
| 361 | =head1 FUNCTIONS | ||||||
| 362 | |||||||
| 363 | L |
||||||
| 364 | |||||||
| 365 | =head2 tag_to_html | ||||||
| 366 | |||||||
| 367 | my $str = tag_to_html 'div', id => 'foo', 'safe content'; | ||||||
| 368 | |||||||
| 369 | Generate HTML/XML tag and render it right away. This is a significantly faster alternative to L"tag"> for template | ||||||
| 370 | systems that have to generate a lot of tags. | ||||||
| 371 | |||||||
| 372 | =head1 ATTRIBUTES | ||||||
| 373 | |||||||
| 374 | L |
||||||
| 375 | |||||||
| 376 | =head2 tree | ||||||
| 377 | |||||||
| 378 | my $tree = $html->tree; | ||||||
| 379 | $html = $html->tree(['root']); | ||||||
| 380 | |||||||
| 381 | Document Object Model. Note that this structure should only be used very carefully since it is very dynamic. | ||||||
| 382 | |||||||
| 383 | =head2 xml | ||||||
| 384 | |||||||
| 385 | my $bool = $html->xml; | ||||||
| 386 | $html = $html->xml($bool); | ||||||
| 387 | |||||||
| 388 | Disable HTML semantics in parser and activate case-sensitivity, defaults to auto-detection based on XML declarations. | ||||||
| 389 | |||||||
| 390 | =head1 METHODS | ||||||
| 391 | |||||||
| 392 | L |
||||||
| 393 | |||||||
| 394 | =head2 parse | ||||||
| 395 | |||||||
| 396 | $html = $html->parse(' |
||||||
| 397 | |||||||
| 398 | Parse HTML/XML fragment. | ||||||
| 399 | |||||||
| 400 | =head2 render | ||||||
| 401 | |||||||
| 402 | my $str = $html->render; | ||||||
| 403 | |||||||
| 404 | Render DOM to HTML/XML. | ||||||
| 405 | |||||||
| 406 | =head2 tag | ||||||
| 407 | |||||||
| 408 | $html = $html->tag('div', id => 'foo', 'safe content'); | ||||||
| 409 | |||||||
| 410 | Generate HTML/XML tag. | ||||||
| 411 | |||||||
| 412 | =head1 SEE ALSO | ||||||
| 413 | |||||||
| 414 | L |
||||||
| 415 | |||||||
| 416 | =cut |