File Coverage

blib/lib/Pod/L10N/Html.pm
Criterion Covered Total %
statement 324 364 89.0
branch 135 184 73.3
condition 42 77 54.5
subroutine 34 37 91.8
pod 3 12 25.0
total 538 674 79.8


line stmt bran cond sub pod time code
1             package Pod::L10N::Html;
2 21     21   239445 use strict;
  21         192  
  21         1578  
3             require Exporter;
4              
5             our $VERSION = '1.07';
6             our @ISA = qw(Exporter);
7             our @EXPORT = qw(pod2htmll10n htmlify);
8             our @EXPORT_OK = qw(anchorify relativize_url);
9              
10 21     21   119 use Carp;
  21         29  
  21         1532  
11 21     21   151 use Config;
  21         36  
  21         750  
12 21     21   101 use Cwd;
  21         44  
  21         1120  
13 21     21   112 use File::Basename;
  21         43  
  21         2326  
14 21     21   122 use File::Spec;
  21         32  
  21         470  
15 21     21   98 use File::Spec::Unix;
  21         31  
  21         537  
16 21     21   13361 use Getopt::Long;
  21         197934  
  21         97  
17 21     21   15939 use Pod::Simple::Search;
  21         112845  
  21         675  
18 21     21   10610 use Pod::Simple::SimpleTree ();
  21         601511  
  21         575  
19              
20 21     21   9561 use Pod::L10N::Model;
  21         14131  
  21         701  
21              
22 21     21   8659 use locale; # make \w work right in non-ASCII lands
  21         10581  
  21         115  
23              
24             =head1 NAME
25              
26             Pod::L10N::Html - module to convert pod files to HTML with L10N
27              
28             =head1 SYNOPSIS
29              
30             use Pod::L10N::Html;
31             pod2htmll10n([options]);
32              
33             =head1 DESCRIPTION
34              
35             Converts files from pod format (see L) to HTML format.
36              
37             Its API is fully compatible with L.
38              
39             If input files support L extended format,
40             Pod::L10N::Html do some more works to print translated text pretty well.
41              
42             =head1 ADDITIONAL FEATURES
43              
44             Additional features from L 1.2202 are:
45              
46             =over
47              
48             =item *
49              
50             Support L extended format.
51              
52             =back
53              
54             =head1 FUNCTIONS
55              
56             =head2 pod2htmll10n
57              
58             pod2htmll10n("pod2htmll10n",
59             "--podpath=lib:ext:pod:vms",
60             "--podroot=/usr/src/perl",
61             "--htmlroot=/perl/nmanual",
62             "--recurse",
63             "--infile=foo.pod",
64             "--outfile=/perl/nmanual/foo.html");
65              
66             See L for details.
67              
68             =head2 htmlify
69              
70             htmlify($heading);
71              
72             See L for details.
73              
74             =head2 anchorify
75              
76             anchorify(@heading);
77              
78             See L for details.
79              
80             =head1 ENVIRONMENT
81              
82             Uses C<$Config{pod2html}> to setup default options.
83              
84             =head1 AUTHOR
85              
86             C is based on L Version 1.2202 written by
87             Marc Green, Emarcgreen@cpan.orgE.
88              
89             Modification to C is written by SHIRAKATA Kentaro,
90             Eargrath@cpan.orgE.
91              
92             =head1 SEE ALSO
93              
94             L, L, L
95              
96             =head1 COPYRIGHT
97              
98             This program is distributed under the Artistic License.
99              
100             =cut
101              
102             # This sub duplicates the guts of Pod::Simple::FromTree. We could have
103             # used that module, except that it would have been a non-core dependency.
104             sub feed_tree_to_parser {
105 0     0 0 0 my($parser, $tree) = @_;
106 0 0 0     0 if(ref($tree) eq "") {
    0          
107 0         0 $parser->_handle_text($tree);
108             } elsif(!($tree->[0] eq "X" && $parser->nix_X_codes)) {
109 0         0 $parser->_handle_element_start($tree->[0], $tree->[1]);
110 0         0 feed_tree_to_parser($parser, $_) foreach @{$tree}[2..$#$tree];
  0         0  
111 0         0 $parser->_handle_element_end($tree->[0]);
112             }
113             }
114              
115             # "
116              
117             my $Cachedir;
118             my $Dircache;
119             my($Htmlroot, $Htmldir, $Htmlfile, $Htmlfileurl);
120             my($Podfile, @Podpath, $Podroot);
121             my $Poderrors;
122             my $Css;
123              
124             my $Recurse;
125             my $Quiet;
126             my $Verbose;
127             my $Doindex;
128              
129             my $Backlink;
130              
131             my($Title, $Header);
132              
133             my %Pages = (); # associative array used to find the location
134             # of pages referenced by L<> links.
135              
136             my $Curdir = File::Spec->curdir;
137              
138             init_globals();
139              
140             sub init_globals {
141 51     51 0 361 $Cachedir = "."; # The directory to which directory caches
142             # will be written.
143              
144 51         240 $Dircache = "pod2htmd.tmp";
145              
146 51         211 $Htmlroot = "/"; # http-server base directory from which all
147             # relative paths in $podpath stem.
148 51         199 $Htmldir = ""; # The directory to which the html pages
149             # will (eventually) be written.
150 51         209 $Htmlfile = ""; # write to stdout by default
151 51         306 $Htmlfileurl = ""; # The url that other files would use to
152             # refer to this file. This is only used
153             # to make relative urls that point to
154             # other files.
155              
156 51         222 $Poderrors = 1;
157 51         173 $Podfile = ""; # read from stdin by default
158 51         199 @Podpath = (); # list of directories containing library pods.
159 51         100 $Podroot = $Curdir; # filesystem base directory from which all
160             # relative paths in $podpath stem.
161 51         123 $Css = ''; # Cascading style sheet
162 51         104 $Recurse = 1; # recurse on subdirectories in $podpath.
163 51         93 $Quiet = 0; # not quiet by default
164 51         84 $Verbose = 0; # not verbose by default
165 51         74 $Doindex = 1; # non-zero if we should generate an index
166 51         71 $Backlink = 0; # no backlinks added by default
167 51         85 $Header = 0; # produce block header/footer
168 51         95 $Title = undef; # title to give the pod(s)
169             }
170              
171             sub pod2htmll10n {
172 30     30 1 7874 local(@ARGV) = @_;
173 30         190 local $_;
174              
175 30         204 init_globals();
176 30         290 parse_command_line();
177              
178             # prevent '//' in urls
179 28 100       116 $Htmlroot = "" if $Htmlroot eq "/";
180 28         181 $Htmldir =~ s#/\z##;
181              
182 28 100 66     432 if ( $Htmlroot eq ''
      100        
      100        
183             && defined( $Htmldir )
184             && $Htmldir ne ''
185             && substr( $Htmlfile, 0, length( $Htmldir ) ) eq $Htmldir
186             ) {
187             # Set the 'base' url for this file, so that we can use it
188             # as the location from which to calculate relative links
189             # to other files. If this is '', then absolute links will
190             # be used throughout.
191             #$Htmlfileurl = "$Htmldir/" . substr( $Htmlfile, length( $Htmldir ) + 1);
192             # Is the above not just "$Htmlfileurl = $Htmlfile"?
193 10         35 $Htmlfileurl = Pod::L10N::Html::_unixify($Htmlfile);
194              
195             }
196              
197             # load or generate/cache %Pages
198 28 100       149 unless (get_cache($Dircache, \@Podpath, $Podroot, $Recurse)) {
199             # generate %Pages
200 25         344 my $pwd = getcwd();
201 25 50       453 chdir($Podroot) ||
202             die "$0: error changing to directory $Podroot: $!\n";
203              
204             # find all pod modules/pages in podpath, store in %Pages
205             # - callback used to remove Podroot and extension from each file
206             # - laborious to allow '.' in dirnames (e.g., /usr/share/perl/5.14.1)
207 25         676 Pod::Simple::Search->new->inc(0)->verbose($Verbose)->laborious(1)
208             ->callback(\&_save_page)->recurse($Recurse)->survey(@Podpath);
209              
210 25 50       23447 chdir($pwd) || die "$0: error changing to directory $pwd: $!\n";
211              
212             # cache the directory list for later use
213 25 100       186 warn "caching directories for later use\n" if $Verbose;
214 25 50       5918 open my $cache, '>', $Dircache
215             or die "$0: error open $Dircache for writing: $!\n";
216              
217 25         432 print $cache join(":", @Podpath) . "\n$Podroot\n";
218 25   33     325 my $_updirs_only = ($Podroot =~ /\.\./) && !($Podroot =~ /[^\.\\\/]/);
219 25         289 foreach my $key (keys %Pages) {
220 468 50       646 if($_updirs_only) {
221 0         0 my $_dirlevel = $Podroot;
222 0         0 while($_dirlevel =~ /\.\./) {
223 0         0 $_dirlevel =~ s/\.\.//;
224             # Assume $Pages{$key} has '/' separators (html dir separators).
225 0         0 $Pages{$key} =~ s/^[\w\s\-\.]+\///;
226             }
227             }
228 468         1101 print $cache "$key $Pages{$key}\n";
229             }
230              
231 25 50       1712 close $cache or die "error closing $Dircache: $!";
232             }
233              
234 28         218 my $input;
235 28 50 33     203 unless (@ARGV && $ARGV[0]) {
236 28 50 33     328 if ($Podfile and $Podfile ne '-') {
237 28         97 $input = $Podfile;
238             } else {
239 0         0 $input = '-'; # XXX: make a test case for this
240             }
241             } else {
242 0         0 $Podfile = $ARGV[0];
243 0         0 $input = *ARGV;
244             }
245              
246 28         176 my ($content, $encoding) = arrange($Podfile);
247 28 100       107 if(!defined $encoding){
248 25         173 $encoding = 'utf-8';
249             }
250              
251             # set options for input parser
252 28         1142 my $parser = Pod::Simple::SimpleTree->new;
253 28         3097 $parser->codes_in_verbatim(0);
254 28         1016 $parser->accept_targets(qw(html HTML));
255 28         1596 $parser->no_errata_section(!$Poderrors); # note the inverse
256              
257 28 100       275 warn "Converting input file $Podfile\n" if $Verbose;
258 28         379 my $podtree = $parser->parse_string_document($content)->root;
259              
260 28 100       187839 unless(defined $Title) {
261 21 100 33     594 if($podtree->[0] eq "Document" && ref($podtree->[2]) eq "ARRAY" &&
      33        
      33        
      33        
      66        
      66        
      66        
      33        
      33        
      100        
      66        
262 21         588 $podtree->[2]->[0] eq "head1" && @{$podtree->[2]} == 3 &&
263             ref($podtree->[2]->[2]) eq "" && $podtree->[2]->[2] eq "NAME" &&
264             ref($podtree->[3]) eq "ARRAY" && $podtree->[3]->[0] eq "Para" &&
265 20         155 @{$podtree->[3]} >= 3 &&
266 20         368 !(grep { ref($_) ne "" }
267 20         73 @{$podtree->[3]}[2..$#{$podtree->[3]}]) &&
  20         65  
268             (@$podtree == 4 ||
269             (ref($podtree->[4]) eq "ARRAY" &&
270             $podtree->[4]->[0] eq "head1"))) {
271 18         67 $Title = join("", @{$podtree->[3]}[2..$#{$podtree->[3]}]);
  18         80  
  18         46  
272             }
273             }
274              
275 28   100     131 $Title //= "";
276 28         240 $Title = html_escape($Title);
277              
278             # set options for the HTML generator
279 28         1029 $parser = Pod::L10N::Html::LocalPodLinks->new();
280 28         6180 $parser->codes_in_verbatim(0);
281 28         541 $parser->anchor_items(1); # the old Pod::Html always did
282 28         319 $parser->backlink($Backlink); # linkify =head1 directives
283 28         278 $parser->force_title($Title);
284 28         332 $parser->htmldir($Htmldir);
285 28         330 $parser->htmlfileurl($Htmlfileurl);
286 28         276 $parser->htmlroot($Htmlroot);
287 28         381 $parser->index($Doindex);
288             # still need as parse twice
289 28         282 $parser->no_errata_section(!$Poderrors); # note the inverse
290 28         452 $parser->output_string(\my $output); # written to file later
291 28         53380 $parser->pages(\%Pages);
292 28         336 $parser->quiet($Quiet);
293 28         299 $parser->verbose($Verbose);
294              
295             # $parser->html_charset('UTF-8');
296 28         451 $parser->html_encode_chars('&<>">');
297             # $parser->html_header_tags('');
298              
299             # We need to add this ourselves because we use our own header, not
300             # ::XHTML's header. We need to set $parser->backlink to linkify
301             # the =head1 directives
302 28 100       245 my $bodyid = $Backlink ? ' id="_podtop_"' : '';
303              
304 28         66 my $csslink = '';
305 28         58 my $tdstyle = ' style="background-color: #cccccc; color: #000"';
306              
307 28 100       103 if ($Css) {
308 4         15 $csslink = qq(\n);
309 4         17 $csslink =~ s,\\,/,g;
310 4         21 $csslink =~ s,(/.):,$1|,;
311 4         8 $tdstyle= '';
312             }
313              
314             # header/footer block
315 28 100       145 my $block = $Header ? <
316            
317            
318              $Title
319            
320            
321             END_OF_BLOCK
322              
323             # create own header/footer because of --header
324 28         2745 $parser->html_header(<<"HTMLHEAD");
325            
326            
327            
328            
329             $Title$csslink
330            
331            
332            
333              
334            
335             $block
336             HTMLHEAD
337              
338 28         420 $parser->html_footer(<<"HTMLFOOT");
339             $block
340            
341              
342            
343             HTMLFOOT
344              
345             # $parser->parse_file($input);
346 28         378 $parser->parse_string_document($content);
347             # $parser->html_charset($parser->encoding());
348              
349             # Write output to file
350 28 50       20478 $Htmlfile = "-" unless $Htmlfile; # stdout
351 28         55 my $fhout;
352 28 50 33     242 if($Htmlfile and $Htmlfile ne '-') {
353 28 50       3050 open $fhout, ">", $Htmlfile
354             or die "$0: cannot open $Htmlfile file for output: $!\n";
355             } else {
356 0         0 open $fhout, ">-";
357             }
358 28     20   1469 binmode $fhout, ":encoding($encoding)";
  20         342  
  20         59  
  20         334  
359 28         198527 print $fhout $output;
360 28 50       1501 close $fhout or die "Failed to close $Htmlfile: $!";
361 28 50       2585 chmod 0644, $Htmlfile unless $Htmlfile eq '-';
362             }
363              
364             ##############################################################################
365              
366             sub usage {
367 2     2 0 3 my $podfile = shift;
368 2 100       12 warn "$0: $podfile: @_\n" if @_;
369 2         29 die <
370             Usage: $0 --help --htmldir= --htmlroot=
371             --infile= --outfile=
372             --podpath=:...: --podroot=
373             --cachedir= --flush --recurse --norecurse
374             --quiet --noquiet --verbose --noverbose
375             --index --noindex --backlink --nobacklink
376             --header --noheader --poderrors --nopoderrors
377             --css= --title=
378              
379             --[no]backlink - turn =head1 directives into links pointing to the top of
380             the page (off by default).
381             --cachedir - directory for the directory cache files.
382             --css - stylesheet URL
383             --flush - flushes the directory cache.
384             --[no]header - produce block header/footer (default is no headers).
385             --help - prints this message.
386             --htmldir - directory for resulting HTML files.
387             --htmlroot - http-server base directory from which all relative paths
388             in podpath stem (default is /).
389             --[no]index - generate an index at the top of the resulting html
390             (default behaviour).
391             --infile - filename for the pod to convert (input taken from stdin
392             by default).
393             --outfile - filename for the resulting html file (output sent to
394             stdout by default).
395             --[no]poderrors - include a POD ERRORS section in the output if there were
396             any POD errors in the input (default behavior).
397             --podpath - colon-separated list of directories containing library
398             pods (empty by default).
399             --podroot - filesystem base directory from which all relative paths
400             in podpath stem (default is .).
401             --[no]quiet - suppress some benign warning messages (default is off).
402             --[no]recurse - recurse on those subdirectories listed in podpath
403             (default behaviour).
404             --title - title that will appear in resulting html file.
405             --[no]verbose - self-explanatory (off by default).
406              
407             END_OF_USAGE
408              
409             }
410              
411             sub parse_command_line {
412 30     30 0 168 my ($opt_backlink,$opt_cachedir,$opt_css,$opt_flush,$opt_header,
413             $opt_help,$opt_htmldir,$opt_htmlroot,$opt_index,$opt_infile,
414             $opt_outfile,$opt_poderrors,$opt_podpath,$opt_podroot,
415             $opt_quiet,$opt_recurse,$opt_title,$opt_verbose);
416              
417 30 50       4810 unshift @ARGV, split ' ', $Config{pod2html} if $Config{pod2html};
418 30         663 my $result = GetOptions(
419             'backlink!' => \$opt_backlink,
420             'cachedir=s' => \$opt_cachedir,
421             'css=s' => \$opt_css,
422             'flush' => \$opt_flush,
423             'help' => \$opt_help,
424             'header!' => \$opt_header,
425             'htmldir=s' => \$opt_htmldir,
426             'htmlroot=s' => \$opt_htmlroot,
427             'index!' => \$opt_index,
428             'infile=s' => \$opt_infile,
429             'outfile=s' => \$opt_outfile,
430             'poderrors!' => \$opt_poderrors,
431             'podpath=s' => \$opt_podpath,
432             'podroot=s' => \$opt_podroot,
433             'quiet!' => \$opt_quiet,
434             'recurse!' => \$opt_recurse,
435             'title=s' => \$opt_title,
436             'verbose!' => \$opt_verbose,
437             );
438 30 100       63664 usage("-", "invalid parameters") if not $result;
439              
440 29 100       156 usage("-") if defined $opt_help; # see if the user asked for help
441 28         174 $opt_help = ""; # just to make -w shut-up.
442              
443 28 100       211 @Podpath = split(":", $opt_podpath) if defined $opt_podpath;
444              
445 28 100       119 $Backlink = $opt_backlink if defined $opt_backlink;
446 28 100       111 $Cachedir = _unixify($opt_cachedir) if defined $opt_cachedir;
447 28 100       105 $Css = $opt_css if defined $opt_css;
448 28 100       93 $Header = $opt_header if defined $opt_header;
449 28 100       121 $Htmldir = _unixify($opt_htmldir) if defined $opt_htmldir;
450 28 100       141 $Htmlroot = _unixify($opt_htmlroot) if defined $opt_htmlroot;
451 28 100       97 $Doindex = $opt_index if defined $opt_index;
452 28 50       105 $Podfile = _unixify($opt_infile) if defined $opt_infile;
453 28 50       136 $Htmlfile = _unixify($opt_outfile) if defined $opt_outfile;
454 28 100       85 $Poderrors = $opt_poderrors if defined $opt_poderrors;
455 28 100       242 $Podroot = _unixify($opt_podroot) if defined $opt_podroot;
456 28 100       105 $Quiet = $opt_quiet if defined $opt_quiet;
457 28 100       110 $Recurse = $opt_recurse if defined $opt_recurse;
458 28 100       93 $Title = $opt_title if defined $opt_title;
459 28 100       85 $Verbose = $opt_verbose if defined $opt_verbose;
460              
461 28 50 66     98 warn "Flushing directory caches\n"
462             if $opt_verbose && defined $opt_flush;
463 28         90 $Dircache = "$Cachedir/pod2htmd.tmp";
464 28 50       127 if (defined $opt_flush) {
465 0         0 1 while unlink($Dircache);
466             }
467             }
468              
469             my $Saved_Cache_Key;
470              
471             sub get_cache {
472 28     28 0 103 my($dircache, $podpath, $podroot, $recurse) = @_;
473 28         73 my @cache_key_args = @_;
474              
475             # A first-level cache:
476             # Don't bother reading the cache files if they still apply
477             # and haven't changed since we last read them.
478              
479 28         108 my $this_cache_key = cache_key(@cache_key_args);
480 28 100 100     232 return 1 if $Saved_Cache_Key and $this_cache_key eq $Saved_Cache_Key;
481 26         67 $Saved_Cache_Key = $this_cache_key;
482              
483             # load the cache of %Pages if possible. $tests will be
484             # non-zero if successful.
485 26         49 my $tests = 0;
486 26 100       253 if (-f $dircache) {
487 1 50       4 warn "scanning for directory cache\n" if $Verbose;
488 1         3 $tests = load_cache($dircache, $podpath, $podroot);
489             }
490              
491 26         112 return $tests;
492             }
493              
494             sub cache_key {
495 28     28 0 93 my($dircache, $podpath, $podroot, $recurse) = @_;
496 28         567 return join('!',$dircache,$recurse,@$podpath,$podroot,stat($dircache));
497             }
498              
499             #
500             # load_cache - tries to find if the cache stored in $dircache is a valid
501             # cache of %Pages. if so, it loads them and returns a non-zero value.
502             #
503             sub load_cache {
504 1     1 0 3 my($dircache, $podpath, $podroot) = @_;
505 1         3 my $tests = 0;
506 1         1 local $_;
507              
508 1 50       3 warn "scanning for directory cache\n" if $Verbose;
509 1 50       32 open(my $cachefh, '<', $dircache) ||
510             die "$0: error opening $dircache for reading: $!\n";
511 1         5 $/ = "\n";
512              
513             # is it the same podpath?
514 1         19 $_ = <$cachefh>;
515 1         4 chomp($_);
516 1 50       5 $tests++ if (join(":", @$podpath) eq $_);
517              
518             # is it the same podroot?
519 1         2 $_ = <$cachefh>;
520 1         2 chomp($_);
521 1 50       3 $tests++ if ($podroot eq $_);
522              
523             # load the cache if its good
524 1 50       3 if ($tests != 2) {
525 0         0 close($cachefh);
526 0         0 return 0;
527             }
528              
529 1 50       2 warn "loading directory cache\n" if $Verbose;
530 1         10 while (<$cachefh>) {
531 0         0 /(.*?) (.*)$/;
532 0         0 $Pages{$1} = $2;
533             }
534              
535 1         10 close($cachefh);
536 1         6 return 1;
537             }
538              
539              
540             #
541             # html_escape: make text safe for HTML
542             #
543             sub html_escape {
544 28     28 0 79 my $rest = $_[0];
545 28         149 $rest =~ s/&/&/g;
546 28         156 $rest =~ s/
547 28         87 $rest =~ s/>/>/g;
548 28         67 $rest =~ s/"/"/g;
549             # ' is only in XHTML, not HTML4. Be conservative
550             #$rest =~ s/'/'/g;
551 28         87 return $rest;
552             }
553              
554             # "
555              
556             #
557             # htmlify - converts a pod section specification to a suitable section
558             # specification for HTML. Note that we keep spaces and special characters
559             # except ", ? (Netscape problem) and the hyphen (writer's problem...).
560             #
561             sub htmlify {
562 0     0 1 0 my( $heading) = @_;
563 0         0 $heading =~ s/(\s+)/ /g;
564 0         0 $heading =~ s/\s+\Z//;
565 0         0 $heading =~ s/\A\s+//;
566             # The hyphen is a disgrace to the English language.
567             # $heading =~ s/[-"?]//g;
568 0         0 $heading =~ s/["?]//g;
569 0         0 $heading = lc( $heading );
570 0         0 return $heading;
571             }
572              
573             # "
574              
575             #
576             # similar to htmlify, but turns non-alphanumerics into underscores
577             #
578             sub anchorify {
579 0     0 1 0 my ($anchor) = @_;
580 0         0 $anchor = htmlify($anchor);
581 0         0 $anchor =~ s/\W/_/g;
582 0         0 return $anchor;
583             }
584              
585             #
586             # store POD files in %Pages
587             #
588             sub _save_page {
589 422     422   419585 my ($modspec, $modname) = @_;
590              
591             # Remove Podroot from path
592 422 100       22345 $modspec = $Podroot eq File::Spec->curdir
593             ? File::Spec->abs2rel($modspec)
594             : File::Spec->abs2rel($modspec,
595             File::Spec->canonpath($Podroot));
596              
597             # Convert path to unix style path
598 422         1602 $modspec = Pod::L10N::Html::_unixify($modspec);
599              
600 422         9973 my ($file, $dir) = fileparse($modspec, qr/\.[^.]*/); # strip .ext
601 422         2260 $Pages{$modname} = $dir.$file;
602             }
603              
604             sub _unixify {
605 647     647   140205 my $full_path = shift;
606 647 100       2056 return '' unless $full_path;
607 602 100       1348 return $full_path if $full_path eq '/';
608              
609 575         8094 my ($vol, $dirs, $file) = File::Spec->splitpath($full_path);
610 575 50       4090 my @dirs = $dirs eq File::Spec->curdir()
611             ? (File::Spec::Unix->curdir())
612             : File::Spec->splitdir($dirs);
613 575 50 33     2609 if (defined($vol) && $vol) {
614 0 0       0 $vol =~ s/:$// if $^O eq 'VMS';
615 0 0       0 $vol = uc $vol if $^O eq 'MSWin32';
616              
617 0 0       0 if( $dirs[0] ) {
618 0         0 unshift @dirs, $vol;
619             }
620             else {
621 0         0 $dirs[0] = $vol;
622             }
623             }
624 575 100       2773 unshift @dirs, '' if File::Spec->file_name_is_absolute($full_path);
625 575 100       1370 return $file unless scalar(@dirs);
626 555         3371 $full_path = File::Spec::Unix->catfile(File::Spec::Unix->catdir(@dirs),
627             $file);
628 555 50       2045 $full_path =~ s|^\/|| if $^O eq 'MSWin32'; # C:/foo works, /C:/foo doesn't
629 555 50       1177 $full_path =~ s/\^\././g if $^O eq 'VMS'; # unescape dots
630 555         1360 return $full_path;
631             }
632              
633             sub arrange {
634 28     28 0 78 my $fn = shift;
635 28         187 my $base;
636             my $ret;
637 28         0 my $encoding;
638              
639 28         351 $base = Pod::L10N::Model::decode_file($fn);
640              
641 28         8605 for (@$base){
642 354         558 my($o, $t) = @$_;
643 354 100       704 if($o =~ /^=encoding (.+)/){
644 3         72 $encoding = $1;
645 3         19 $ret .= $o . "\n\n";
646 3         9 next;
647             }
648 351 100       780 if($o =~ /^=/){
649 141 100       405 if(defined $t){
650 4         25 $t =~ /\((.+)\)/;
651 4         26 $ret .= $o . '@@@@@@@@@@' . $1;
652             } else {
653 137         227 $ret .= $o;
654             }
655             } else {
656 210 50       322 if(defined $t){
657 0         0 $ret .= $t;
658             } else {
659 210         280 $ret .= $o;
660             }
661             }
662 351         461 $ret .= "\n\n";
663             }
664              
665 28         149 return ($ret, $encoding);
666             }
667              
668             package Pod::L10N::Html::LocalPodLinks;
669 21     21   65488 use strict;
  21         50  
  21         594  
670 21     21   109 use warnings;
  21         35  
  21         891  
671 21     21   9187 use parent 'Pod::Simple::XHTML';
  21         5308  
  21         107  
672              
673 21     21   253559 use File::Spec;
  21         46  
  21         478  
674 21     21   104 use File::Spec::Unix;
  21         38  
  21         25915  
675              
676             __PACKAGE__->_accessorize(
677             'htmldir',
678             'htmlfileurl',
679             'htmlroot',
680             'pages', # Page name => relative/path/to/page from root POD dir
681             'quiet',
682             'verbose',
683             );
684              
685              
686             sub idify {
687 159     159   294 my ($self, $t, $not_unique) = @_;
688 159         323 for ($t) {
689 159         454 s/<[^>]+>//g; # Strip HTML.
690 159         1196 s/&[^;]+;//g; # Strip entities.
691 159         1134 s/^\s+//; s/\s+$//; # Strip white space.
  159         1140  
692 159         1102 s/^([^a-zA-Z]+)$/pod$1/; # Prepend "pod" if no valid chars.
693 159         1150 s/^[^a-zA-Z]+//; # First char must be a letter.
694 159         969 s/[^-a-zA-Z0-9_:.]+/-/g; # All other chars must be valid.
695 159         1710 s/[-:.]+$//; # Strip trailing punctuation.
696             }
697 159 100       701 return $t if $not_unique;
698 83         190 my $i = '';
699 83         473 $i++ while $self->{ids}{"$t$i"}++;
700 83         250 return "$t$i";
701             }
702              
703             sub resolve_pod_page_link {
704 118     118   83614 my ($self, $to, $section) = @_;
705              
706 118 50 66     505 return undef unless defined $to || defined $section;
707 118 100       235 if (defined $section) {
708 76         165 $section = '#' . $self->idify($section, 1);
709 76 100       467 return $section unless defined $to;
710             } else {
711 42         103 $section = '';
712             }
713              
714 71         103 my $path; # path to $to according to %Pages
715 71 100       188 unless (exists $self->pages->{$to}) {
716             # Try to find a POD that ends with $to and use that.
717             # e.g., given L, if there is no $Podpath/XHTML in %Pages,
718             # look for $Podpath/*/XHTML in %Pages, with * being any path,
719             # as a substitute (e.g., $Podpath/Pod/Simple/XHTML)
720 27         536 my @matches;
721 27         48 foreach my $modname (keys %{$self->pages}) {
  27         72  
722 598 100       7683 push @matches, $modname if $modname =~ /::\Q$to\E\z/;
723             }
724              
725 27 100       395 if ($#matches == -1) {
    50          
726 22 50       80 warn "Cannot find \"$to\" in podpath: " .
727             "cannot find suitable replacement path, cannot resolve link\n"
728             unless $self->quiet;
729 22         239 return '';
730             } elsif ($#matches == 0) {
731 5 50       17 warn "Cannot find \"$to\" in podpath: " .
732             "using $matches[0] as replacement path to $to\n"
733             unless $self->quiet;
734 5         34 $path = $self->pages->{$matches[0]};
735             } else {
736 0 0       0 warn "Cannot find \"$to\" in podpath: " .
737             "more than one possible replacement path to $to, " .
738             "using $matches[-1]\n" unless $self->quiet;
739             # Use [-1] so newer (higher numbered) perl PODs are used
740 0         0 $path = $self->pages->{$matches[-1]};
741             }
742             } else {
743 44         832 $path = $self->pages->{$to};
744             }
745              
746 49         723 my $url = File::Spec::Unix->catfile(Pod::L10N::Html::_unixify($self->htmlroot),
747             $path);
748              
749 49 100       195 if ($self->htmlfileurl ne '') {
750             # then $self->htmlroot eq '' (by definition of htmlfileurl) so
751             # $self->htmldir needs to be prepended to link to get the absolute path
752             # that will be relativized
753 23         183 $url = relativize_url(
754             File::Spec::Unix->catdir(Pod::L10N::Html::_unixify($self->htmldir), $url),
755             $self->htmlfileurl # already unixified
756             );
757             }
758              
759 49         351 return $url . ".html$section";
760             }
761              
762             #
763             # relativize_url - convert an absolute URL to one relative to a base URL.
764             # Assumes both end in a filename.
765             #
766             sub relativize_url {
767 23     23   253 my ($dest, $source) = @_;
768              
769             # Remove each file from its path
770 23         191 my ($dest_volume, $dest_directory, $dest_file) =
771             File::Spec::Unix->splitpath( $dest );
772 23         179 $dest = File::Spec::Unix->catpath( $dest_volume, $dest_directory, '' );
773              
774 23         166 my ($source_volume, $source_directory, $source_file) =
775             File::Spec::Unix->splitpath( $source );
776 23         113 $source = File::Spec::Unix->catpath( $source_volume, $source_directory, '' );
777              
778 23         49 my $rel_path = '';
779 23 50       56 if ($dest ne '') {
780 23         1272 $rel_path = File::Spec::Unix->abs2rel( $dest, $source );
781             }
782              
783 23 50 33     177 if ($rel_path ne '' && substr( $rel_path, -1 ) ne '/') {
784 23         61 $rel_path .= "/$dest_file";
785             } else {
786 0         0 $rel_path .= "$dest_file";
787             }
788              
789 23         52 return $rel_path;
790             }
791              
792             sub _end_head {
793 67     67   52712 my $h = delete $_[0]{in_head};
794              
795 67         343 my $add = $_[0]->html_h_level;
796 67 50       482 $add = 1 unless defined $add;
797 67         140 $h += $add - 1;
798              
799 67         279 my ($orig, $trans) = split /@@@@@@@@@@/, $_[0]{scratch};
800 67 100       166 if(!defined $trans){
801 65         109 $trans = $orig;
802             }
803 67         182 my $id = $_[0]->idify($orig);
804 67         114 my $text = $trans;
805 67 100 66     217 $_[0]{'scratch'} = $_[0]->backlink && ($h - $add == 0)
806             # backlinks enabled && =head1
807             ? qq{$text}
808             : qq{$text};
809 67         794 $_[0]->emit;
810 67         836 push @{ $_[0]{'to_index'} }, [$h, $id, $text];
  67         285  
811             }
812              
813             sub end_item_text {
814 16     16   20431 my ($orig, $trans) = split /@@@@@@@@@@/, $_[0]{scratch};
815 16 100       73 if(!defined $trans){
816 14         24 $trans = $orig;
817             }
818              
819             # idify and anchor =item content if wanted
820 16 50       68 my $dt_id = $_[0]{'anchor_items'}
821             ? ' id="'. $_[0]->idify($orig) .'"'
822             : '';
823              
824             # reset scratch
825 16         27 my $text = $trans;
826 16         41 $_[0]{'scratch'} = '';
827              
828 16 100       79 if ($_[0]{'in_dd'}[ $_[0]{'dl_level'} ]) {
829 6         10 $_[0]{'scratch'} = "\n";
830 6         9 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 0;
831             }
832              
833 16         63 $_[0]{'scratch'} .= qq{$text\n
};
834 16         37 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 1;
835 16         47 $_[0]->emit;
836             }
837              
838             1;