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   11009 use strict;
  16         33  
  16         489  
5 16     16   78 use warnings;
  16         33  
  16         417  
6              
7 16     16   81 use Moose::Role;
  16         31  
  16         149  
8              
9 16     16   93945 use HTML::Entities;
  16         73978  
  16         1446  
10 16     16   151 use Scalar::Util 'blessed';
  16         32  
  16         795  
11 16     16   666 use MIME::Base64 'encode_base64url';
  16         807  
  16         26967  
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 659 my $self = shift;
21 425         1178 my $anchor = $self->emit_kids( encode => 'index_anchor' );
22 425         1343 return encode_base64url( $anchor );
23             }
24              
25             sub get_link_for_anchor
26             {
27 53     53 0 107 my ($self, $anchor) = @_;
28 53         1679 my $anchors = $self->anchors;
29              
30 53 50       200 return unless my $heading = $anchors->{$anchor};
31 53         1621 my $filename = $heading->link;
32 53         136 my $target = $heading->get_anchor;
33 53         599 my $title = $heading->get_link_text;
34              
35 53         184 return $filename, $target, $title;
36             }
37              
38             sub resolve_anchors
39             {
40 14     14 0 96 my $self = shift;
41 14         463 my $anchors = $self->anchors;
42              
43 14         34 for my $anchor (@{ $self->anchor })
  14         445  
44             {
45 53         165 my $a = $anchor->emit_kids;
46 53         159 $anchors->{$anchor->emit_kids} = $anchor;
47             }
48             }
49              
50             sub get_index_entries
51             {
52 20     20 0 188 my ($self, $seen) = @_;
53 20   50     151 $seen ||= {};
54              
55 20         39 my @entries;
56              
57 20         48 for my $entry (@{ $self->index })
  20         707  
58             {
59 199         486 my $text = $entry->emit_kids( encode => 'index_anchor' );
60 199         6222 $entry->id( ++$seen->{ $text } );
61 199         491 push @entries, $entry;
62             }
63              
64 20         163 return @entries;
65             }
66              
67 50     50 0 499 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 313 my ($self, %args) = @_;
85 143         242 my $content = eval { $self->emit_kids };
  143         334  
86              
87 143 50       337 return '' unless defined $content;
88              
89 143 50       653 if (my ($char, $class) = $content =~ /(\w)(\w+)/)
90             {
91 143 100       531 return $characters{$class}->($char) if exists $characters{$class};
92             }
93              
94 52   50     137 $args{encode} ||= '';
95 52         228 my $char = Pod::Escapes::e2char( $content );
96 52 100       1015 return $char if $args{encode} =~ /^(index_|id$)/;
97              
98 26         87 return $self->handle_encoding( $char );
99             }
100              
101             sub emit
102             {
103 5301     5301 0 8237 my $self = shift;
104 5301         137126 my $type = $self->type;
105 5301         9502 my $emit = 'emit_' . $type;
106              
107 5301         13221 $self->$emit( @_ );
108             }
109              
110             sub emit_document
111             {
112 41     41 0 107 my $self = shift;
113              
114 41 100       1799 return $self->emit_body if $self->add_body_tags;
115 40         204 return $self->emit_kids( @_ );
116             }
117              
118             sub extract_headings
119             {
120 1     1 0 3 my ($self, %args) = @_;
121 1         3 my @headings;
122              
123 1         2 for my $kid (@{ $self->children })
  1         41  
124             {
125 47 100       1366 next unless $kid->type eq 'header';
126 8 100       37 next if $kid->exclude_from_toc( $args{max_depth} );
127 5         15 push @headings, $kid;
128             }
129              
130 1         3 return \@headings;
131             }
132              
133             sub emit_toc
134             {
135 1     1 0 156 my $self = shift;
136 1         6 my $headings = $self->extract_headings;
137              
138 1         40 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     8 $args{indent} ||= '';
145              
146 1         3 my $toc = '';
147              
148 1         3 for my $heading (@$headings)
149             {
150 5         9 $toc .= $args{indent};
151              
152 5 50       23 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         14 $toc .= qq|</li>\n|;
167             }
168              
169 1         7 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         50 $content =~ s/^\*//;
181 5         21 return qq|<a href="$filename#$frag">$content</a>|;
182             }
183              
184             sub emit_body
185             {
186 1     1 0 3 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 3470 my $self = shift;
204 2258         3033 join '', map { $_->emit( @_ ) } @{ $self->children };
  3570         22409  
  2258         62545  
205             }
206              
207             sub emit_header
208             {
209 114     114 0 201 my $self = shift;
210 114         277 my $content = $self->emit_kids( @_ );
211 114         3349 my $id_node = $self->anchor;
212 114 100       738 my $id = $id_node ? $id_node->get_anchor : $self->get_anchor;
213 114         1247 my $no_toc = $content =~ s/^\*//;
214 114         3383 my $level = 'h' . ($self->level + 1);
215 114 100       384 my $anchor = $id_node ? $self->emit_index( @_ ) : '';
216              
217 114         589 return qq|<$level id="$id">$anchor$content</$level>\n\n|;
218             }
219              
220             sub emit_plaintext
221             {
222 3140     3140 0 6042 my ($self, %args) = @_;
223 3140         87252 my $content = $self->content;
224 3140 50       6231 $content = '' unless defined $content;
225 3140         7126 $self->handle_encoding( $content, %args );
226             }
227              
228             sub handle_encoding
229             {
230 3166     3166 0 6096 my ($self, $content, %args) = @_;
231              
232 3166 100       6384 if (my $encode = $args{encode})
233             {
234 1212         2041 my $method = 'encode_' . $encode;
235 1212         3244 return $self->$method( $content, %args );
236             }
237              
238 1954         3821 return $self->encode_text( $content, %args );
239             }
240              
241 26     26 0 163 sub encode_none { $_[1] }
242              
243             sub encode_split
244             {
245 39     39 0 146 my ($self, $content, %args) = @_;
246 39         82 my $target = $args{target};
247             return join $args{joiner},
248 39         634 map { $self->encode_text( $_ ) } split /\s*\Q$target\E\s*/, $content;
  78         160  
249             }
250              
251             sub encode_text
252             {
253 2032     2032 0 3320 my ($self, $text) = @_;
254              
255 16     16   173 use Carp;
  16         40  
  16         18487  
256 2032 50       3462 unless (defined $text)
257             {
258 0         0 confess 'no text';
259             }
260 2032         4249 $text = encode_entities($text);
261 2032         25781 $text =~ s/\s*---\s*/&#8213;/g;
262 2032         2948 $text =~ s/\s*--\s*/&mdash;/g;
263              
264 2032         8236 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 1433 my ($self, $text) = @_;
278              
279 813         1353 $text =~ s/^\*//;
280 813         2627 $text =~ s/[\s"]//g;
281              
282 813         3874 return $text;
283             }
284              
285             sub encode_index_key
286             {
287 17     17 0 38 my ($self, $text) = @_;
288 17         90 $text =~ s/^\s+|\s+$//g;
289 17         203 return $text;
290             }
291              
292             sub encode_verbatim_text
293             {
294 317     317 0 616 my ($self, $text) = @_;
295 317         723 return encode_entities( $text );
296             }
297              
298             sub emit_literal
299             {
300 13     13 0 35 my $self = shift;
301 13         27 my @kids;
302              
303 13 50       381 if (my $title = $self->title)
304             {
305 13         57 my $target = $title->emit_kids( encode => 'none' );
306             @kids = map
307             {
308 13         84 $_->emit_kids(
309             encode => 'split', target => $target, joiner => "</p>\n\n<p>",
310             )
311 13         33 } @{ $self->children };
  13         421  
312             }
313             else
314             {
315 0         0 @kids = map { $_->emit_kids( @_ ) } @{ $self->children };
  0         0  
  0         0  
316             }
317              
318 13         98 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 2 my $self = shift;
326 1         5 return qq|<a name="| . $self->get_anchor . qq|"></a>|;
327             }
328              
329             sub emit_number_item
330             {
331 41     41 0 81 my $self = shift;
332 41         1312 my $marker = $self->marker;
333 41 50       158 my $number = $marker ? qq| number="$marker"| : '';
334 41         149 return "<li$number>" . $self->emit_kids . "</li>\n\n";
335             }
336              
337             sub emit_text_item
338             {
339 145     145 0 220 my $self = shift;
340 145         3896 my $kids = $self->children;
341 145 50       344 return "<li></li>\n\n" unless @$kids;
342              
343 145         266 my $first = shift @$kids;
344 145 100       344 return '<li>' . $first->emit( @_ ) . qq|</li>\n\n| unless @$kids;
345              
346             return "<li><p>" . $first->emit . "</p>\n\n"
347 106         256 . join( '', map { $_->emit } @$kids ) . "</li>\n\n";
  106         266  
348             }
349              
350             sub emit_verbatim
351             {
352 42     42 0 101 my $self = shift;
353 42         130 return "<pre><code>" . $self->emit_kids( encode => 'verbatim_text', @_ )
354             . "</code></pre>\n\n";
355             }
356              
357 150     150 0 430 sub emit_italics { shift->emit_tagged_kids( 'em', @_ ) }
358 211     211 0 487 sub emit_code { shift->emit_tagged_kids( 'code', @_ ) }
359 30     30 0 116 sub emit_bold { shift->emit_tagged_kids( 'strong', @_ ) }
360 14     14 0 209 sub emit_superscript { shift->emit_tagged_kids( 'sup', @_ ) }
361 14     14 0 67 sub emit_subscript { shift->emit_tagged_kids( 'sub', @_ ) }
362 40     40 0 148 sub emit_file { shift->emit_tagged_kids( 'em', @_ ) }
363              
364             sub emit_tagged_kids
365             {
366 459     459 0 1035 my ($self, $tag, %args) = @_;
367 459         1296 my $kids = $self->emit_kids( encode => 'verbatim_text', %args );
368 459   100     4965 $args{encode} ||= '';
369              
370 459 100       1471 return $kids if $args{encode} =~ /^(index_|id$)/;
371 273         1028 return qq|<$tag>$kids</$tag>|;
372             }
373              
374             sub emit_footnote
375             {
376 14     14 0 39 my $self = shift;
377 14         63 return ' <span class="footnote">' . $self->emit_kids . '</span>';
378             }
379              
380             sub emit_url
381             {
382 14     14 0 45 my $self = shift;
383 14         52 my $url = $self->emit_kids;
384 14         84 return qq|<a class="url" href="$url">$url</a>|;
385             }
386              
387             sub emit_link
388             {
389 53     53 0 97 my $self = shift;
390 53         127 my $anchor = $self->emit_kids;
391              
392 53         174 my ($file, $frag, $text) = $self->get_link_for_anchor( $anchor );
393 53         239 return qq|<a href="$file#$frag">$text</a>|;
394             }
395              
396 16     16   153 use constant { BEFORE => 0, AFTER => 1 };
  16         59  
  16         4662  
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   100 my $self = shift;
        56      
        56      
        56      
411 56         1589 my $title = $self->title;
412 56         1575 my $env = $self->emit_environments;
413              
414             return $self->make_basic_block( $env->{$tag}, $title, @_ )
415 56 100       173 if exists $env->{$tag};
416              
417             # deal with title somehow
418 54         186 return $values->[BEFORE]
419             . $self->make_block_title( $title )
420             . $self->emit_kids . $values->[AFTER]
421             . "\n\n";
422             };
423              
424 16     16   135 do { no strict 'refs'; *{ 'emit_' . $tag } = $sub };
  16         39  
  16         7782  
425             }
426              
427             my %invisibles = map { $_ => 1 } qw( index anchor );
428              
429             sub emit_paragraph
430             {
431 481     481 0 782 my $self = shift;
432 481         684 my @kids = @{ $self->children };
  481         12945  
433 481         936 my $has_visible_text = grep { ! exists $invisibles{ $_->type } } @kids;
  1465         36769  
434 481 50       1060 return $self->emit_kids( @_ ) unless $has_visible_text;
435              
436 481 100 66     12702 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         977 my $content = join '', map { $_->emit( @_ ) } @kids;
  1439         3085  
442 481         2574 return "<p$attrs>" . $content . qq|</p>\n\n|;
443             }
444              
445             sub get_anchored_paragraph_attrs
446             {
447 26     26 0 84 my ($self, $tag) = @_;
448 26         688 my $type = $tag->type;
449              
450 26 50       158 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         92 my $content = $tag->get_anchor . $tag->id;
458 26         108 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   386 my $self = shift;
        230      
        230      
        230      
475 230         578 return $values->[BEFORE] . $self->emit_kids( @_ ) . $values->[AFTER]
476             . "\n\n";
477             };
478              
479 16     16   124 do { no strict 'refs'; *{ 'emit_' . $tag } = $sub };
  16         46  
  16         12575  
480             }
481              
482             sub emit_block
483             {
484 83     83 0 167 my $self = shift;
485 83 100       2312 my $title = $self->title ? $self->title->emit_kids : '';
486 83         2440 my $target = $self->target;
487              
488 83 100       2408 if (my $environment = $self->emit_environments->{$target})
    100          
489             {
490 2         4 $target = $environment;
491             }
492             elsif (my $meth = $self->can( 'emit_' . $target))
493             {
494 55         208 return $self->$meth( @_ );
495             }
496              
497 28         772 return $self->make_basic_block( $self->target, $title, @_ );
498             }
499              
500             sub emit_html
501             {
502 13     13 0 33 my $self = shift;
503 13         49 return $self->emit_kids( encode => 'none' );
504             }
505              
506             sub make_basic_block
507             {
508 30     30 0 88 my ($self, $target, $title, @rest) = @_;
509              
510 30         91 $title = $self->make_block_title( $title );
511              
512 30         142 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 183 my ($self, $title) = @_;
520              
521 84 100 100     541 return '' unless defined $title and length $title;
522 28         133 return qq|<p class="title">$title</p>\n|;
523             }
524              
525             sub emit_index
526             {
527 196     196 0 311 my $self = shift;
528 196         399 my $content = $self->get_anchor;
529 196 100       6910 $content .= $self->id if $self->type eq 'index';
530              
531 196         619 return qq|<a name="$content"></a>|;
532             }
533              
534             sub emit_index_link
535             {
536 17     17 0 31 my $self = shift;
537 17         554 my $id = $self->id;
538 17         46 my $frag = $self->get_anchor . $id;
539 17         859 my $file = $self->link;
540 17         171 return qq|<a href="$file#$frag">$id</a>|;
541             }
542              
543             sub emit_table
544             {
545 13     13 0 39 my $self = shift;
546 13 50       414 my $title = $self->title ? $self->title->emit_kids : '';
547              
548 13         50 my $content = qq|<table>\n|;
549 13 50       71 $content .= qq|<caption>$title</caption>\n| if $title;
550 13         53 $content .= $self->emit_kids;
551 13         43 $content .= qq|</table>\n\n|;
552              
553 13         886 return $content;
554             }
555              
556             sub emit_headrow
557             {
558 13     13 0 31 my $self = shift;
559              
560             # kids should be cells
561 13         34 my $content = '<tr>';
562              
563 13         39 for my $kid (@{ $self->children })
  13         476  
564             {
565 26         89 $content .= '<th>' . $kid->emit_kids . '</th>';
566             }
567              
568 13         52 return $content . "</tr>\n";
569             }
570              
571             sub emit_row
572             {
573 26     26 0 1297 my $self = shift;
574              
575 26         108 return '<tr>' . $self->emit_kids . qq|</tr>\n|;
576             }
577              
578             sub emit_cell
579             {
580 52     52 0 157 my $self = shift;
581 52         108 return '<td>' . $self->emit_kids . qq|</td>\n|;
582             }
583              
584             sub emit_figure
585             {
586 13     13 0 31 my $self = shift;
587 13         468 my $caption = $self->caption;
588 13         97 my $anchor = $self->anchor;
589 13 50       79 my $id = defined $anchor ? ' id="' . $anchor->get_anchor . '"' : '';
590 13         299 my $file = $self->file->emit_kids;
591 13         50 my $content = qq|<p$id>|;
592              
593 13 50       72 $content .= $anchor if $anchor;
594 13         45 $content .= qq|<img src="$file" />|;
595 13 50       59 $content .= qq|<br />\n<em>$caption</em>| if $caption;
596 13         33 $content .= qq|</p>\n\n|;
597              
598 13         41 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.2032
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