File Coverage

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 '[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 is the HTML/XML engine used by L, based on the L
359             Standard|https://html.spec.whatwg.org> and the L.
360              
361             =head1 FUNCTIONS
362              
363             L implements the following functions, which can be imported individually.
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 for template
370             systems that have to generate a lot of tags.
371              
372             =head1 ATTRIBUTES
373              
374             L implements the following attributes.
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 inherits all methods from L and implements the following new ones.
393              
394             =head2 parse
395              
396             $html = $html->parse('I ♥ Mojolicious!');
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, L, L.
415              
416             =cut