File Coverage

lib/Pod/PseudoPod/DOM/Role/HTML.pm
Criterion Covered Total %
statement 259 271 95.5
branch 51 68 75.0
condition 11 16 68.7
subroutine 69 71 97.1
pod 0 53 0.0
total 390 479 81.4


line stmt bran cond sub pod time code
1             package Pod::PseudoPod::DOM::Role::HTML;
2             # ABSTRACT: an HTML formatter role for PseudoPod DOM trees
3              
4 16     16   13053 use strict;
  16         45  
  16         628  
5 16     16   98 use warnings;
  16         32  
  16         443  
6              
7 16     16   91 use Moose::Role;
  16         36  
  16         185  
8              
9 16     16   105676 use HTML::Entities;
  16         84787  
  16         1450  
10 16     16   161 use Scalar::Util 'blessed';
  16         39  
  16         815  
11 16     16   581 use MIME::Base64 'encode_base64url';
  16         701  
  16         29586  
12              
13             requires 'type';
14             has 'add_body_tags', is => 'ro', default => 0;
15             has 'emit_environments', is => 'ro', default => sub { {} };
16             has 'anchors', is => 'rw', default => sub { {} };
17              
18             sub get_anchor
19             {
20 425     425 0 745 my $self = shift;
21 425         1082 my $anchor = $self->emit_kids( encode => 'index_anchor' );
22 425         1634 return encode_base64url( $anchor );
23             }
24              
25             sub get_link_for_anchor
26             {
27 53     53 0 157 my ($self, $anchor) = @_;
28 53         1890 my $anchors = $self->anchors;
29              
30 53 50       222 return unless my $heading = $anchors->{$anchor};
31 53         1865 my $filename = $heading->link;
32 53         160 my $target = $heading->get_anchor;
33 53         737 my $title = $heading->get_link_text;
34              
35 53         216 return $filename, $target, $title;
36             }
37              
38             sub resolve_anchors
39             {
40 14     14 0 111 my $self = shift;
41 14         522 my $anchors = $self->anchors;
42              
43 14         41 for my $anchor (@{ $self->anchor })
  14         536  
44             {
45 53         173 my $a = $anchor->emit_kids;
46 53         205 $anchors->{$anchor->emit_kids} = $anchor;
47             }
48             }
49              
50             sub get_index_entries
51             {
52 20     20 0 216 my ($self, $seen) = @_;
53 20   50     176 $seen ||= {};
54              
55 20         53 my @entries;
56              
57 20         50 for my $entry (@{ $self->index })
  20         827  
58             {
59 199         609 my $text = $entry->emit_kids( encode => 'index_anchor' );
60 199         7304 $entry->id( ++$seen->{ $text } );
61 199         555 push @entries, $entry;
62             }
63              
64 20         149 return @entries;
65             }
66              
67 50     50 0 504 sub accept_targets { qw( html HTML xhtml XHTML ) }
68       0 0   sub encode_E_contents {}
69              
70             my %characters = (
71             acute => sub { '&' . shift . 'acute;' },
72             grave => sub { '&' . shift . 'grave;' },
73             uml => sub { '&' . shift . 'uml;' },
74             cedilla => sub { '&' . shift . 'cedil;' },
75             opy => sub { '©' },
76             dash => sub { '—' },
77             lusmn => sub { '±' },
78             mp => sub { '&' },
79             rademark => sub { '™' },
80             );
81              
82             sub emit_character
83             {
84 143     143 0 373 my ($self, %args) = @_;
85 143         279 my $content = eval { $self->emit_kids };
  143         434  
86              
87 143 50       386 return '' unless defined $content;
88              
89 143 50       766 if (my ($char, $class) = $content =~ /(\w)(\w+)/)
90             {
91 143 100       687 return $characters{$class}->($char) if exists $characters{$class};
92             }
93              
94 52   50     198 $args{encode} ||= '';
95 52         240 my $char = Pod::Escapes::e2char( $content );
96 52 100       1166 return $char if $args{encode} =~ /^(index_|id$)/;
97              
98 26         96 return $self->handle_encoding( $char );
99             }
100              
101             sub emit
102             {
103 5301     5301 0 8799 my $self = shift;
104 5301         161502 my $type = $self->type;
105 5301         11472 my $emit = 'emit_' . $type;
106              
107 5301         15908 $self->$emit( @_ );
108             }
109              
110             sub emit_document
111             {
112 41     41 0 106 my $self = shift;
113              
114 41 100       1995 return $self->emit_body if $self->add_body_tags;
115 40         220 return $self->emit_kids( @_ );
116             }
117              
118             sub extract_headings
119             {
120 1     1 0 4 my ($self, %args) = @_;
121 1         2 my @headings;
122              
123 1         3 for my $kid (@{ $self->children })
  1         42  
124             {
125 47 100       1316 next unless $kid->type eq 'header';
126 8 100       37 next if $kid->exclude_from_toc( $args{max_depth} );
127 5         17 push @headings, $kid;
128             }
129              
130 1         4 return \@headings;
131             }
132              
133             sub emit_toc
134             {
135 1     1 0 173 my $self = shift;
136 1         7 my $headings = $self->extract_headings;
137              
138 1         43 return $self->walk_headings( $headings, filename => $self->filename );
139             }
140              
141             sub walk_headings
142             {
143 1     1 0 5 my ($self, $headings, %args) = @_;
144 1   50     11 $args{indent} ||= '';
145              
146 1         2 my $toc = '';
147              
148 1         4 for my $heading (@$headings)
149             {
150 5         10 $toc .= $args{indent};
151              
152 5 50       25 if (blessed($heading))
153             {
154 5         16 $toc .= '<li>' . $heading->get_heading_link( %args );
155             }
156             else
157             {
158 0         0 my $indent = $args{indent} . ' ';
159             $toc .= qq|\n$args{indent}|
160             . $args{indent} . qq|<ul>\n|
161             . $self->walk_headings( $heading, %args, indent => $indent )
162 0         0 . $args{indent} . qq|</ul>\n|;
163              
164             }
165              
166 5         17 $toc .= qq|</li>\n|;
167             }
168              
169 1         20 return $toc . qq|\n|;
170             }
171              
172             sub get_heading_link
173             {
174 5     5 0 15 my ($self, %args) = @_;
175              
176 5         11 my $content = $self->emit_kids;
177 5   50     169 my $filename = $self->filename || '';
178 5         13 my $frag = $self->get_anchor;
179              
180 5         51 $content =~ s/^\*//;
181 5         34 return qq|<a href="$filename#$frag">$content</a>|;
182             }
183              
184             sub emit_body
185             {
186 1     1 0 2 my $self = shift;
187 1         5 return <<END_HTML_HEAD . $self->emit_kids( @_ ) . <<END_HTML;
188             <!DOCTYPE html>
189             <html lang="en">
190             <head>
191             <link rel="stylesheet" href="../css/style.css" type="text/css" />
192             </head>
193             <body>
194              
195             END_HTML_HEAD
196             </body>
197             </html>
198             END_HTML
199             }
200              
201             sub emit_kids
202             {
203 2258     2258 0 3924 my $self = shift;
204 2258         3547 join '', map { $_->emit( @_ ) } @{ $self->children };
  3570         26009  
  2258         72818  
205             }
206              
207             sub emit_header
208             {
209 114     114 0 236 my $self = shift;
210 114         311 my $content = $self->emit_kids( @_ );
211 114         4066 my $id_node = $self->anchor;
212 114 100       482 my $id = $id_node ? $id_node->get_anchor : $self->get_anchor;
213 114         1461 my $no_toc = $content =~ s/^\*//;
214 114         3961 my $level = 'h' . ($self->level + 1);
215 114 100       438 my $anchor = $id_node ? $self->emit_index( @_ ) : '';
216              
217 114         631 return qq|<$level id="$id">$anchor$content</$level>\n\n|;
218             }
219              
220             sub emit_plaintext
221             {
222 3140     3140 0 7417 my ($self, %args) = @_;
223 3140         102561 my $content = $self->content;
224 3140 50       7258 $content = '' unless defined $content;
225 3140         8264 $self->handle_encoding( $content, %args );
226             }
227              
228             sub handle_encoding
229             {
230 3166     3166 0 7361 my ($self, $content, %args) = @_;
231              
232 3166 100       7543 if (my $encode = $args{encode})
233             {
234 1212         2390 my $method = 'encode_' . $encode;
235 1212         3968 return $self->$method( $content, %args );
236             }
237              
238 1954         4507 return $self->encode_text( $content, %args );
239             }
240              
241 26     26 0 193 sub encode_none { $_[1] }
242              
243             sub encode_split
244             {
245 39     39 0 175 my ($self, $content, %args) = @_;
246 39         109 my $target = $args{target};
247             return join $args{joiner},
248 39         740 map { $self->encode_text( $_ ) } split /\s*\Q$target\E\s*/, $content;
  78         205  
249             }
250              
251             sub encode_text
252             {
253 2032     2032 0 3800 my ($self, $text) = @_;
254              
255 16     16   158 use Carp;
  16         43  
  16         20696  
256 2032 50       4257 unless (defined $text)
257             {
258 0         0 confess 'no text';
259             }
260 2032         5028 $text = encode_entities($text);
261 2032         29866 $text =~ s/\s*---\s*/&#8213;/g;
262 2032         3509 $text =~ s/\s*--\s*/&mdash;/g;
263              
264 2032         9742 return $text;
265             }
266              
267             sub encode_id
268             {
269 0     0 0 0 my ($self, $text) = @_;
270 0         0 $text =~ s/<.+?>//g;
271 0         0 $text =~ s/\W//g;
272 0         0 return lc $text;
273             }
274              
275             sub encode_index_anchor
276             {
277 813     813 0 1713 my ($self, $text) = @_;
278              
279 813         1599 $text =~ s/^\*//;
280 813         3099 $text =~ s/[\s"]//g;
281              
282 813         4560 return $text;
283             }
284              
285             sub encode_index_key
286             {
287 17     17 0 37 my ($self, $text) = @_;
288 17         85 $text =~ s/^\s+|\s+$//g;
289 17         187 return $text;
290             }
291              
292             sub encode_verbatim_text
293             {
294 317     317 0 681 my ($self, $text) = @_;
295 317         939 return encode_entities( $text );
296             }
297              
298             sub emit_literal
299             {
300 13     13 0 40 my $self = shift;
301 13         34 my @kids;
302              
303 13 50       447 if (my $title = $self->title)
304             {
305 13         76 my $target = $title->emit_kids( encode => 'none' );
306             @kids = map
307             {
308 13         87 $_->emit_kids(
309             encode => 'split', target => $target, joiner => "</p>\n\n<p>",
310             )
311 13         45 } @{ $self->children };
  13         455  
312             }
313             else
314             {
315 0         0 @kids = map { $_->emit_kids( @_ ) } @{ $self->children };
  0         0  
  0         0  
316             }
317              
318 13         118 return qq|<div class="literal"><p>|
319             . join( "\n", @kids )
320             . qq|</p></div>\n\n|;
321             }
322              
323             sub emit_anchor
324             {
325 1     1 0 3 my $self = shift;
326 1         18 return qq|<a name="| . $self->get_anchor . qq|"></a>|;
327             }
328              
329             sub emit_number_item
330             {
331 41     41 0 102 my $self = shift;
332 41         1474 my $marker = $self->marker;
333 41 50       182 my $number = $marker ? qq| number="$marker"| : '';
334 41         144 return "<li$number>" . $self->emit_kids . "</li>\n\n";
335             }
336              
337             sub emit_text_item
338             {
339 145     145 0 262 my $self = shift;
340 145         4642 my $kids = $self->children;
341 145 50       402 return "<li></li>\n\n" unless @$kids;
342              
343 145         360 my $first = shift @$kids;
344 145 100       423 return '<li>' . $first->emit( @_ ) . qq|</li>\n\n| unless @$kids;
345              
346             return "<li><p>" . $first->emit . "</p>\n\n"
347 106         324 . join( '', map { $_->emit } @$kids ) . "</li>\n\n";
  106         343  
348             }
349              
350             sub emit_verbatim
351             {
352 42     42 0 97 my $self = shift;
353 42         144 return "<pre><code>" . $self->emit_kids( encode => 'verbatim_text', @_ )
354             . "</code></pre>\n\n";
355             }
356              
357 150     150 0 477 sub emit_italics { shift->emit_tagged_kids( 'em', @_ ) }
358 211     211 0 592 sub emit_code { shift->emit_tagged_kids( 'code', @_ ) }
359 30     30 0 141 sub emit_bold { shift->emit_tagged_kids( 'strong', @_ ) }
360 14     14 0 238 sub emit_superscript { shift->emit_tagged_kids( 'sup', @_ ) }
361 14     14 0 74 sub emit_subscript { shift->emit_tagged_kids( 'sub', @_ ) }
362 40     40 0 169 sub emit_file { shift->emit_tagged_kids( 'em', @_ ) }
363              
364             sub emit_tagged_kids
365             {
366 459     459 0 1213 my ($self, $tag, %args) = @_;
367 459         1504 my $kids = $self->emit_kids( encode => 'verbatim_text', %args );
368 459   100     5702 $args{encode} ||= '';
369              
370 459 100       1778 return $kids if $args{encode} =~ /^(index_|id$)/;
371 273         1182 return qq|<$tag>$kids</$tag>|;
372             }
373              
374             sub emit_footnote
375             {
376 14     14 0 49 my $self = shift;
377 14         70 return ' <span class="footnote">' . $self->emit_kids . '</span>';
378             }
379              
380             sub emit_url
381             {
382 14     14 0 49 my $self = shift;
383 14         65 my $url = $self->emit_kids;
384 14         90 return qq|<a class="url" href="$url">$url</a>|;
385             }
386              
387             sub emit_link
388             {
389 53     53 0 111 my $self = shift;
390 53         152 my $anchor = $self->emit_kids;
391              
392 53         208 my ($file, $frag, $text) = $self->get_link_for_anchor( $anchor );
393 53         276 return qq|<a href="$file#$frag">$text</a>|;
394             }
395              
396 16     16   227 use constant { BEFORE => 0, AFTER => 1 };
  16         68  
  16         5133  
397              
398             my %block_items =
399             (
400             programlisting => [ qq|<div class="programlisting">\n\n|, q|</div>| ],
401             sidebar => [ qq|<div class="sidebar">\n\n|, q|</div>| ],
402             epigraph => [ qq|<div class="epigraph">\n\n|, q|</div>| ],
403             blockquote => [ qq|<div class="blockquote">\n\n|, q|</div>| ],
404             );
405              
406             while (my ($tag, $values) = each %block_items)
407             {
408             my $sub = sub
409             {
410 56     56   127 my $self = shift;
        56      
        56      
        56      
411 56         1911 my $title = $self->title;
412 56         1936 my $env = $self->emit_environments;
413              
414             return $self->make_basic_block( $env->{$tag}, $title, @_ )
415 56 100       215 if exists $env->{$tag};
416              
417             # deal with title somehow
418 54         220 return $values->[BEFORE]
419             . $self->make_block_title( $title )
420             . $self->emit_kids . $values->[AFTER]
421             . "\n\n";
422             };
423              
424 16     16   139 do { no strict 'refs'; *{ 'emit_' . $tag } = $sub };
  16         41  
  16         8354  
425             }
426              
427             my %invisibles = map { $_ => 1 } qw( index anchor );
428              
429             sub emit_paragraph
430             {
431 481     481 0 869 my $self = shift;
432 481         758 my @kids = @{ $self->children };
  481         15221  
433 481         1040 my $has_visible_text = grep { ! exists $invisibles{ $_->type } } @kids;
  1465         42401  
434 481 50       1143 return $self->emit_kids( @_ ) unless $has_visible_text;
435              
436 481 100 66     14827 my $attrs = @kids && $kids[0]->type =~ /^(?:anchor|index)$/
437             ? $self->get_anchored_paragraph_attrs( shift @kids )
438             : '';
439              
440             # inlined emit_kids() here to reflect any anchor manipulation
441 481         1133 my $content = join '', map { $_->emit( @_ ) } @kids;
  1439         3529  
442 481         3008 return "<p$attrs>" . $content . qq|</p>\n\n|;
443             }
444              
445             sub get_anchored_paragraph_attrs
446             {
447 26     26 0 95 my ($self, $tag) = @_;
448 26         874 my $type = $tag->type;
449              
450 26 50       217 if ($type eq 'anchor')
    50          
451             {
452 0         0 my $content = $tag->get_anchor;
453 0         0 return qq| id="$content"|;
454             }
455             elsif ($type eq 'index')
456             {
457 26         119 my $content = $tag->get_anchor . $tag->id;
458 26         129 return qq| id="$content"|;
459             }
460             }
461              
462             my %parent_items =
463             (
464             text_list => [ qq|<ul>\n\n|, q|</ul>| ],
465             bullet_list => [ qq|<ul>\n\n|, q|</ul>| ],
466             bullet_item => [ qq|<li>|, q|</li>| ],
467             number_list => [ qq|<ol>\n\n|, q|</ol>| ],
468             );
469              
470             while (my ($tag, $values) = each %parent_items)
471             {
472             my $sub = sub
473             {
474 230     230   405 my $self = shift;
        230      
        230      
        230      
475 230         695 return $values->[BEFORE] . $self->emit_kids( @_ ) . $values->[AFTER]
476             . "\n\n";
477             };
478              
479 16     16   137 do { no strict 'refs'; *{ 'emit_' . $tag } = $sub };
  16         54  
  16         13977  
480             }
481              
482             sub emit_block
483             {
484 83     83 0 205 my $self = shift;
485 83 100       2738 my $title = $self->title ? $self->title->emit_kids : '';
486 83         2884 my $target = $self->target;
487              
488 83 100       2880 if (my $environment = $self->emit_environments->{$target})
    100          
489             {
490 2         9 $target = $environment;
491             }
492             elsif (my $meth = $self->can( 'emit_' . $target))
493             {
494 55         233 return $self->$meth( @_ );
495             }
496              
497 28         968 return $self->make_basic_block( $self->target, $title, @_ );
498             }
499              
500             sub emit_html
501             {
502 13     13 0 46 my $self = shift;
503 13         65 return $self->emit_kids( encode => 'none' );
504             }
505              
506             sub make_basic_block
507             {
508 30     30 0 126 my ($self, $target, $title, @rest) = @_;
509              
510 30         119 $title = $self->make_block_title( $title );
511              
512 30         164 return qq|<div class="$target">\n$title|
513             . $self->emit_kids( @rest )
514             . qq|</div>|;
515             }
516              
517             sub make_block_title
518             {
519 84     84 0 221 my ($self, $title) = @_;
520              
521 84 100 100     648 return '' unless defined $title and length $title;
522 28         152 return qq|<p class="title">$title</p>\n|;
523             }
524              
525             sub emit_index
526             {
527 196     196 0 344 my $self = shift;
528 196         478 my $content = $self->get_anchor;
529 196 100       8386 $content .= $self->id if $self->type eq 'index';
530              
531 196         737 return qq|<a name="$content"></a>|;
532             }
533              
534             sub emit_index_link
535             {
536 17     17 0 36 my $self = shift;
537 17         651 my $id = $self->id;
538 17         64 my $frag = $self->get_anchor . $id;
539 17         849 my $file = $self->link;
540 17         170 return qq|<a href="$file#$frag">$id</a>|;
541             }
542              
543             sub emit_table
544             {
545 13     13 0 45 my $self = shift;
546 13 50       493 my $title = $self->title ? $self->title->emit_kids : '';
547              
548 13         54 my $content = qq|<table>\n|;
549 13 50       109 $content .= qq|<caption>$title</caption>\n| if $title;
550 13         63 $content .= $self->emit_kids;
551 13         54 $content .= qq|</table>\n\n|;
552              
553 13         1113 return $content;
554             }
555              
556             sub emit_headrow
557             {
558 13     13 0 38 my $self = shift;
559              
560             # kids should be cells
561 13         36 my $content = '<tr>';
562              
563 13         35 for my $kid (@{ $self->children })
  13         585  
564             {
565 26         106 $content .= '<th>' . $kid->emit_kids . '</th>';
566             }
567              
568 13         68 return $content . "</tr>\n";
569             }
570              
571             sub emit_row
572             {
573 26     26 0 1795 my $self = shift;
574              
575 26         117 return '<tr>' . $self->emit_kids . qq|</tr>\n|;
576             }
577              
578             sub emit_cell
579             {
580 52     52 0 197 my $self = shift;
581 52         130 return '<td>' . $self->emit_kids . qq|</td>\n|;
582             }
583              
584             sub emit_figure
585             {
586 13     13 0 51 my $self = shift;
587 13         552 my $caption = $self->caption;
588 13         116 my $anchor = $self->anchor;
589 13 50       93 my $id = defined $anchor ? ' id="' . $anchor->get_anchor . '"' : '';
590 13         331 my $file = $self->file->emit_kids;
591 13         63 my $content = qq|<p$id>|;
592              
593 13 50       82 $content .= $anchor if $anchor;
594 13         55 $content .= qq|<img src="$file" />|;
595 13 50       73 $content .= qq|<br />\n<em>$caption</em>| if $caption;
596 13         35 $content .= qq|</p>\n\n|;
597              
598 13         47 return $content;
599             }
600              
601             1;
602              
603             __END__
604              
605             =pod
606              
607             =encoding UTF-8
608              
609             =head1 NAME
610              
611             Pod::PseudoPod::DOM::Role::HTML - an HTML formatter role for PseudoPod DOM trees
612              
613             =head1 VERSION
614              
615             version 1.20210620.2040
616              
617             =head1 AUTHOR
618              
619             chromatic <chromatic@wgz.org>
620              
621             =head1 COPYRIGHT AND LICENSE
622              
623             This software is copyright (c) 2021 by chromatic.
624              
625             This is free software; you can redistribute it and/or modify it under
626             the same terms as the Perl 5 programming language system itself.
627              
628             =cut