File Coverage

blib/lib/Pod/Tree/HTML.pm
Criterion Covered Total %
statement 322 332 96.9
branch 111 122 90.9
condition 8 11 72.7
subroutine 51 54 94.4
pod 8 13 61.5
total 500 532 93.9


line stmt bran cond sub pod time code
1             package Pod::Tree::HTML;
2 12     12   2098975 use 5.006;
  12         41  
3 12     12   69 use strict;
  12         33  
  12         262  
4 12     12   64 use warnings;
  12         39  
  12         422  
5              
6             # Copyright (c) 1999-2007 by Steven McDougall. This module is free
7             # software; you can redistribute it and/or modify it under the same
8             # terms as Perl itself.
9              
10 12     12   2833 use HTML::Stream;
  12         14843  
  12         567  
11 12     12   1486 use IO::File;
  12         26186  
  12         1611  
12 12     12   5800 use IO::String;
  12         29039  
  12         213  
13 12     12   2005 use Pod::Tree;
  12         30  
  12         138  
14 12     12   7769 use Text::Template;
  12         53565  
  12         629  
15              
16 12     12   4918 use Pod::Tree::BitBucket;
  12         32  
  12         196  
17 12     12   4498 use Pod::Tree::StrStream;
  12         29  
  12         188  
18 12     12   4678 use Pod::Tree::HTML::LinkMap;
  12         26  
  12         195  
19              
20 12     12   394 use constant BGCOLOR => '#ffffff';
  12         28  
  12         1616  
21 12     12   69 use constant TEXT => '#000000';
  12         24  
  12         46763  
22              
23             our $VERSION = '1.29';
24              
25             sub new {
26 60     60 1 88101 my ( $class, $source, $dest, %options ) = @_;
27 60 50       176 defined $dest or die "Pod::Tree::HTML::new: not enough arguments\n";
28              
29 60         147 my $tree = _resolve_source($source);
30 60         185 my ( $fh, $stream ) = _resolve_dest( $dest, $tree, \%options );
31              
32 60         1184 my $options = {
33             bgcolor => BGCOLOR,
34             depth => 0,
35             hr => 1,
36             link_map => Pod::Tree::HTML::LinkMap->new(),
37             text => TEXT,
38             toc => 1,
39             };
40              
41 60         162 my $HTML = {
42             tree => $tree,
43             root => $tree->get_root,
44             stream => $stream,
45             fh => $fh,
46             text_method => 'text',
47             options => $options,
48             };
49              
50 60         170 bless $HTML, $class;
51              
52 60         169 $HTML->set_options(%options);
53 60         158 $HTML;
54             }
55              
56             sub _resolve_source {
57 60     60   88 my $source = shift;
58 60         102 my $ref = ref $source;
59 60         181 local *isa = \&UNIVERSAL::isa;
60              
61 60 100       333 isa( $source, 'Pod::Tree' ) and return $source;
62              
63 51         210 my $tree = Pod::Tree->new;
64 51 100       212 not $ref and $tree->load_file($source);
65 51 100       385 isa( $source, 'IO::File' ) and $tree->load_fh($source);
66 51 100       127 $ref eq 'SCALAR' and $tree->load_string($$source);
67 51 100       109 $ref eq 'ARRAY' and $tree->load_paragraphs($source);
68              
69 51 50       168 $tree->loaded
70             or die "Pod::Tree::HTML::_resolve_source: Can't load POD from $source\n";
71              
72 51         178 $tree;
73             }
74              
75             sub _resolve_dest {
76 60     60   121 my ( $dest, $tree, $options ) = @_;
77              
78             $tree->has_pod
79             or $options->{empty}
80 60 100 100     162 or return ( undef, Pod::Tree::BitBucket->new );
81              
82 59         152 local *isa = \&UNIVERSAL::isa;
83 59         119 local *can = \&UNIVERSAL::can;
84              
85 59 100       203 isa( $dest, 'HTML::Stream' ) and return ( undef, $dest );
86 58 100       173 isa( $dest, 'IO::File' ) and return ( $dest, HTML::Stream->new($dest) );
87 51 100       188 can( $dest, 'print' ) and return ( $dest, HTML::Stream->new($dest) );
88              
89 50 100       123 if ( ref $dest eq 'SCALAR' ) {
90 42         207 my $fh = IO::String->new($$dest);
91 42         1877 return ( $fh, HTML::Stream->new($fh) );
92             }
93              
94 8 50 33     35 if ( ref $dest eq '' and $dest ) {
95 8         32 my $fh = IO::File->new;
96 8 50       304 $fh->open( $dest, '>' ) or die "Pod::Tree::HTML::new: Can't open $dest: $!\n";
97 8         956 return ( $fh, HTML::Stream->new($fh) );
98             }
99              
100 0         0 die "Pod::Tree::HTML::_resolve_dest: Can't write HTML to $dest\n";
101             }
102              
103             sub set_options {
104 118     118 1 361 my ( $html, %options ) = @_;
105              
106 118         160 my ( $key, $value );
107 118         317 while ( ( $key, $value ) = each %options ) {
108 61         350 $html->{options}{$key} = $value;
109             }
110             }
111              
112             sub get_options {
113 0     0 1 0 my ( $html, @options ) = @_;
114              
115 0         0 map { $html->{options}{$_} } @options;
  0         0  
116             }
117              
118 0     0 0 0 sub get_stream { shift->{stream} }
119              
120             sub translate {
121 58     58 1 256 my ( $html, $template ) = @_;
122              
123 58 100       113 if ($template) {
124 12         31 $html->_template($template);
125             }
126             else {
127 46         96 $html->_translate;
128             }
129             }
130              
131             sub _translate {
132 46     46   56 my $html = shift;
133 46         63 my $stream = $html->{stream};
134 46         73 my $bgcolor = $html->{options}{bgcolor};
135 46         68 my $text = $html->{options}{text};
136 46         91 my $title = $html->_make_title;
137 46         81 my $base = $html->{options}{base};
138 46         62 my $css = $html->{options}{css};
139              
140 46         936 $stream->HTML->HEAD;
141              
142 46 100       8969 defined $title and $stream->TITLE->text($title)->_TITLE;
143 46 100       6874 defined $base and $stream->BASE( href => $base );
144 46 50       593 defined $css and $stream->LINK(
145             href => $css,
146             type => "text/css",
147             rel => "stylesheet"
148             );
149              
150 46         574 $stream->_HEAD->BODY( BGCOLOR => $bgcolor, TEXT => $text );
151              
152 46         8192 $html->emit_toc;
153 46         959 $html->emit_body;
154              
155 46         920 $stream->nl->_BODY->_HTML;
156             }
157              
158             sub _template {
159 12     12   28 my ( $html, $tSource ) = @_;
160              
161 12         76 my $fh = $html->{fh};
162 12         79 my $sStream = Pod::Tree::StrStream->new;
163 12         33 $html->{stream} = HTML::Stream->new($sStream);
164              
165 12         180 our $bgcolor = $html->{options}{bgcolor};
166 12         21 our $text = $html->{options}{text};
167 12         35 our $title = $html->_make_title;
168 12         28 our $base = $html->{options}{base};
169 12         20 our $css = $html->{options}{css};
170              
171 12         36 $html->emit_toc;
172 12         59 our $toc = $sStream->get;
173              
174 12         34 $html->emit_body;
175 12         35 our $body = $sStream->get;
176              
177 12 50       103 my $template = Text::Template->new( SOURCE => $tSource )
178             or die "Can't create Text::Template object: $Text::Template::ERROR\n";
179              
180 12 50       3427 $template->fill_in( OUTPUT => $fh )
181             or die $Text::Template::ERROR;
182             }
183              
184             sub _make_title {
185 58     58   87 my $html = shift;
186              
187 58         106 my $title = $html->{options}{title};
188 58 50       113 defined $title and return $title;
189              
190 58         150 my $children = $html->{root}->get_children;
191 58         74 my $node1;
192 58         80 my $i = 0;
193 58         120 for my $child (@$children) {
194 144 100       263 $child->is_pod or next;
195 112 100       227 $i++ and $node1 = $child;
196 112 100       202 $node1 and last;
197             }
198              
199 58 100       115 $node1 or return undef; ##no critic (ProhibitExplicitReturnUndef)
200              
201 56         122 my $text = $node1->get_deep_text;
202 56         197 ($title) = split m(\s+-), $text;
203              
204 56 100       120 $title or return undef; ##no critic (ProhibitExplicitReturnUndef)
205 50         259 $title =~ s(\s+$)();
206              
207 50         138 $title;
208             }
209              
210             sub emit_toc {
211 59     59 1 156 my $html = shift;
212 59 100       140 $html->{options}{toc} or return;
213              
214 22         33 my $root = $html->{root};
215 22         67 my $nodes = $root->get_children;
216 22         64 my @nodes = @$nodes;
217              
218 22         64 $html->_emit_toc_1( \@nodes );
219              
220 22 100       806 $html->{options}{hr} > 0 and $html->{stream}->HR;
221             }
222              
223             sub _emit_toc_1 {
224 22     22   44 my ( $html, $nodes ) = @_;
225 22         41 my $stream = $html->{stream};
226              
227 22         424 $stream->UL;
228              
229 22         835 while (@$nodes) {
230 130         644 my $node = $nodes->[0];
231 130 100       211 $node->is_c_head2 and $html->_emit_toc_2($nodes), next;
232 116 100       213 $node->is_c_head1 and $html->_emit_toc_item($node);
233 116         848 shift @$nodes;
234             }
235              
236 22         353 $stream->_UL;
237             }
238              
239             sub _emit_toc_2 {
240 14     14   33 my ( $html, $nodes ) = @_;
241 14         24 my $stream = $html->{stream};
242              
243 14         220 $stream->UL;
244              
245 14         465 while (@$nodes) {
246 156         178 my $node = $nodes->[0];
247 156 100       232 $node->is_c_head1 and last;
248 151 100       234 $node->is_c_head2 and $html->_emit_toc_item($node);
249 151         686 shift @$nodes;
250             }
251              
252 14         190 $stream->_UL;
253             }
254              
255             sub _emit_toc_item {
256 69     69   109 my ( $html, $node ) = @_;
257 69         81 my $stream = $html->{stream};
258 69         131 my $target = $html->_make_anchor($node);
259              
260 69         982 $stream->LI->A( HREF => "#$target" );
261 69         3540 $html->_emit_children($node);
262 69         978 $stream->_A;
263             }
264              
265             sub emit_body {
266 59     59 1 91 my $html = shift;
267 59         84 my $root = $html->{root};
268 59         126 $html->_emit_children($root);
269             }
270              
271             sub _emit_children {
272 1751     1751   2452 my ( $html, $node ) = @_;
273              
274 1751         2905 my $children = $node->get_children;
275              
276 1751         2765 for my $child (@$children) {
277 3487         7876 $html->_emit_node($child);
278             }
279             }
280              
281             sub _emit_siblings {
282 252     252   367 my ( $html, $node ) = @_;
283              
284 252         473 my $siblings = $node->get_siblings;
285              
286 252 100 100     912 if ( @$siblings == 1 and $siblings->[0]{type} eq 'ordinary' ) {
287              
288             # don't put

around a single ordinary paragraph
289 210         355 $html->_emit_children( $siblings->[0] );
290             }
291             else {
292 42         73 for my $sibling (@$siblings) {
293 72         112 $html->_emit_node($sibling);
294             }
295             }
296              
297             }
298              
299             sub _emit_node {
300 3559     3559   4597 my ( $html, $node ) = @_;
301 3559         4632 my $type = $node->{type};
302              
303 3559         4584 for ($type) {
304 3559 100       5711 /command/ and $html->_emit_command($node);
305 3559 100       24456 /for/ and $html->_emit_for($node);
306 3559 100       6109 /item/ and $html->_emit_item($node);
307 3559 100       9375 /list/ and $html->_emit_list($node);
308 3559 100       10209 /ordinary/ and $html->_emit_ordinary($node);
309 3559 100       18096 /sequence/ and $html->_emit_sequence($node);
310 3559 100       24556 /text/ and $html->_emit_text($node);
311 3559 100       43513 /verbatim/ and $html->_emit_verbatim($node);
312             }
313             }
314              
315             my %HeadTag = (
316             head1 => { 'open' => 'H1', 'close' => '_H1', level => 1 },
317             head2 => { 'open' => 'H2', 'close' => '_H2', level => 2 },
318             head3 => { 'open' => 'H3', 'close' => '_H3', level => 3 },
319             head4 => { 'open' => 'H4', 'close' => '_H4', level => 4 }
320             );
321              
322             sub _emit_command {
323 230     230   312 my ( $html, $node ) = @_;
324 230         288 my $stream = $html->{stream};
325 230         416 my $command = $node->get_command;
326 230         414 my $head_tag = $HeadTag{$command};
327 230 100       396 $head_tag or return;
328 212         358 my $anchor = $html->_make_anchor($node);
329              
330 212         523 $html->_emit_hr( $head_tag->{level} );
331              
332 212         681 my $tag;
333 212         294 $tag = $head_tag->{'open'};
334 212         3220 $stream->$tag()->A( NAME => $anchor );
335              
336 212         20338 $html->_emit_children($node);
337              
338 212         321 $tag = $head_tag->{'close'};
339 212         3038 $stream->_A->$tag();
340             }
341              
342             sub _emit_hr {
343 212     212   294 my ( $html, $level ) = @_;
344 212 100       443 $html->{options}{hr} > $level or return;
345 8 100       19 $html->{skip_first}++ or return;
346 6         120 $html->{stream}->HR;
347             }
348              
349             sub _emit_for {
350 24     24   39 my ( $html, $node ) = @_;
351              
352 24         51 my $interpreter = lc $node->get_arg;
353 24         50 my $emit = "_emit_for_$interpreter";
354              
355 24 100       115 $html->$emit($node) if $html->can($emit);
356             }
357              
358             sub _emit_for_html {
359 12     12   23 my ( $html, $node ) = @_;
360              
361 12         17 my $stream = $html->{stream};
362 12         192 $stream->P;
363 12         624 $stream->io->print( $node->get_text );
364 12         282 $stream->_P;
365             }
366              
367             sub _emit_for_image {
368 6     6   15 my ( $html, $node ) = @_;
369              
370 6         10 my $stream = $html->{stream};
371 6         15 my $link = $node->get_text;
372 6         28 $link =~ s(\s+$)();
373              
374 6         83 $stream->IMG( src => $link );
375             }
376              
377             sub _emit_item {
378 252     252   322 my ( $html, $node ) = @_;
379              
380 252         291 my $stream = $html->{stream};
381 252         421 my $item_type = $node->get_item_type;
382 252         394 for ($item_type) {
383 252 100       411 /bullet/ and do {
384 120         1579 $stream->LI();
385 120         4202 $html->_emit_siblings($node);
386 120         2375 $stream->_LI();
387             };
388              
389 252 100       5677 /number/ and do {
390 48         666 $stream->LI();
391 48         1562 $html->_emit_siblings($node);
392 48         689 $stream->_LI();
393             };
394              
395 252 100       2396 /text/ and do {
396 84         145 my $anchor = $html->_make_anchor($node);
397 84         1184 $stream->DT->A( NAME => "$anchor" );
398 84         6601 $html->_emit_children($node);
399 84         1211 $stream->_A->_DT->DD;
400 84         10922 $html->_emit_siblings($node);
401 84         1175 $stream->_DD;
402             };
403             }
404              
405             }
406              
407             my %ListTag = (
408             bullet => { 'open' => 'UL', 'close' => '_UL' },
409             number => { 'open' => 'OL', 'close' => '_OL' },
410             text => { 'open' => 'DL', 'close' => '_DL' }
411             );
412              
413             sub _emit_list {
414 114     114   144 my ( $html, $node ) = @_;
415 114         139 my ( $list_tag, $tag ); # to quiet -w, see beloew
416              
417 114         153 my $stream = $html->{stream};
418 114         230 my $list_type = $node->get_list_type;
419              
420 114 100       209 $list_type and $list_tag = $ListTag{$list_type};
421 114 100       203 $list_tag and $tag = $list_tag->{'open'};
422 114 100       1512 $tag and $stream->$tag();
423              
424 114         5773 $html->_emit_children($node);
425              
426 114 100       205 $list_tag and $tag = $list_tag->{'close'};
427 114 100       1422 $tag and $stream->$tag();
428             }
429              
430             sub _emit_ordinary {
431 474     474   635 my ( $html, $node ) = @_;
432 474         590 my $stream = $html->{stream};
433              
434 474         6292 $stream->P;
435 474         27296 $html->_emit_children($node);
436 474         6718 $stream->_P;
437             }
438              
439             sub _emit_sequence {
440 567     567   709 my ( $html, $node ) = @_;
441              
442 567         956 for ( $node->get_letter ) {
443 567 100       1892 /I|B|C|F/ and $html->_emit_element($node), last;
444 272 100       573 /S/ and $html->_emit_nbsp($node), last;
445 260 100       655 /L/ and $html->_emit_link($node), last;
446 38 50       155 /X/ and $html->_emit_index($node), last;
447 0 0       0 /E/ and $html->_emit_entity($node), last;
448             }
449             }
450              
451             my %ElementTag = (
452             I => { 'open' => 'I', 'close' => '_I' },
453             B => { 'open' => 'B', 'close' => '_B' },
454             C => { 'open' => 'CODE', 'close' => '_CODE' },
455             F => { 'open' => 'I', 'close' => '_I' }
456             );
457              
458             sub _emit_element {
459 295     295   463 my ( $html, $node ) = @_;
460              
461 295         423 my $letter = $node->get_letter;
462 295         382 my $stream = $html->{stream};
463              
464 295         297 my $tag;
465 295         433 $tag = $ElementTag{$letter}{'open'};
466 295         4370 $stream->$tag();
467 295         11051 $html->_emit_children($node);
468 295         452 $tag = $ElementTag{$letter}{'close'};
469 295         4221 $stream->$tag();
470             }
471              
472             sub _emit_nbsp {
473 12     12   31 my ( $html, $node ) = @_;
474              
475 12         24 my $old_method = $html->{text_method};
476 12         18 $html->{text_method} = 'text_nbsp';
477 12         31 $html->_emit_children($node);
478 12         23 $html->{text_method} = $old_method;
479             }
480              
481             sub _emit_link {
482 222     222   319 my ( $html, $node ) = @_;
483              
484 222         291 my $stream = $html->{stream};
485 222         394 my $target = $node->get_target;
486 222         380 my $domain = $target->get_domain;
487 222         376 my $method = "make_${domain}_URL";
488 222         424 my $url = $html->$method($target);
489              
490 222         3728 $stream->A( HREF => $url );
491 222         9946 $html->_emit_children($node);
492 222         3196 $stream->_A;
493             }
494              
495             sub make_POD_URL {
496 206     206 0 303 my ( $html, $target ) = @_;
497              
498 206         271 my $link_map = $html->{options}{link_map};
499              
500 206 100       719 return $link_map->url( $html, $target ) if $link_map->can("url");
501              
502 25         46 $html->make_mapped_URL($target);
503             }
504              
505             sub make_mapped_URL {
506 25     25 0 30 my ( $html, $target ) = @_;
507              
508 25         37 my $link_map = $html->{options}{link_map};
509 25   50     68 my $base = $html->{options}{base} || '';
510 25         46 my $page = $target->get_page;
511 25         45 my $section = $target->get_section;
512 25         37 my $depth = $html->{options}{depth};
513              
514 25         55 ( $base, $page, $section ) = $link_map->map( $base, $page, $section, $depth );
515              
516 25         155 $base =~ s(/$)();
517 25 100       45 $page .= '.html' if $page;
518 25         73 my $fragment = $html->escape_2396($section);
519 25         47 my $url = $html->assemble_url( $base, $page, $fragment );
520              
521 25         48 $url;
522             }
523              
524             sub make_HTTP_URL {
525 16     16 0 29 my ( $html, $target ) = @_;
526              
527 16         35 $target->get_page;
528             }
529              
530             sub _emit_index {
531 38     38   58 my ( $html, $node ) = @_;
532              
533 38         65 my $stream = $html->{stream};
534 38         69 my $anchor = $html->_make_anchor($node);
535 38         608 $stream->A( NAME => $anchor )->_A;
536             }
537              
538             sub _emit_entity {
539 0     0   0 my ( $html, $node ) = @_;
540              
541 0         0 my $stream = $html->{stream};
542 0         0 my $entity = $node->get_deep_text;
543 0         0 $stream->ent($entity);
544             }
545              
546             sub _emit_text {
547 1855     1855   2255 my ( $html, $node ) = @_;
548 1855         2115 my $stream = $html->{stream};
549 1855         2990 my $text = $node->get_text;
550 1855         2403 my $text_method = $html->{text_method};
551              
552 1855         3863 $stream->$text_method($text);
553             }
554              
555             sub _emit_verbatim {
556 29     29   53 my ( $html, $node ) = @_;
557 29         46 my $stream = $html->{stream};
558 29         58 my $text = $node->get_text;
559 29         126 $text =~ s(\n\n$)();
560              
561 29         408 $stream->PRE->text($text)->_PRE;
562             }
563              
564             sub _make_anchor {
565 403     403   542 my ( $html, $node ) = @_;
566 403         719 my $text = $node->get_deep_text;
567 403         1194 $text =~ s( \s*\n\s*/ )( )xg; # close line breaks
568 403         1434 $text =~ s( ^\s+ | \s+$ )()xg; # clip leading and trailing WS
569 403         825 $html->escape_2396($text);
570             }
571              
572 206     206 0 538 sub bin { oct '0b' . join '', @_ }
573              
574             my @LinkFormat = (
575             sub { my ( $b, $p, $f ) = @_; "" },
576             sub { my ( $b, $p, $f ) = @_; "#$f" },
577             sub { my ( $b, $p, $f ) = @_; "$p" },
578             sub { my ( $b, $p, $f ) = @_; "$p#$f" },
579             sub { my ( $b, $p, $f ) = @_; "$b/" },
580             sub { my ( $b, $p, $f ) = @_; "#$f" },
581             sub { my ( $b, $p, $f ) = @_; "$b/$p" },
582             sub { my ( $b, $p, $f ) = @_; "$b/$p#$f" }
583             );
584              
585             sub assemble_url {
586 206     206 1 385 my ( $html, $base, $page, $fragment ) = @_;
587              
588 206 100       321 my $i = bin map { length($_) ? 1 : 0 } ( $base, $page, $fragment );
  618         1088  
589 206         474 my $url = $LinkFormat[$i]( $base, $page, $fragment );
590              
591 206         356 $url;
592             }
593              
594             sub escape_2396 {
595 609     609 1 931 my ( $html, $text ) = @_;
596 609         1270 $text =~ s(([^\w\-.!~*'()]))(sprintf("%%%02x", ord($1)))eg;
  459         1657  
597 609         1194 $text;
598             }
599              
600             __END__