File Coverage

blib/lib/Marek/Pod/HTML.pm
Criterion Covered Total %
statement 492 708 69.4
branch 135 284 47.5
condition 27 115 23.4
subroutine 32 41 78.0
pod 6 14 42.8
total 692 1162 59.5


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #############################################################################
3             # Pod/HTML.pm -- converts Pod to HTML
4             #
5             # Copyright (C) 1999,2000 by Marek Rouchal. All rights reserved.
6             # This package is free software; you can redistribute it and/or modify
7             # it under the same terms as Perl itself.
8             #############################################################################
9              
10             package Marek::Pod::HTML;
11              
12             =head1 NAME
13              
14             Marek::Pod::HTML - convert Perl POD documents to HTML
15              
16             =head1 SYNOPSIS
17              
18             use Marek::Pod::HTML;
19             pod2html( { -dir => 'html' },
20             { '/usr/lib/perl5/Pod/HTML.pm' => 'Pod::HTML' });
21              
22             =head1 DESCRIPTION
23              
24             THIS IS PRELIMINARY SOFTWARE! The C namespace is strictly
25             preliminary until a regular place in CPAN is found.
26              
27             B converts one or more Pod documents into individual HTML
28             files. This is meant to be a successor of Tom Christiansen's original
29             Pod::HTML. However it is not a plug-in replacement as there are
30             significant differences.
31              
32             When no document is specified, this script acts as a filter
33             (from STDIN to STDOUT). No index or table of contents is generated.
34             In any other case one or more corresponding F<.html> file(s) is/are
35             created.
36              
37             Optionally B can generate a table of contents and an index.
38             As it makes use of the L module, it can
39             also generate Postscript output using L.
40              
41             There is a hook for customization of the translation result before
42             writing the actual HTML.
43              
44             =head2 Pod directives and their translation
45              
46             The following section gives an overview of the translation equivalences.
47              
48             =over 4
49              
50             =item C<=head>I
51              
52             A heading is turned into a HTML heading, e.g. C<=head1> corresponds to
53             CH2E>. The CH1E> heading is reserved for page titles.
54              
55             =item S I>, C<=item>, C<=back>
56              
57             Itemized lists are turned into either COLE> (numbered list),
58             CULE> (buletted list), or CDLE> (definition list),
59             depending on whether the first item in the list starts with a digit,
60             a number or nothing, or anything else, respectively.
61              
62             =item C>, C>, C<=end>
63              
64             Paragraphs starting with C<=for html> or encapsulated in
65             C> are parsed as HTML and included into the document.
66             All other C<=for>/C<=begin> paragraphs are ignored.
67              
68             =item C...E>
69              
70             Turned into bold text using EBE...E/BE.
71              
72             =item C...E>
73              
74             Turned into italic text using EIE...E/IE.
75              
76             =item C...E> C...E>
77              
78             Turned into monospaced (typewriter) text using
79             ECODEE...E/CODEE.
80              
81             =item C...E>
82              
83             Pod entities are mapped to the corresponding HTML characters or
84             entities. The most important HTML entities (e.g. CcopyE>)
85             are recognized. See also L.
86              
87             =item C...E>
88              
89             All whitespace in this sequence is turned into C< >, i.e.
90             non-breakable spaces.
91              
92             =item C...E>
93              
94             The text of this sequence is included in the index (along with all
95             non-trivial C<=item> entries), pointing to the place of its ocurrence
96             in the text.
97              
98             =item C...E>
99              
100             Pod hyperlinks are turned into active HTML hyperlinks if the destination
101             has been found in the Pod documents processed in this conversion session.
102             Otherwise the link text is simply underlined.
103              
104             Note: There is no caching mechanism for deliberate reasons: a) One does
105             not run huge conversion jobs three times a day, so performance is not
106             the most important goal, b) caching is hard to code, and c) although
107             following conversion jobs could make profit of the existing cache of
108             destination nodes in the already converted documents, these will not
109             notice that some of their previously unresolved links may now be ok
110             because the required document has been converted. Conclusion: Run
111             B over I your Pod documents after adding new ones and
112             you will have a consistent state.
113              
114             As a special unofficial feature HTML hyperlinks are also supported:
115             Chttp://www.perl.comE>.
116              
117             =back
118              
119             =head2 Options
120              
121             B recognizes the following options. Those passed to the
122             B class directly are marked with (*).
123              
124             =over 4
125              
126             =item B<-converter> I
127              
128             The converter class to use, defaults to C. This hook allows
129             for simple customization, see also L<"Customizing">.
130              
131             =item B<-suffix> I
132              
133             Use this string for links to other converted Pod documents. The default
134             is C<.html> and also sets the filename suffix unless B<-filesuffix> has
135             been specified. The dot must be included!
136              
137             =item B<-filesuffix> I
138              
139             Use this string as a suffix for the output HTML files. This does not
140             change the suffix used in the hyperlinks to different documents. This
141             feature is meant to be used if some (Makefile based) postprocessing
142             of the generated files has to be performed, but without having to
143             adapt the links.
144              
145             =item B<-dir> I
146              
147             Write the generated HTML files (can be a directory hierarchy) to this
148             path. The default is the current working directory.
149              
150             =item B<-libpods> I
151              
152             This option activates a highly magical feature: The C<=item> nodes of
153             the specified Pod documents (given by Pod name, e.g. C)
154             serve as destinations for highlighted text in all converted Pod
155             documents. Typical usage: When converting your Perl installation's
156             documentation, you may want to say
157              
158             pod2html -libpods perlfunc,perlvar,perlrun -script -inc
159              
160             then you will get a hyperlink to L in the text
161             C$|E>.
162              
163             =item B<-localtoc> I
164              
165             This is by default true, so that at the top of the page a local
166             table of contents with all the C<=head>I lines is generated.
167              
168             =item B<-navigation> I
169              
170             When using the default customization, this flag enables or disables
171             the navigation in the header of each Pod document.
172              
173             =item B<-toc> I
174              
175             If true, a table of contents is built from the processed Pod documents.
176              
177             =item B<-idx> I
178              
179             If true, an index is built from all C<=item>s of the processed Pod
180             documents.
181              
182             =item B<-idxopt> I
183              
184             Options for index building. Default is "item,x", which means that
185             item strings as well as text marked up with C...E>
186             generate entries in the index.
187              
188             =item B<-tocname> I
189              
190             Use I as the filename of the table of contents. Default is
191             F. The general file suffix is added to this name.
192              
193             =item B<-idxname> I
194              
195             Use I as the filename of the index. Default is
196             F. The general file suffix is added to this name.
197              
198             =item B<-toctitle> I
199              
200             The string that is used as the heading of the table of contents.
201             Default is `Table of Contents'.
202              
203             =item B<-idxtitle> I
204              
205             The string that is used as the heading of the table of contents.
206             Default is `Index'.
207              
208             =item B<-ps> I
209              
210             In addition to HTML, generate also Postscript output. The suffix is
211             F<.ps>.
212              
213             =item B<-psdir>
214              
215             The root directory where to write Postscript files. Defaults to the
216             same as B<-dir>.
217              
218             =item B<-psfont> I
219              
220             Generate Postscript files using the font I. Default is
221             `Helvetica'.
222              
223             =item B<-papersize> I
224              
225             Generate Postscript files using the paper size I. Default is
226             `A4'.
227              
228             =item B<-warnings> I
229              
230             When processing the first pass, print warnings. See L
231             for more information on warnings. Note: This can procude a lot of
232             output if the Pod source does not correspond to strict guidelines.
233              
234             =item B<-stylesheet> I
235              
236             The (optional) link to a style sheet, which is included in the resulting HTML
237             as
238              
239            
240              
241             =item B<-banner> I
242              
243             If true, a banner is included at the bottom of the generated
244             page. Default is true.
245              
246             =back
247              
248             =cut
249              
250 4     4   1142 use strict;
  4         9  
  4         214  
251 4     4   20 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  4         9  
  4         443  
252              
253             require Exporter;
254 4     4   30 use File::Basename;
  4         10  
  4         320  
255 4     4   23 use File::Path;
  4         8  
  4         244  
256 4     4   22 use Pod::Parser;
  4         13  
  4         168  
257 4     4   5044 use Pod::Checker;
  4         42500  
  4         548  
258 4     4   3936 use HTML::Entities;
  4         23705  
  4         340  
259 4     4   4660 use HTML::TreeBuilder;
  4         110895  
  4         56  
260              
261             $VERSION = '0.49';
262             @ISA = qw(Exporter Pod::Parser);
263              
264             @EXPORT = qw();
265             @EXPORT_OK = qw(&pod2html &_construct_file_name);
266              
267             ##############################################################################
268              
269             # this is used everywhere
270             my $NBSP = HTML::Entities::decode_entities(' ');
271              
272             # This makes HTML::Element print properly opened and closed

tags

273             $HTML::Tagset::optionalEndTag{'p'} = 0;
274              
275             ##---------------------------------
276             ## Function definitions begin here
277             ##---------------------------------
278              
279             sub pod2html {
280 2     2 0 4 my (%opts,%PODS);
281             # options hash
282 2 50       8 if(ref $_[0]) {
283 2         3 %opts = %{shift()};
  2         23  
284             }
285             # PODs hash
286 2 50       10 if(ref $_[0]) {
287 2         3 %PODS = %{shift()};
  2         7  
288             }
289             else {
290 0         0 %PODS = map { $_ => do {
  0         0  
291 0 0       0 my $name = ref($_) ? 'STDIN' : $_;
292 0         0 $name =~ s:^.*/::;
293 0         0 $name =~ s:\.(pod|pm|pl)$::i;
294 0 0       0 $name =~ s:\.(bat|exe|cmd)$::i if($^O =~ /win|os2/i);
295 0         0 $name;
296             } } @_;
297             }
298             # set defaults
299 2         13 _default(\%opts, '-converter', 'Marek::Pod::HTML');
300 2         5 _default(\%opts, '-filter', 0);
301 2         5 _default(\%opts, '-suffix', '.html');
302 2         6 _default(\%opts, '-filesuffix', $opts{-suffix});
303 2         5 _default(\%opts, '-dir', '.');
304 2         6 _default(\%opts, '-libpods', '');
305 2         5 _default(\%opts, '-localtoc', 1);
306 2         5 _default(\%opts, '-navigation', 1);
307 2         4 _default(\%opts, '-toc', 1);
308 2         4 _default(\%opts, '-idx', 1);
309 2         5 _default(\%opts, '-tocname', 'podtoc');
310 2         4 _default(\%opts, '-idxname', 'podindex');
311 2         4 _default(\%opts, '-toctitle', 'Table of Contents');
312 2         4 _default(\%opts, '-idxtitle', 'Index');
313 2         5 _default(\%opts, '-ps', 0);
314 2         6 _default(\%opts, '-psdir', $opts{-dir});
315 2         5 _default(\%opts, '-psfont', 'Helvetica');
316 2         5 _default(\%opts, '-papersize', 'A4');
317 2         5 _default(\%opts, '-warnings', 0);
318 2         4 _default(\%opts, '-verbose', 0);
319 2         5 _default(\%opts, '-stylesheet', '');
320 2         5 _default(\%opts, '-banner', 1);
321 2         6 _default(\%opts, '-idxopt', 'item,x');
322             # only a single file?
323 2 50       11 if($opts{-filter}) {
324 0         0 $opts{-toc} = $opts{-idx} = 0;
325             }
326             # nothing to do
327 2 50       16 return 0 unless(keys %PODS);
328              
329             ###################################################
330             # first pass: run Pod::Checker on all the files
331             # and extract hyperlink nodes
332             ###################################################
333              
334 2         20 my $cache = Pod::Cache->new();
335 2         27 foreach my $infile (sort keys %PODS) {
336 3 50       85 warn "\n+++ Scanning $infile\n" if($opts{-verbose});
337             ## Now create a pod scanner, based on Pod::Checker
338 3   50     75 my $scanner = Pod::Checker->new(-warnings => $opts{'-warnings'},
339             -name => $PODS{$infile} || 'STDIN');
340              
341             ## Now check the pod document for errors
342 3         827 $scanner->parse_from_file($infile, \*STDERR);
343            
344             ## Return the number of errors found
345 3         10641 my $errs = $scanner->num_errors();
346 3 50       28 if($errs == -1) {
    50          
347 0 0       0 warn "Warning: No POD in `$infile', skipping\n"
348             if($opts{'-warnings'});
349 0         0 next;
350             }
351             elsif($errs > 0) {
352 0         0 warn "Warning: Conversion may be garbled because of errors above\n";
353             }
354              
355 3         12 my $name = $scanner->name();
356             # also allow X<> entries as link destinations
357 3         23 my @nodes = _unique_ids($scanner->node()); #,$scanner->idx());
358              
359             # hack for perlrun - get the nodes for all switches
360 3 50       12 if($name eq 'perlrun') {
361 0         0 my @addnodes = ();
362 0         0 my %have = map { $_->[0] => 1 } @nodes;
  0         0  
363 0         0 foreach(@nodes) {
364 0 0 0     0 if($_->[0] =~ /^(-\w)\S/ && !$have{$1}++) {
365 0         0 push(@addnodes, [ $1 , $_->[1] ]);
366             }
367             }
368 0         0 push(@nodes,@addnodes);
369             }
370            
371             ## remember settings
372             $cache->item(
373 3         18 -file => $infile,
374             -page => $name,
375             -nodes => [ @nodes ],
376             -idx => [ _unique_ids($scanner->idx()) ]);
377             } # end first pass
378              
379             # build lookup table for libpods
380 2         110 my %lib;
381 2         10 foreach my $pod (split(/,/, $opts{-libpods})) {
382 0         0 warn "\n+++ Adding $pod to autolink lookup table\n";
383 0         0 my $have_it = $cache->find_page($pod);
384 0 0       0 unless($have_it) {
385 0         0 warn "Error: Could not find the library POD '$pod'.\n";
386 0         0 next;
387             }
388 0         0 foreach ($have_it->nodes()) {
389 0         0 my ($name,$id) = @$_;
390             # only add significant nodes. The first libpod takes precedence
391 0 0 0     0 if($name ne '*' && !defined $lib{$name}) {
392 0         0 $lib{$name} = [ $have_it->page(), $id ];
393             }
394             }
395             }
396              
397             #######################################################
398             # second pass: do the conversion
399             #######################################################
400              
401             # Schwartzian transform to reduce sort effort
402             # compare case-insensitively, only in case of equality compare
403             # case sensitively
404 3 50       15 my @cache = map { $_->[0] } sort { $a->[1] cmp $b->[1] || $a->[0]->page() cmp $b->[0]->page() }
  1         13  
  3         26  
405 2         18 map { [ $_ , lc($_->page()) ] } $cache->item();
406 2         5 my @index;
407             # propagate some of the options
408             my %conv_opts;
409 2         6 for(qw(-suffix -navigation -localtoc -toc -tocname -toctitle -idx
410             -idxname -idxtitle -idxopt -stylesheet -verbose -banner)) {
411 26         49 $conv_opts{$_} = $opts{$_};
412             }
413            
414 2         7 $conv_opts{-cache} = $cache;
415 2         6 $conv_opts{-lib} = \%lib;
416 2         8 $conv_opts{-mycache} = '';
417 2         5 $conv_opts{'-next'} = '';
418 2         5 $conv_opts{-prev} = '';
419              
420 2         9 for(my $i = 0; $i< scalar(@cache); $i++) {
421             ## Now create a pod converter
422 3         39 $_ = $cache[$i];
423 3         12 my $infile = $_->file();
424 3 50       25 warn "\n+++ Converting $infile\n" if($opts{-verbose});
425              
426 3         31 my %current_opts = %conv_opts;
427 3         13 $current_opts{-name} = $_->page();
428 3         19 $current_opts{-mycache} = $_;
429 3 100       21 $current_opts{'-next'} = ($i < $#cache) ? $cache[$i+1]->page() :
    100          
430             ($current_opts{-idx} ? $current_opts{-idxname} : '');
431 3 100       19 $current_opts{-prev} = ($i > 0) ? $cache[$i-1]->page() :
    100          
432             ($current_opts{-toc} ? $current_opts{-tocname} : '');
433              
434 3         39 my $converter = $opts{-converter}->new(%current_opts);
435              
436             ## Now convert it
437 3         10 my $outfile;
438 3         11 my $outpath = _construct_file_name($_->page(), 0, $opts{-filesuffix});
439 3 50       12 if($opts{-filter}) {
440 0         0 $outfile = \*STDOUT;
441             }
442             else {
443 3 50       15 $outfile = $opts{-outfile} ? $opts{-outfile} :
444             $opts{-dir} . '/' . $outpath;
445 3         151 my $ddir = dirname($outfile);
446 3 50       56 mkpath($ddir) unless(-d $ddir);
447             }
448 3         647 $converter->parse_from_file($infile,$outfile);
449 3         14 $_->description($converter->description());
450 3         27 $_->path($outpath);
451 3         22 push(@index, map { $$_[1] = "$outpath#$$_[1]"; $$_[2] = $current_opts{-name}; $_ }
  12         27  
  12         26  
  12         69  
452             $converter->indices());
453             # dump postscript if requested
454 3 50       15 if($opts{-ps}) {
455 0         0 my $pspath = $opts{-psdir} . '/' . _construct_file_name(
456             $_->page(), 0, '.ps');
457 0         0 my $ddir = dirname($pspath);
458 0 0       0 mkpath($ddir) unless(-d $ddir);
459 0         0 _write_ps($pspath,$converter->{_html},\%opts);
460             }
461              
462             # kill the HTML tree, required by HTML::Element
463 3         20 $converter->{_html}->delete();
464              
465             } # end second pass
466              
467             ################################################
468             # create a table of contents
469             ################################################
470              
471 2 100       90 if($opts{-toc}) {
472             # Style classes in TOC:
473             # H1 CLASS=PODTOC : Table of contents heading
474             # TD CLASS=PODTOC_NAME : POD name (appears as link)
475             # TD CLASS=PODTOC_DESC : Description
476 1 50       5 warn "\n+++ Creating table of contents\n" if($opts{-verbose});
477              
478             # create a Marek::Pod::HTML object to gain access to the customize
479             # method
480 1         10 my $tocobj = bless { %conv_opts, '-next' => $cache[0]->page() },
481             $opts{-converter};
482 1         32 ($tocobj->{_html}, $tocobj->{_head}, $tocobj->{_body}) =
483             _basic_html();
484 1         7 $tocobj->depth(0);
485              
486 1         5 my $table = HTML::Element->new('table');
487 1         24 $tocobj->{_body}->push_content($table, "\n");
488              
489 1         25 foreach(sort { lc $a->page() cmp lc $b->page() } $cache->item()) {
  0         0  
490 1         13 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
491             href => $_->path());
492 1         122 $anchor->push_content($_->page());
493 1         18 my $row = HTML::Element->new('tr');
494 1         18 my $name = HTML::Element->new('td', CLASS => 'PODTOC_NAME');
495 1         21 my $text = HTML::Element->new('td', CLASS => 'PODTOC_DESC');
496 1         21 $row->push_content($name, $text);
497 1         17 $table->push_content($row,"\n");
498 1         16 $name->push_content($anchor);
499             # $desc is either a simple string or a reference to an array
500             # of HTML::Element's
501 1 50       12 if(my $desc = $_->description()) {
502 1 50       8 $text->push_content(ref $desc ? @{$desc} : $desc);
  1         4  
503             # correct POD_LINKs
504 1         15 foreach($text->find_by_tag_name('a')) {
505 0         0 my $class = $_->attr('CLASS');
506 0 0 0     0 next unless($class && $class eq 'POD_LINK');
507 0         0 my $href = $_->attr('href');
508 0         0 $href =~ s:^(\.\./)+::; # the TOC is on top!
509 0         0 $_->attr('href', $href);
510             }
511             }
512             else {
513             # we have no description
514 0         0 $text->push_content('');
515             }
516             }
517              
518             # add all the HTML gimmicks
519 1         29 $tocobj->customize($opts{-toctitle});
520              
521             # write HTML file
522 1         19 _write_html($tocobj->{_html},
523             "$opts{-dir}/$opts{-tocname}$opts{-filesuffix}",undef,$opts{-verbose});
524              
525             # dump postscript output
526 1 50       6 if($opts{-ps}) {
527 0         0 _write_ps("$opts{-psdir}/$opts{-tocname}.ps",
528             $tocobj->{_html}, \%opts);
529             }
530              
531             # remove the HTML
532 1         5 $tocobj->{_html}->delete();
533             }
534              
535             ################################################
536             # create an index
537             ################################################
538              
539 2 100       48 if($opts{-idx}) {
540             # Style classes in Index:
541             # H1 CLASS=PODIDX : Index heading
542             # H2 CLASS=PODIDX : Index section heading
543 1 50       6 warn "\n+++ Creating index\n" if($opts{-verbose});
544              
545 1         7 my $idxobj = bless { %conv_opts, '-prev' => $cache[-1]->page() },
546             $opts{-converter};
547 1         18 ($idxobj->{_html}, $idxobj->{_head}, $idxobj->{_body}) =
548             _basic_html();
549 1         4 $idxobj->depth(0);
550              
551             # now generate the real index
552              
553 1         3 my %idx;
554 1         4 foreach(@index) {
555 7         14 my ($text,$id, $page) = @$_;
556 7         10 my $key;
557 7 50       26 if($text =~ /^\W*([a-z])/i) {
    0          
558 7         12 $key = uc($1);
559             }
560             elsif($text =~ /^\W*([0-9])/) {
561 0         0 $key = '0-9';
562             }
563             else {
564 0         0 $key = 'Sym';
565             }
566 7         9 push(@{$idx{$key}{$text}}, [ $id, $page ]);
  7         29  
567              
568             }
569 1         6 foreach my $key (qw(Sym 0-9), sort keys %idx) {
570 4 100       10 next unless(defined $idx{$key});
571 2         9 my $heading = HTML::Element->new('h2', CLASS => 'PODIDX');
572 2         54 $heading->push_content($key);
573 2         26 $idxobj->{_body}->push_content($heading, "\n");
574 2         34 foreach my $text (sort {lc $a cmp lc $b} keys %{$idx{$key}}) {
  3         7  
  2         10  
575 4         66 $idxobj->{_body}->push_content($text);
576 4         42 foreach(@{$idx{$key}{$text}}) {
  4         9  
577 7         67 my $anchor = HTML::Element->new('a', HREF => $$_[0],
578             CLASS => 'POD_NAVLINK');
579 7         210 $anchor->push_content("[$$_[1]]");
580 7         94 $idxobj->{_body}->push_content($NBSP x 2, $anchor);
581             }
582 4         64 $idxobj->{_body}->push_content(HTML::Element->new('br'),"\n");
583             }
584 2         60 delete $idx{$key};
585             }
586              
587             # add all the HTML gimmicks
588 1         4 $idxobj->customize($opts{-idxtitle});
589              
590 1         18 _write_html($idxobj->{_html},
591             "$opts{-dir}/$opts{-idxname}$opts{-filesuffix}",undef,$opts{-verbose});
592              
593             # dump postscript if requested
594 1 50       6 if($opts{-ps}) {
595 0         0 _write_ps("$opts{-psdir}/$opts{-idxname}.ps",
596             $idxobj->{_html}, \%opts);
597             }
598              
599             # remove the HTML::Element objects
600 1         6 $idxobj->{_html}->delete();
601             }
602             }
603              
604             # write HTML tree as PostScript
605             sub _write_ps
606             {
607 0     0   0 my ($file,$html,$opts) = @_;
608              
609 0 0       0 warn "Writing PostScript $file\n" if($opts->{-verbose});
610 0 0       0 unless(open(PS,">$file")) {
611 0         0 warn "Error: Cannot write '$file': $!\n";
612 0         0 return 0;
613             }
614 0         0 require HTML::FormatPS;
615 0         0 my $formatter = new HTML::FormatPS
616             FontFamily => $opts->{-psfont},
617             HorizontalMargin => HTML::FormatPS::mm(15),
618             VerticalMargin => HTML::FormatPS::mm(20),
619             PaperSize => $opts->{-papersize};
620 0         0 print PS $formatter->format($html);
621 0         0 close(PS);
622             }
623              
624             ##-------------------------------
625             ## Method definitions begin here
626             ##-------------------------------
627              
628             =head2 OO Interface
629              
630             The B module has an object oriented interface that allows
631             to customize the converter for special requirements or for
632             proprietary conversion tools. This section describes the most important
633             methods.
634              
635             =over 4
636              
637             =item new()
638              
639             Create a new converter object. Idiom:
640              
641             my $converter = new Marek::Pod::HTML;
642              
643             =cut
644              
645             # set up a new object
646             sub new {
647 3     3 1 5 my $this = shift;
648 3   33     18 my $class = ref($this) || $this;
649 3         21 my %params = @_;
650 3         27 my $self = {%params};
651 3         11 bless $self, $class;
652 3         9 $self->initialize();
653 3         69 return $self;
654             }
655              
656             # initalize, set defaults
657             sub initialize {
658 3     3 0 8 my $self = shift;
659              
660             ## Options
661             # the POD name
662 3   50     23 $self->{-name} ||= '';
663              
664             # the suffix for links
665 3   50     9 $self->{-suffix} ||= '.html';
666              
667             # the short description, taken from NAME
668 3   50     16 $self->{-description} ||= '';
669              
670             # generate local navigation
671 3 50       12 $self->{-localtoc} = 1 unless(defined $self->{-localtoc});
672              
673             # global navigation
674 3 50       10 $self->{-navigation} = 1 unless(defined $self->{-navigation});
675              
676             ## Internal
677             # counter for headings and items
678 3         5 $self->{_current_node} = 0;
679 3         8 $self->{_current_idx} = 0;
680              
681             # a stack for nested lists
682 3         5 $self->{_list_stack} = [];
683              
684             # a stack for nested lists
685 3         6 $self->{_current_anchor} = '';
686              
687             # no parser errors here, we've seen them in the first pass
688 3     0   32 $self->SUPER::errorsub(sub { return 1; });
  0         0  
689             }
690              
691             =item customize($name)
692              
693             This method is called after the complete Pod source code has been
694             converted, thus allowing for customizations like title, navigation
695             and footer. I<$name> should contain the page title.
696             This method also reads properties of the current Marek::Pod::HTML object
697             to do the customizations. It is executed for each POD file processed and
698             -- if enabled -- the index and the table of contents.
699              
700             XIt is quite simple to build a proprietary
701             customization by writing a new module that inherits from B:
702              
703             package POD::HTML::mystyle;
704             use Marek::Pod::HTML qw(pod2html);
705             use vars qw(@ISA @EXPORT @EXPORT_OK);
706             require Exporter;
707             @ISA = qw(Marek::Pod::HTML);
708             @EXPORT_OK = qw(&pod2html);
709             sub customize {
710             my ($self,$name) = @_;
711             # if you just want to add things, use this line first:
712             $self->SUPER::customize($name);
713             # do your own things here
714             #...
715             }
716              
717             For complete customization, it is a good starting point to copy the
718             customize method from B.
719              
720             You can access all the converter's methods and properties through the
721             C<$self->method()> and C<$self->{-property}> syntax, respectively.
722              
723             =cut
724              
725             # this method can be overridden to customize the HTML output
726             sub customize {
727 5     5 1 11 my ($self,$name) = @_;
728              
729             # set document class
730 5         21 my $root = HTML::Element->new('~declaration', text =>
731             'DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"');
732 5         134 $root->push_content("\n", $self->{_html});
733 5         91 $self->{_html} = $root;
734              
735             # include stylesheet
736 5 50       23 if($self->{-stylesheet}) {
737 0         0 my $css = HTML::Element->new('link', TYPE => "text/css",
738             REL => "stylesheet", HREF => $self->{-stylesheet});
739 0         0 $self->{_head}->push_content($css, "\n");
740             }
741              
742             # customize the title
743 5         17 my $title = HTML::Element->new('title');
744 5   50     120 $title->push_content($self->{-title} || $name || 'POD');
745 5         69 $self->{_head}->push_content($title, "\n");
746              
747             # prepend big heading
748 5 50       97 if($name) {
749 5         19 my $titleh = HTML::Element->new('h1', CLASS => 'POD_TITLE');
750 5         133 $titleh->push_content($name);
751 5         73 $self->{_body}->unshift_content("\n",$titleh,"\n",
752             HTML::Element->new('hr'));
753             }
754              
755 5 50       244 if($self->{-navigation}) {
756             # add navigation
757 5         22 my $table = HTML::Element->new('table', width => '100%');
758 5         124 $self->{_body}->unshift_content("\n",$table);
759              
760 5         89 my $tr = HTML::Element->new('tr');
761 5         88 $table->push_content("\n",$tr,"\n");
762              
763 5 100       110 if($self->{'-next'}) {
764 3         13 my $td = HTML::Element->new('td', align => 'left', width => '1%');
765 3         98 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
766             href => _construct_file_name($self->{'-next'}, $self->depth(), $self->{-suffix}));
767 3         97 $anchor->push_content('Next:', HTML::Element->new('br'), $self->{'-next'});
768 3         121 $td->push_content($anchor);
769 3         46 $tr->push_content($td);
770             }
771              
772 5 100       55 if($self->{'-prev'}) {
773 3         13 my $td = HTML::Element->new('td', align => 'left', width => '1%');
774 3         91 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
775             href => _construct_file_name($self->{'-prev'}, $self->depth(), $self->{-suffix}));
776 3         92 $anchor->push_content('Previous:', HTML::Element->new('br'), $self->{'-prev'});
777 3         109 $td->push_content($anchor);
778 3         52 $tr->push_content($td);
779             }
780              
781 5         59 my $filler = HTML::Element->new('td', width => '90%');
782 5         120 $filler->push_content($NBSP);
783 5         63 $tr->push_content($filler);
784              
785 5 100       67 if($self->{-toc}) {
786 3         17 my $td = HTML::Element->new('td', align => 'right', width => '1%');
787 3         92 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLILNK',
788             href => _construct_file_name($self->{-tocname}, $self->depth(), $self->{-suffix}));
789 3         85 my $text = '['.$self->{-toctitle}.']';
790 3         56 $text =~ s/\s+/$NBSP/g;
791 3         11 $anchor->push_content($text);
792 3         38 $td->push_content($anchor);
793 3         38 $tr->push_content($td);
794             }
795              
796 5 100       51 if($self->{-idx}) {
797 3         10 my $td = HTML::Element->new('td', align => 'right', width => '1%');
798 3         93 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
799             href => _construct_file_name($self->{-idxname}, $self->depth(), $self->{-suffix}));
800 3         96 my $text = '['.$self->{-idxtitle}.']';
801 3         6 $text =~ s/\s+/$NBSP/g;
802 3         9 $anchor->push_content($text);
803 3         42 $td->push_content($anchor);
804 3         44 $tr->push_content($td);
805             }
806             } # end navigation
807              
808             # for finding the way back to the top
809 5         49 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
810             name => 'Pod_TOP_OF_PAGE');
811 5         156 $self->{_body}->unshift_content("\n",$anchor);
812              
813             # customize the footer
814 5         94 $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
815             href => '#Pod_TOP_OF_PAGE');
816 5         153 $anchor->push_content('[Top]');
817 5         71 $self->{_body}->push_content(HTML::Element->new('hr'), "\n", $anchor, "\n");
818 5 50       464 $self->{_body}->push_content("Generated by Marek::Pod::HTML $VERSION on " . localtime() . "\n")
819             if($self->{-banner});
820             }
821              
822             =item depth()
823              
824             Returns how "deep" this documents is buried in the directory
825             hierarchy. This value is derived from the C<-name> property and is
826             for instance 1 for B.
827              
828             =cut
829              
830             # which hierarchy level does this POD have?
831             sub depth {
832 16     16 1 36 my ($self,$depth) = @_;
833 16 100       105 if(defined $depth) {
    100          
834 2         5 $self->{-depth} = $depth;
835             } elsif(!defined $self->{-depth}) {
836 3         8 $self->{-depth} = 0;
837 3         15 $self->{-depth}++ while($self->{-name} =~ /::/g);
838             }
839 16         72 $self->{-depth};
840             }
841              
842             =item description()
843              
844             Sets or retrieves the short description from the C<=head1 NAME> section of
845             the Pod document. Empty if there is no such section.
846              
847             =cut
848              
849             # The POD description, taken out of NAME if present
850             sub description {
851 9 100   9 1 88 return (@_ > 1) ? ($_[0]->{-description} = $_[1]) : $_[0]->{-description};
852             }
853              
854             =item indices()
855              
856             Add a new item or return the list of index entries of this document.
857             Each index is represented by an index text (in HTML) and the unique id
858             (i.e. the anchor name) of the index entry in the HTML document.
859              
860             =cut
861              
862             # store/retrieve index entries
863             sub indices {
864 15     15 1 25 my $self = shift;
865 15 100       47 unless(defined $self->{_indices}) {
866 3         8 $self->{_indices} = [];
867             }
868 15 100       31 if(@_) {
869 12         15 push(@{$self->{_indices}}, [ @_ ]);
  12         41  
870 12         443 return $self->{_indices}->[-1];
871             }
872             else {
873 3         5 return @{$self->{_indices}};
  3         11  
874             }
875             }
876              
877             =item name()
878              
879             Set/retrieve the C<-name> property, i.e. the canonical Pod name
880             (e.g. C).
881              
882             =back
883              
884             See the F file for additional helper functions that
885             you may use in your code, but beware: things may change there without
886             notice!
887              
888             =cut
889              
890             # set and/or retrieve canonical name of POD
891             sub name {
892 3 50   3 1 26 return (@_ > 1) ? ($_[0]->{-name} = $_[1]) : $_[0]->{-name};
893             }
894              
895             ## overrides for Pod::Parser
896              
897             # things to do at start of POD
898             sub begin_input {
899 3     3 0 7 my $self = shift;
900              
901 3         10 ($self->{_html}, $self->{_head}, $self->{_body}) =
902             _basic_html();
903 3         8 $self->{_current} = $self->{_body};
904 3         227 $self->{_current_head1_title} = '';
905             }
906              
907             # things to do at end of POD
908             sub end_pod {
909 3     3 0 137 my $self = shift;
910 3         24 my $out_fh = $self->output_handle();
911             #delete $self->{_p_for_reuse};
912 3         7 delete $self->{_current};
913              
914             # close any lists left
915 3         5 while(@{$self->{_list_stack}}) {
  3         13  
916 0         0 my $list = shift(@{$self->{_list_stack}});
  0         0  
917 0         0 warn "Warning: autoclosing list at EOF\n";
918             # nothing to do thanks to HTML::Element
919             }
920              
921             ## add local TOC
922 3 50       13 if($self->{-localtoc}) {
923 3         12 $self->_local_toc();
924             }
925              
926             ## Do any page customizations
927 3         94 $self->customize($self->name());
928              
929             # dump it
930 3         76 _write_html($self->{_html},$self->output_file(),$out_fh,$self->{-verbose});
931 3         324 1;
932             }
933              
934             sub _write_html
935             {
936 5     5   11 my ($obj, $file, $handle,$verbose) = @_;
937 5 50       23 warn "Writing HTML $file\n" if($verbose);
938 5         20 my $html = $obj->as_HTML() . "\n";
939 5 100       25400 unless($handle) {
940 2 50       221 unless(open(OUT, ">$file")) {
941 0         0 warn "Error: Cannot write: $!\n";
942 0         0 return 0;
943             }
944 2         14 print OUT $html;
945 2         82 close(OUT);
946             } else {
947 3         27 print $handle $html;
948             }
949 5         11 1;
950             }
951              
952             # expand a POD command
953             sub command {
954 28     28 0 647 my ($self, $command, $paragraph, $line_num, $pod_para) = @_;
955 28         147 my ($file, $line) = $pod_para->file_line;
956              
957             # Heading
958 28 100       147 if ($command =~ /^head(\d)/) {
    100          
    100          
    50          
    0          
    0          
    0          
959 9         15 my $n = $1;
960              
961             # close any lists left
962 9         12 while(@{$self->{_list_stack}}) {
  9         28  
963 0         0 my $list = shift(@{$self->{_list_stack}});
  0         0  
964 0         0 warn "Warning: autoclosing list at $command"
965             . " at line $line_num of file $file\n";
966 0         0 $self->{_current} = $list->parent();
967             }
968              
969             # expand the heading's text
970 9         41 $paragraph =~ s/[\s\n]+$//;
971 9         25 my @title = $self->interpolate($paragraph, $line_num);
972              
973             # retrieve the heading's id
974 9         69 my $count = ($self->{_current_node})++;
975 9         9 my ($node,$id) = @{$self->{-mycache}->{-nodes}->[$count]};
  9         31  
976              
977             # make

and

, but leave space for deeper

978             # levels. By special request of Achim Bohnet ;-)
979 9         48 my $heading = HTML::Element->new('h'.($n + 1), CLASS => "POD_HEAD$n");
980 9         294 my $anchor = HTML::Element->new('a', name => $id);
981 9         209 $self->{_current_anchor} = $id;
982 9         25 $anchor->push_content(@title);
983 9         146 $heading->push_content($anchor);
984 9         128 $self->{_current}->push_content($heading,"\n");
985              
986             # save heading details for later reference
987 9 100       153 if($n == 1) {
988 6         21 $self->{_current_head1_title} = $heading->as_text();
989             }
990 9 50       169 if($self->{-localtoc}) {
991 9         11 push(@{$self->{_toc}}, [ $n, $id,
  9         41  
992             HTML::Element->clone_list(@title) ]);
993             }
994             }
995             # Start of List
996             elsif ($command eq 'over') {
997 4         8 $self->{_current_anchor} = '';
998 4         15 $paragraph =~ s/[\s\n]+$//;
999 4         7 unshift(@{$self->{_list_stack}},
  4         25  
1000             Pod::List->new(-indent => $paragraph,
1001             -parent => $self->{_current}));
1002             }
1003              
1004             # a list item
1005             elsif ($command eq 'item') {
1006             # Check for an open list
1007 11 50       14 unless(@{$self->{_list_stack}}) {
  11         30  
1008 0         0 unshift(@{$self->{_list_stack}},
  0         0  
1009             Pod::List->new(-indent => 4, -parent =>
1010             $self->{_current}));
1011 0         0 warn "Warning: =item without =over, auto-opening `=over 4'"
1012             . " at line $line_num of file $file\n";
1013             }
1014 11         19 my $list = $self->{_list_stack}[0];
1015 11         45 $paragraph =~ s/[\s\n]+$//;
1016 11 100       61 unless($list->type()) {
    100          
1017             # determine type of list
1018 4 50 66     65 if($paragraph =~ s/^()\s*\d+\.?\s*/$1/) {
    100          
1019             # an ordered list
1020 0         0 $list->type('ol');
1021 0         0 $list->rx('^()\s*\d+\.?\s*');
1022             }
1023             # artificial intelligence: look behind opening tags
1024             elsif($paragraph =~ s/^((\s*\w<)*)\s*[*]\s*/$1/ ||
1025             $paragraph =~ s/^\s*$//) {
1026             # a bulleted list
1027 2         5 $list->type('ul');
1028 2         12 $list->rx('^((\s*\w<)*)\s*[*]\s*');
1029             }
1030             else {
1031             # a definition list
1032 2         8 $list->type('dl');
1033             }
1034 4         33 $list->tag(HTML::Element->new($list->type(), CLASS => 'POD_LIST')
1035             )->push_content("\n");
1036 4         182 $self->{_current}->push_content($list->tag(),"\n");
1037             } elsif(my $rx = $list->rx()) {
1038             # simplify the item text
1039 4         231 $paragraph =~ s/$rx/$1/;
1040             }
1041              
1042             # retrieve node id
1043 11         127 my $count = ($self->{_current_node})++;
1044 11         15 my ($node,$id) = @{$self->{-mycache}->{-nodes}->[$count]};
  11         41  
1045 11         19 $self->{_current_anchor} = $id;
1046              
1047 11         26 my @text = $self->interpolate($paragraph, $line_num);
1048              
1049 11         92 my $item;
1050 11         38 my $anchor = HTML::Element->new('a', name => $id);
1051 11 100       375 if($list->type() eq 'dl') {
1052 5         29 my $dt;
1053 5         16 my $content = $list->tag()->content();
1054 5 50 33     122 if(defined $content && ref($content) && @$content &&
      33        
      66        
      66        
      66        
1055             ref($content->[-1]) && $content->[-1]->tag() eq 'dd' &&
1056             $content->[-1]->is_empty()) {
1057 0         0 $dt = $content->[-1];
1058 0         0 $dt->tag('dt');
1059             } else {
1060 5         66 $dt = HTML::Element->new('dt', CLASS => 'POD_ITEM');
1061 5         126 $list->tag()->push_content($dt);
1062             }
1063 5         89 $dt->push_content($anchor,"\n");
1064 5         91 $anchor->push_content(@text);
1065 5         63 $item = HTML::Element->new('dd');
1066 5         131 $self->{_last_p_by} = 'dd';
1067             } else {
1068 6         43 $item = HTML::Element->new('li', CLASS => 'POD_ITEM');
1069 6 100       143 if(length $paragraph) {
1070 3         9 my $p = HTML::Element->new('p');
1071 3         52 $p->push_content(@text);
1072 3         43 $anchor->push_content($p);
1073             } else {
1074 3         7 $anchor->push_content(@text);
1075             }
1076 6         60 $item->push_content($anchor);
1077 6         84 $item->push_content("\n");
1078             }
1079 11         88 $list->tag()->push_content($item);
1080 11         171 $self->{_current} = $item;
1081              
1082 11 50       54 if($self->{-idxopt} =~ /(^|,)item(,|$)/i) {
1083             # save item html text for later reference
1084 11 100 33     292 $self->indices(_to_text(@text),$id)
1085             if($paragraph =~ /^\s*(\w<\s*)*(\S*)/ && $2);
1086             }
1087             }
1088              
1089             # End of a list
1090             elsif ($command eq 'back') {
1091 4         9 $self->{_current_anchor} = '';
1092 4         4 my $list = shift(@{$self->{_list_stack}});
  4         11  
1093 4 50       11 unless($list) {
1094 0         0 warn "Warning: =back without =over, ignoring"
1095             . " at line $line_num of file $file\n";
1096             }
1097             else {
1098 4         16 $self->{_current} = $list->parent();
1099             }
1100             }
1101              
1102             # 'for' converter paragraph
1103             elsif ($command eq 'for') {
1104 0         0 $self->{_current_anchor} = '';
1105 0         0 $paragraph =~ s/[\s\n]+$//s;
1106 0 0 0     0 if($paragraph =~ s/^[\s\n]*(\S+)[\s\n]*// && lc($1) eq 'html') {
1107 0         0 my $curr = $self->{_current};
1108 0         0 my $p = _get_last_p_or_new($curr, 'POD_RAW');
1109 0         0 $self->_push_raw_html($p,$paragraph);
1110             }
1111             }
1112              
1113             # 'begin' converter brace
1114             elsif ($command eq 'begin') {
1115 0         0 $self->{_current_anchor} = '';
1116 0 0       0 unless($paragraph =~ /(\S+)/) {
1117 0         0 warn "Warning: =begin without parameter, ignoring"
1118             . " at line $line_num of file $file\n";
1119             }
1120             else {
1121 0         0 $self->{_begin} = lc($1);
1122 0 0       0 if($self->{_begin} eq 'html') {
1123             # set up a raw HTML storage
1124 0         0 $self->{_raw_html} = '';
1125             }
1126             }
1127             }
1128              
1129             # 'end' converter brace
1130             elsif ($command eq 'end') {
1131 0         0 $self->{_current_anchor} = '';
1132 0         0 $self->{_begin} = undef;
1133             # do I have html?
1134 0 0       0 if($self->{_raw_html}) {
1135             # try to find a preceding

tag

1136 0         0 my $curr = $self->{_current};
1137 0         0 my $p = _get_last_p_or_new($curr, 'POD_RAW');
1138 0         0 $self->_push_raw_html($p,$self->{_raw_html});
1139 0         0 delete $self->{_raw_html};
1140             }
1141             }
1142             # ignore all the rest
1143             }
1144              
1145             sub _get_last_p_or_new
1146             {
1147 0     0   0 my ($curr,$class) = @_;
1148 0         0 my $p;
1149 0         0 my $content = $curr->content();
1150 0 0 0     0 if(defined $content && ref($content) && @$content &&
      0        
      0        
      0        
1151             ref($content->[-2]) && $content->[-2]->tag() eq 'p') {
1152 0         0 $p = $content->[-2];
1153             } else { # need a new one
1154 0         0 $p = HTML::Element->new('p', CLASS => $class);
1155 0         0 $curr->push_content($p,"\n");
1156             }
1157 0         0 $p;
1158             }
1159              
1160             # process a verbatim paragraph
1161             sub verbatim {
1162 1     1 0 2 my ($self, $paragraph, $line_num, $pod_para) = @_;
1163              
1164 1         3 $self->{_current_anchor} = '';
1165             # strip trailing whitespace
1166 1         5 $paragraph =~ s/[\s\n]+$//s;
1167              
1168 1 50       4 unless(length($paragraph)) {
    0          
    0          
1169             # just an empty line
1170 1         4 $self->{_current}->push_content(HTML::Element->new('p'), "\n");
1171             }
1172             elsif(!$self->{_begin}) {
1173             # a regular paragraph
1174 0         0 my $pre;
1175 0         0 my $content = $self->{_current}->content();
1176             # reuse last
 if immediate predecessor 
1177 0 0 0     0 if(defined $content && ref($content) && @$content &&
      0        
      0        
      0        
1178             ref($content->[-2]) && $content->[-2]->tag() eq 'pre') {
1179 0         0 $pre = $content->[-2];
1180             } else {
1181 0         0 $pre = HTML::Element->new('pre', CLASS => 'POD_VERBATIM');
1182 0         0 $self->{_current}->push_content($pre,"\n");
1183             }
1184 0         0 $pre->push_content("\n");
1185              
1186 0 0 0     0 if($self->{_current_head1_title} eq 'NAME' && !$self->description()) {
1187             # save the description for further use in TOC
1188 0         0 my $str = $paragraph;
1189 0         0 $str =~ s/^[\n\s]+//;
1190 0 0       0 $self->description($str) if($str);
1191             }
1192             # this is special in perl.pod
1193 0         0 foreach(split(/\n/,$paragraph)) {
1194             # TODO expand tabs correctly?
1195 0 0       0 if(s/^(\s+)([\w:]+)(\t+)//) {
1196             # this is for perl.pod - an implied list
1197 0         0 my ($indent,$page,$postdent) = ($1,$2,$3);
1198 0         0 my $dest = $self->{-cache}->find_page($page);
1199 0 0       0 if($dest) {
1200 0         0 my $destfile = _construct_file_name(
1201             $dest->page(), $self->depth(), $self->{-suffix});
1202 0         0 my $link = HTML::Element->new('a', href => $destfile,
1203             CLASS => 'POD_LINK');
1204 0         0 $link->push_content($page);
1205 0         0 $page = $link;
1206             }
1207 0         0 $pre->push_content($indent,$page,$postdent,$_,"\n");
1208             } else {
1209 0         0 $pre->push_content($_,"\n");
1210             }
1211             }
1212             }
1213             # a "verbatim" =begin html paragraph
1214             elsif($self->{_begin} eq 'html') {
1215 0         0 $self->{_raw_html} .= $paragraph;
1216             }
1217             }
1218              
1219             # a regular text paragraph
1220             sub textblock {
1221 22     22 0 365 my ($self, $paragraph, $line_num, $pod_para) = @_;
1222              
1223 22         141 $paragraph =~ s/[\s\n]+$//s;
1224              
1225             # regular context
1226 22 50       52 if(!$self->{_begin}) {
    0          
1227 22         59 my @text = $self->interpolate($paragraph, $line_num);
1228             # remember first paragraph in NAME section
1229 22 50 66     282 if($self->{_current_head1_title} eq 'NAME' && $paragraph &&
      66        
1230             !$self->description()) {
1231             # save the description for further use in TOC
1232 3         12 $self->description([ HTML::Element->clone_list(@text) ]);
1233             }
1234 22         38 my $par;
1235 22 100 100     135 if($self->{_last_p_by} && $self->{_last_p_by} eq 'dd') {
    50 66        
1236 5         8 $par = $self->{_current};
1237 5         13 delete $self->{_last_p_by};
1238             }
1239             elsif($self->{_last_p_by} && $self->{_last_p_by} eq 'beginfor') {
1240 0         0 $par = _get_last_p_or_new($self->{_current}, 'POD_TEXT');
1241             }
1242             else {
1243 17         55 $par = HTML::Element->new('p', CLASS => 'POD_TEXT');
1244 17         442 $self->{_current}->push_content($par, "\n");
1245             }
1246 22         334 $par->push_content("\n",@text,"\n");
1247 22         532 $self->{_last_p_by} = 'text';
1248             }
1249             # =begin html context
1250             elsif($self->{_begin} eq 'html') {
1251 0         0 $self->{_raw_html} .= $paragraph;
1252             }
1253             # reset currrent anchor this late so that in this par no autolinks
1254             # are generated
1255 22         1169 $self->{_current_anchor} = '';
1256             }
1257              
1258             # expand a POD text string
1259             sub interpolate {
1260 42     42 0 115 my ($self, $paragraph, $line) = @_;
1261             ## Check the interior sequences in the command-text
1262             # and return the text as array of HTML::Element's
1263 42         2009 $self->_expand_ptree(
1264             $self->parse_text($paragraph,$line), $line, '');
1265             }
1266              
1267             sub _expand_ptree {
1268 48     48   369 my ($self,$ptree,$line,$nestlist) = @_;
1269 48         50 local($_);
1270 48         66 my @text = ();
1271             # process each node in the parse tree
1272 48         77 foreach(@$ptree) {
1273             # regular text chunk
1274 58 100       145 unless(ref) {
1275 48         59 my $chunk = $_;
1276             # do magic linebreaking
1277 48         143 while($chunk =~ s/^([^\n]*)\n([ \t]+)//) {
1278 0         0 my ($line,$indent) = ($1,$2);
1279 0 0       0 $line =~ s/\s/$NBSP/g if($nestlist =~ /S/);
1280 0         0 push(@text, $line, HTML::Element->new('br'),
1281             _expand_tab($indent) );
1282             }
1283             # escape whitespace if in S<>
1284 48 50       88 if($chunk) {
1285 48 50       85 $chunk =~ s/\s/$NBSP/g if($nestlist =~ /S/);
1286 48         123 push(@text,$chunk);
1287             }
1288 48         103 next; # finished this chunk
1289             }
1290             # have an interior sequence
1291 10         39 my $cmd = $_->cmd_name();
1292 10         31 my $contents = $_->parse_tree();
1293 10         11 my $file;
1294 10         52 ($file,$line) = $_->file_line();
1295              
1296             # an entity
1297 10 50       63 if($cmd eq 'E') {
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
1298 0         0 my $entity = $contents->raw_text();
1299 0         0 $entity =~ s/^[\n\s]+|[\n\s]+$//g;
1300 0 0       0 if($entity =~ /^(0x[0-9a-f]+)$/i) {
    0          
    0          
    0          
    0          
1301             # hexadecimal
1302 0         0 push(@text, chr(hex($1)));
1303             }
1304             elsif($entity =~ /^(0[0-7]+)$/) {
1305             # octal
1306 0         0 push(@text, chr(oct($1)));
1307             }
1308             elsif($entity =~ /^(\d+)$/) {
1309             # decimal
1310 0         0 push(@text, chr($1));
1311             }
1312             elsif($entity =~ /^sol$/i) {
1313             # forward slash
1314 0         0 push(@text, '/');
1315             }
1316             elsif($entity =~ /^verbar$/i) {
1317             # vertical bar
1318 0         0 push(@text, '|');
1319             }
1320             else {
1321             # textual entity
1322 0   0     0 push(@text, HTML::Entities::decode_entities("&$entity;") || '');
1323             }
1324             }
1325              
1326             # a hyperlink
1327             elsif($cmd eq 'L') {
1328             # try to parse the hyperlink
1329 2         14 my $raw = $contents->raw_text();
1330 2         12 my $link = Pod::Hyperlink->new($raw);
1331 2 50       178 unless(defined $link) {
1332             # the link cannot be parsed
1333 0         0 my $underline = HTML::Element->new('u');
1334 0         0 $underline->push_content($raw);
1335 0         0 push(@text,$underline);
1336 0         0 next;
1337             }
1338              
1339             # only underline if destination not found
1340 2         4 $self->{_link_pagemark} = 'u';
1341 2         4 $self->{_link_pageopt} = +{};
1342 2         5 $self->{_link_sectionmark} = 'u';
1343 2         4 $self->{_link_sectionopt} = +{};
1344              
1345             # search for page
1346 2         6 my $page = $link->page();
1347 2         11 $page =~ s/[(]\w*[)]$//; # strip manpage section
1348 2         1 my $dest;
1349 2         4 my $destfile = '';
1350 2 50       5 if($page) {
1351 2         9 $dest = $self->{-cache}->find_page($page);
1352 2 50       28 if($dest) {
1353 2         9 $destfile = _construct_file_name(
1354             $dest->page(), $self->depth(), $self->{-suffix});
1355 2         8 $self->{_link_pagemark} = $self->{_link_sectionmark} = 'a';
1356 2         8 $self->{_link_pageopt} =
1357             $self->{_link_sectionopt} =
1358             { CLASS => 'POD_LINK', HREF => $destfile };
1359             }
1360             else {
1361 0         0 warn "Cannot find page `$page' at L<> on line $line\n";
1362             }
1363             } else {
1364 0         0 $dest = $self->{-mycache};
1365             }
1366              
1367 2 50       8 if($link->type() eq 'hyperlink') {
1368 0         0 $self->{_link_sectionmark} = 'a';
1369 0         0 $self->{_link_sectionopt} =
1370             { CLASS => 'POD_LINK', HREF => $link->node() };
1371             } else {
1372             # search for node in page
1373 2         16 my $node = '';
1374             # use Pod::Checker's expand procedure to get the link
1375             # destination node
1376 2 50       6 if($link->node()) {
1377 2         42 my $cruncher = Pod::Checker->new(-quiet => 1);
1378 2     0   82 $cruncher->errorsub(sub { 1; }); # suppress any errors
  0         0  
1379 2         10 $node = $cruncher->interpolate_and_check($link->node(),
1380             $line,$file);
1381             }
1382 2 50 33     150 if($dest && $node) {
1383 2         8 my $id = $dest->find_node($node);
1384 2 50       40 if($id) {
1385 2         5 $self->{_link_sectionmark} = 'a';
1386 2         8 $self->{_link_sectionopt} =
1387             { CLASS => 'POD_LINK', HREF => "$destfile#$id" };
1388             } else {
1389 0 0       0 my $inpage = $page ? " in `$page'" : '';
1390 0         0 warn "Cannot find node `$node'$inpage at L<> on line $line\n";
1391             }
1392             }
1393             }
1394 2         8 $link->line($line); # remember line
1395              
1396             # convert the link text (expand POD markup)
1397 2         13 push(@text, $self->_expand_ptree($self->parse_text(
1398             $link->markup(), $line), $line, "$nestlist$cmd"));
1399             }
1400              
1401             # internal: hyperlink to page
1402             elsif($cmd eq 'P') {
1403 2         10 my $tag = HTML::Element->new($self->{_link_pagemark},
1404 2         5 %{$self->{_link_pageopt}});
1405 2         69 push(@text,$tag);
1406 2         5 $tag->push_content($self->_expand_ptree($contents, $line,
1407             "$nestlist$cmd"));
1408             }
1409              
1410             # internal: hyperlink to section
1411             elsif($cmd eq 'Q') {
1412 2         11 my $tag = HTML::Element->new($self->{_link_sectionmark},
1413 2         3 %{$self->{_link_sectionopt}});
1414 2         68 push(@text,$tag);
1415 2         15 $tag->push_content($self->_expand_ptree($contents, $line,
1416             "$nestlist$cmd"));
1417             }
1418              
1419             # bold text
1420             elsif($cmd eq 'B') {
1421 0         0 $self->_autolink_and_highlight(\@text, $contents, $line,
1422             "$nestlist$cmd", 'b', 0);
1423             }
1424              
1425             # code text
1426             elsif($cmd eq 'C') {
1427 0         0 $self->_autolink_and_highlight(\@text, $contents, $line,
1428             "$nestlist$cmd", 'code', 1);
1429             }
1430              
1431             # file text
1432             elsif($cmd eq 'F') {
1433 0         0 $self->_autolink_and_highlight(\@text, $contents, $line,
1434             "$nestlist$cmd", 'code' , 0);
1435             }
1436              
1437             # italic text
1438             elsif($cmd eq 'I') {
1439             # TODO I<...I<...>...> should be expanded to
1440             # ......... - according to Achim Bohnet
1441 0         0 $self->_autolink_and_highlight(\@text, $contents, $line,
1442             "$nestlist$cmd", 'i', 0);
1443             }
1444              
1445             # non-breakable space
1446             elsif($cmd eq 'S') {
1447             # will be taken care of above, when expanding text chunk
1448 0         0 push(@text, $self->_expand_ptree($contents, $line, "$nestlist$cmd"));
1449             }
1450              
1451             # zero-size element
1452             elsif($cmd eq 'Z') {
1453             # do nothing - a comment would be nice
1454             # ​ is the correct entity, but it won't work with the
1455             # current HTML::Entities
1456             }
1457              
1458             # custom index entries
1459             # TODO these should run also through Pod::Checker and result in
1460             # valid L<...> destinations
1461             elsif($cmd eq 'X') {
1462             # set up a fast lookup cache for node ids
1463 4         8 my $count = ($self->{_current_idx})++;
1464 4         4 my ($node,$id) = @{$self->{-mycache}->{-idx}->[$count]};
  4         14  
1465 4         13 my $tag = HTML::Element->new('a', name => $id);
1466             #$tag->push_content(@key);
1467 4         99 push(@text,$tag);
1468 4 50       33 $self->indices($node,$id) # $node was $txt
1469             if($self->{-idxopt} =~ /(^|,)x(,|$)/i);
1470             }
1471             # ignore everything else
1472             }
1473 48         258 @text;
1474             }
1475              
1476             ## Helpers
1477              
1478             # set some default value unless already defined
1479             sub _default
1480             {
1481 46 100   46   127 $_[0]->{$_[1]} = $_[2] unless(defined $_[0]->{$_[1]});
1482             }
1483              
1484             # setup the basic frame for a HTML tree
1485             sub _basic_html
1486             {
1487 5     5   68 my $html = HTML::Element->new('html');
1488 5         146 my $head = HTML::Element->new('head');
1489 5         96 $head->push_content("\n",
1490             HTML::Element->new('meta', 'http-equiv' => 'Content-Type',
1491             content => 'text/html; charset=ISO-8859-1'), "\n",
1492             HTML::Element->new('meta', 'http-equiv' => 'Content-Style-Type',
1493             content => 'text/css'), "\n",
1494             HTML::Element->new('meta', 'name' => 'GENERATOR',
1495             content => "Marek::Pod::HTML $VERSION"), "\n");
1496 5         729 $html->push_content("\n",$head,"\n");
1497 5         104 my $body = HTML::Element->new('body');
1498 5         87 $body->push_content("\n");
1499 5         62 $html->push_content($body,"\n");
1500 5         94 ($html,$head,$body);
1501             }
1502              
1503             # create a set of unique ids
1504             sub _unique_ids {
1505 6     6   45 my (@nodes) = @_;
1506              
1507             # we need the hashes both ways...
1508 6         11 my %hash = ();
1509 6         8 my %Node = ();
1510 6         9 foreach my $node (@nodes) {
1511             # start with string
1512 24         42 my $id = _idfy($node,\%hash);
1513 24         64 $hash{$id} = 1;
1514 24         41 $Node{$node} = $id;
1515 24         58 $node = [ $node, $id ];
1516             }
1517             # create secondary nodes (needed mainly for perlfunc)
1518 6         12 my @addnodes = ();
1519 6         14 foreach my $node (keys %Node) {
1520 19 100       61 if($node =~ /^(\S+)\s+\S/) { # more than one word
1521 3 50       14 push(@addnodes, [ $1, $Node{$node} ]) unless(defined $Node{$1});
1522             }
1523             }
1524 6         44 @nodes,@addnodes;
1525             }
1526              
1527             # turn a string into a unique id
1528             # hashref points to a has with already existing ids
1529             sub _idfy
1530             {
1531 24     24   30 my ($id,$hashref) = @_;
1532            
1533             # collapse entities
1534 24         27 $id =~ s/E<([^>]*)>/$1/g;
1535             # collapse all non-alphanum characters to _
1536 24         41 $id =~ s/\W+/_/g;
1537             # collapse multiple _
1538 24         24 $id =~ s/_{2,}/_/g;
1539             # abbreviate to 20 characters
1540 24         33 $id = substr($id,0,20);
1541             # has to have some contents
1542 24 100       36 $id = '_' unless($id);
1543 24         28 my $ext = '';
1544             # find something unique
1545 24         66 $ext++ while($hashref->{$id.$ext});
1546 24         44 $id . $ext;
1547             }
1548              
1549              
1550             # prepend a paragraph with links to an HTML object's contents
1551             sub _add_links {
1552 0     0   0 1;
1553             }
1554              
1555             # turn a POD name into a HTML file name
1556             sub _construct_file_name {
1557 17     17   50 my ($file,$depth,$suffix) = @_;
1558 17         83 $file =~ s!::!/!g; #/
1559 17 50       39 $file .= $suffix if($suffix);
1560 17         101 ('../' x $depth) . $file;
1561             }
1562              
1563             # check if linkable and put into appropriate tag
1564             sub _autolink_and_highlight
1565             {
1566 0     0   0 my ($self,$tref,$contents,$line,$nest,$type,$doit) = @_;
1567              
1568 0         0 my $tag = HTML::Element->new($type);
1569 0         0 push(@$tref,$tag);
1570             # canonicalize raw_text before lookup
1571 0         0 my $cruncher = Pod::Checker->new(-quiet => 1);
1572 0     0   0 $cruncher->errorsub(sub { 1; }); # suppress any errors
  0         0  
1573 0         0 my $text = $cruncher->interpolate_and_check($contents->raw_text(),
1574             $line,'');
1575 0         0 $text =~ s/^\s+|\s+$//g;
1576 0         0 my ($node_ref); # will contain [$page,$id]
1577             # try to find text in the libpod nodes. Do not link if
1578             # currently processing the anchor paragraph itself
1579             # (avoid reciprocal links)
1580 0 0 0     0 if($doit && $self->{-lib} &&
      0        
      0        
      0        
1581             ($node_ref = $self->{-lib}->{$text}) &&
1582             !($$node_ref[0] eq $self->{-name} &&
1583             $$node_ref[1] eq $self->{_current_anchor})) {
1584 0         0 my $anchor = HTML::Element->new('a', CLASS => 'POD_LINK',
1585             href => _construct_file_name($$node_ref[0], $self->depth(),
1586             $self->{-suffix} . '#' . $$node_ref[1]));
1587 0         0 $tag->push_content($anchor);
1588 0         0 $tag = $anchor;
1589             }
1590 0         0 $tag->push_content($self->_expand_ptree($contents, $line, $nest));
1591             }
1592              
1593             # expand blanks and tabs to an appropriate amount of non-breaking space
1594             sub _expand_tab {
1595             # TODO more magic: indent by one blank less than in $str -
1596             # this would allow for the missing E
syntax
1597 0     0   0 my ($str, $pos) = @_;
1598 0         0 my $new = '';
1599 0   0     0 $pos ||= 0;
1600 0         0 while($str =~ m/([ \t])/g) {
1601 0 0       0 if($1 eq ' ') {
1602 0         0 $new .= $NBSP;
1603 0         0 $pos++;
1604             }
1605             else {
1606 0         0 my $len = $pos % 8;
1607 0 0       0 $len = 8 unless($len);
1608 0         0 $new .= $NBSP x $len;
1609 0         0 $pos += $len;
1610             }
1611             }
1612 0         0 $new;
1613             }
1614              
1615             # prepend local navigation to body
1616             sub _local_toc {
1617 3     3   5 my $self = shift;
1618 3 50       14 if(defined $self->{_toc}) {
1619 3         5 my $level = 1;
1620 3         11 my @hier = ( HTML::Element->new('ul') );
1621 3         60 $hier[0]->push_content("\n");
1622 3         45 $self->{_body}->unshift_content("\n", $hier[0], "\n",
1623             HTML::Element->new('hr'));
1624 3         172 foreach(@{$self->{_toc}}) {
  3         8  
1625 9         115 my ($l, $id, @line) = @$_;
1626 9         34 while($l > $level) {
1627             # new sublevel
1628 1         5 push(@hier, HTML::Element->new('ul'));
1629 1         19 $hier[-2]->push_content($hier[-1], "\n");
1630 1         16 $level++;
1631 1         6 $hier[-1]->push_content("\n");
1632             }
1633 9         34 while($l < $level) {
1634 0         0 pop(@hier);
1635 0         0 $level--;
1636             }
1637 9         28 my $item = HTML::Element->new('li');
1638 9         177 my $anchor = HTML::Element->new('a', CLASS => 'POD_NAVLINK',
1639             href => "#$id");
1640 9         268 $item->push_content($anchor);
1641 9         146 $anchor->push_content(@line);
1642 9         117 $hier[-1]->push_content($item, "\n");
1643             }
1644             }
1645             }
1646              
1647             # push a raw HTML string on the current contents
1648             sub _push_raw_html {
1649 0     0   0 my ($self,$node,$str) = @_;
1650 0         0 my $tree = new HTML::TreeBuilder;
1651 0         0 $tree->warn(1);
1652 0         0 $tree->implicit_tags(1);
1653 0         0 $tree->ignore_unknown(1);
1654 0         0 $tree->store_comments(1);
1655 0         0 $tree->p_strict(1);
1656             #$tree->implicit_body_p_tag(1);
1657 0         0 $tree->parse($str);
1658 0         0 $tree->eof;
1659 0         0 my $head = $tree->find_by_tag_name('head');
1660 0 0 0     0 $self->{_head}->push_content(@{$head->content()},"\n")
  0         0  
1661             if($head && $head->content());
1662 0         0 my $body = $tree->find_by_tag_name('body');
1663 0 0 0     0 $node->push_content(@{$body->content()})
  0         0  
1664             if($body && $body->content());
1665             # this will not delete the contents, they have been pushed
1666             # somewhere else
1667 0         0 $tree->delete();
1668              
1669             # consolidate p tags, i.e. re-root them appropriately
1670 0         0 my $lastp;
1671 0 0       0 if($node->tag() eq 'p') {
1672 0         0 my $root = $node->parent();
1673 0         0 foreach($node->content_refs_list) {
1674 0 0 0     0 if(ref $$_ && $$_->tag() eq 'p') {
1675 0         0 my $parent = $$_->parent();
1676 0         0 my $pindex = $$_->pindex();
1677 0         0 my ($p,@rest) = $parent->splice_content($pindex);
1678 0 0       0 if(@rest) {
1679 0         0 my %attr = $node->all_attr();
1680 0         0 my $newp = HTML::Element->new('p', $node->all_external_attr());
1681 0         0 $newp->push_content(@rest);
1682 0         0 $root->push_content($p,"\n",$newp,"\n");
1683 0         0 $lastp = 'beginfor';
1684             } else {
1685 0         0 $root->push_content($p,"\n");
1686 0         0 $lastp = 'raw';
1687             }
1688             }
1689             }
1690             }
1691 0   0     0 $self->{_last_p_by} = $lastp || 'beginfor';
1692 0         0 1;
1693             }
1694              
1695             # process a part of HTML::Element into plain text
1696             sub _to_text {
1697 8     8   13 my @out;
1698 8         15 foreach(@_) {
1699 8 50       18 if(ref $_) {
1700 0         0 push(@out, $_->as_text());
1701             }
1702             else {
1703 8         73 push(@out, HTML::Entities::decode_entities($_));
1704             }
1705             }
1706 8         38 join('',@out);
1707             }
1708              
1709             # needed to get rid of all HTML::Element's
1710             sub DESTROY {
1711 5     5   2786 my $self = shift;
1712 5 50       27 $self->{_html}->delete() if(defined $self->{_html});
1713             }
1714              
1715             =head1 SEE ALSO
1716              
1717             L, L, L, L,
1718             L, L, L,
1719             L, L, L, L
1720              
1721             =head1 AUTHOR
1722              
1723             Marek Rouchal Emarekr@cpan.orgE
1724              
1725             =head1 HISTORY
1726              
1727             A big deal of this code has been recycled from a variety of existing
1728             Pod converters, e.g. by Tom Christiansen and Russ Allbery. A lot of
1729             ideas came from Nick Ing-Simmons' B, e.g. the usage of the
1730             B module and the B module.
1731             Without the B module by Brad Appleton and the
1732             B module by Gisle Aas this module would not exist.
1733              
1734             =cut
1735              
1736             1;
1737