File Coverage

blib/lib/Pod/L10N/Html.pm
Criterion Covered Total %
statement 321 364 88.1
branch 131 184 71.2
condition 42 77 54.5
subroutine 33 37 89.1
pod 3 12 25.0
total 530 674 78.6


line stmt bran cond sub pod time code
1             package Pod::L10N::Html;
2 20     20   186751 use strict;
  20         153  
  20         1439  
3             require Exporter;
4              
5             our $VERSION = '1.06';
6             our @ISA = qw(Exporter);
7             our @EXPORT = qw(pod2htmll10n htmlify);
8             our @EXPORT_OK = qw(anchorify relativize_url);
9              
10 20     20   113 use Carp;
  20         28  
  20         1326  
11 20     20   107 use Config;
  20         34  
  20         607  
12 20     20   98 use Cwd;
  20         31  
  20         1015  
13 20     20   104 use File::Basename;
  20         30  
  20         2100  
14 20     20   108 use File::Spec;
  20         28  
  20         392  
15 20     20   75 use File::Spec::Unix;
  20         36  
  20         482  
16 20     20   12440 use Getopt::Long;
  20         184894  
  20         75  
17 20     20   15312 use Pod::Simple::Search;
  20         104317  
  20         543  
18 20     20   7998 use Pod::Simple::SimpleTree ();
  20         534498  
  20         491  
19              
20 20     20   8477 use Pod::L10N::Model;
  20         13130  
  20         643  
21              
22 20     20   8400 use locale; # make \w work right in non-ASCII lands
  20         9967  
  20         97  
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 48     48 0 349 $Cachedir = "."; # The directory to which directory caches
142             # will be written.
143              
144 48         221 $Dircache = "pod2htmd.tmp";
145              
146 48         196 $Htmlroot = "/"; # http-server base directory from which all
147             # relative paths in $podpath stem.
148 48         194 $Htmldir = ""; # The directory to which the html pages
149             # will (eventually) be written.
150 48         174 $Htmlfile = ""; # write to stdout by default
151 48         274 $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 48         193 $Poderrors = 1;
157 48         148 $Podfile = ""; # read from stdin by default
158 48         172 @Podpath = (); # list of directories containing library pods.
159 48         100 $Podroot = $Curdir; # filesystem base directory from which all
160             # relative paths in $podpath stem.
161 48         121 $Css = ''; # Cascading style sheet
162 48         98 $Recurse = 1; # recurse on subdirectories in $podpath.
163 48         77 $Quiet = 0; # not quiet by default
164 48         64 $Verbose = 0; # not verbose by default
165 48         69 $Doindex = 1; # non-zero if we should generate an index
166 48         58 $Backlink = 0; # no backlinks added by default
167 48         64 $Header = 0; # produce block header/footer
168 48         82 $Title = undef; # title to give the pod(s)
169             }
170              
171             sub pod2htmll10n {
172 28     28 1 6713 local(@ARGV) = @_;
173 28         159 local $_;
174              
175 28         201 init_globals();
176 28         255 parse_command_line();
177              
178             # prevent '//' in urls
179 28 100       95 $Htmlroot = "" if $Htmlroot eq "/";
180 28         188 $Htmldir =~ s#/\z##;
181              
182 28 100 66     396 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         37 $Htmlfileurl = Pod::L10N::Html::_unixify($Htmlfile);
194              
195             }
196              
197             # load or generate/cache %Pages
198 28 100       133 unless (get_cache($Dircache, \@Podpath, $Podroot, $Recurse)) {
199             # generate %Pages
200 25         283 my $pwd = getcwd();
201 25 50       395 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         658 Pod::Simple::Search->new->inc(0)->verbose($Verbose)->laborious(1)
208             ->callback(\&_save_page)->recurse($Recurse)->survey(@Podpath);
209              
210 25 50       20744 chdir($pwd) || die "$0: error changing to directory $pwd: $!\n";
211              
212             # cache the directory list for later use
213 25 100       183 warn "caching directories for later use\n" if $Verbose;
214 25 50       2490 open my $cache, '>', $Dircache
215             or die "$0: error open $Dircache for writing: $!\n";
216              
217 25         371 print $cache join(":", @Podpath) . "\n$Podroot\n";
218 25   33     316 my $_updirs_only = ($Podroot =~ /\.\./) && !($Podroot =~ /[^\.\\\/]/);
219 25         289 foreach my $key (keys %Pages) {
220 468 50       677 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         1095 print $cache "$key $Pages{$key}\n";
229             }
230              
231 25 50       1606 close $cache or die "error closing $Dircache: $!";
232             }
233              
234 28         196 my $input;
235 28 50 33     179 unless (@ARGV && $ARGV[0]) {
236 28 50 33     301 if ($Podfile and $Podfile ne '-') {
237 28         88 $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         135 my ($content, $encoding) = arrange($Podfile);
247 28 100       95 if(!defined $encoding){
248 25         157 $encoding = 'utf-8';
249             }
250              
251             # set options for input parser
252 28         969 my $parser = Pod::Simple::SimpleTree->new;
253 28         3051 $parser->codes_in_verbatim(0);
254 28         826 $parser->accept_targets(qw(html HTML));
255 28         1868 $parser->no_errata_section(!$Poderrors); # note the inverse
256              
257 28 100       263 warn "Converting input file $Podfile\n" if $Verbose;
258 28         280 my $podtree = $parser->parse_string_document($content)->root;
259              
260 28 100       184161 unless(defined $Title) {
261 21 100 33     546 if($podtree->[0] eq "Document" && ref($podtree->[2]) eq "ARRAY" &&
      33        
      33        
      33        
      66        
      66        
      66        
      33        
      33        
      100        
      66        
262 21         493 $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         169 @{$podtree->[3]} >= 3 &&
266 20         335 !(grep { ref($_) ne "" }
267 20         58 @{$podtree->[3]}[2..$#{$podtree->[3]}]) &&
  20         70  
268             (@$podtree == 4 ||
269             (ref($podtree->[4]) eq "ARRAY" &&
270             $podtree->[4]->[0] eq "head1"))) {
271 18         123 $Title = join("", @{$podtree->[3]}[2..$#{$podtree->[3]}]);
  18         64  
  18         56  
272             }
273             }
274              
275 28   100     126 $Title //= "";
276 28         246 $Title = html_escape($Title);
277              
278             # set options for the HTML generator
279 28         927 $parser = Pod::L10N::Html::LocalPodLinks->new();
280 28         5822 $parser->codes_in_verbatim(0);
281 28         500 $parser->anchor_items(1); # the old Pod::Html always did
282 28         289 $parser->backlink($Backlink); # linkify =head1 directives
283 28         289 $parser->force_title($Title);
284 28         393 $parser->htmldir($Htmldir);
285 28         298 $parser->htmlfileurl($Htmlfileurl);
286 28         280 $parser->htmlroot($Htmlroot);
287 28         341 $parser->index($Doindex);
288             # still need as parse twice
289 28         252 $parser->no_errata_section(!$Poderrors); # note the inverse
290 28         391 $parser->output_string(\my $output); # written to file later
291 28         48975 $parser->pages(\%Pages);
292 28         278 $parser->quiet($Quiet);
293 28         270 $parser->verbose($Verbose);
294              
295             # $parser->html_charset('UTF-8');
296 28         326 $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         131 my $csslink = '';
305 28         66 my $tdstyle = ' style="background-color: #cccccc; color: #000"';
306              
307 28 100       94 if ($Css) {
308 4         15 $csslink = qq(\n);
309 4         12 $csslink =~ s,\\,/,g;
310 4         18 $csslink =~ s,(/.):,$1|,;
311 4         6 $tdstyle= '';
312             }
313              
314             # header/footer block
315 28 100       173 my $block = $Header ? <
316            
317            
318              $Title
319            
320            
321             END_OF_BLOCK
322              
323             # create own header/footer because of --header
324 28         2481 $parser->html_header(<<"HTMLHEAD");
325            
326            
327            
328            
329             $Title$csslink
330            
331            
332            
333              
334            
335             $block
336             HTMLHEAD
337              
338 28         377 $parser->html_footer(<<"HTMLFOOT");
339             $block
340            
341              
342            
343             HTMLFOOT
344              
345             # $parser->parse_file($input);
346 28         335 $parser->parse_string_document($content);
347             # $parser->html_charset($parser->encoding());
348              
349             # Write output to file
350 28 50       20093 $Htmlfile = "-" unless $Htmlfile; # stdout
351 28         63 my $fhout;
352 28 50 33     234 if($Htmlfile and $Htmlfile ne '-') {
353 28 50       2788 open $fhout, ">", $Htmlfile
354             or die "$0: cannot open $Htmlfile file for output: $!\n";
355             } else {
356 0         0 open $fhout, ">-";
357             }
358 20     20   314 binmode $fhout, ":encoding($encoding)";
  20         54  
  20         322  
  28         1391  
359 28         189629 print $fhout $output;
360 28 50       1337 close $fhout or die "Failed to close $Htmlfile: $!";
361 28 50       2243 chmod 0644, $Htmlfile unless $Htmlfile eq '-';
362             }
363              
364             ##############################################################################
365              
366             sub usage {
367 0     0 0 0 my $podfile = shift;
368 0 0       0 warn "$0: $podfile: @_\n" if @_;
369 0         0 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 28     28 0 182 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 28 50       3695 unshift @ARGV, split ' ', $Config{pod2html} if $Config{pod2html};
418 28         562 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 28 50       60887 usage("-", "invalid parameters") if not $result;
439              
440 28 50       112 usage("-") if defined $opt_help; # see if the user asked for help
441 28         144 $opt_help = ""; # just to make -w shut-up.
442              
443 28 100       184 @Podpath = split(":", $opt_podpath) if defined $opt_podpath;
444              
445 28 100       143 $Backlink = $opt_backlink if defined $opt_backlink;
446 28 100       104 $Cachedir = _unixify($opt_cachedir) if defined $opt_cachedir;
447 28 100       100 $Css = $opt_css if defined $opt_css;
448 28 100       86 $Header = $opt_header if defined $opt_header;
449 28 100       103 $Htmldir = _unixify($opt_htmldir) if defined $opt_htmldir;
450 28 100       129 $Htmlroot = _unixify($opt_htmlroot) if defined $opt_htmlroot;
451 28 100       97 $Doindex = $opt_index if defined $opt_index;
452 28 50       102 $Podfile = _unixify($opt_infile) if defined $opt_infile;
453 28 50       132 $Htmlfile = _unixify($opt_outfile) if defined $opt_outfile;
454 28 100       100 $Poderrors = $opt_poderrors if defined $opt_poderrors;
455 28 100       252 $Podroot = _unixify($opt_podroot) if defined $opt_podroot;
456 28 100       108 $Quiet = $opt_quiet if defined $opt_quiet;
457 28 100       92 $Recurse = $opt_recurse if defined $opt_recurse;
458 28 100       115 $Title = $opt_title if defined $opt_title;
459 28 100       94 $Verbose = $opt_verbose if defined $opt_verbose;
460              
461 28 50 66     110 warn "Flushing directory caches\n"
462             if $opt_verbose && defined $opt_flush;
463 28         83 $Dircache = "$Cachedir/pod2htmd.tmp";
464 28 50       115 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 106 my($dircache, $podpath, $podroot, $recurse) = @_;
473 28         81 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         91 my $this_cache_key = cache_key(@cache_key_args);
480 28 100 100     207 return 1 if $Saved_Cache_Key and $this_cache_key eq $Saved_Cache_Key;
481 26         73 $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         54 my $tests = 0;
486 26 100       257 if (-f $dircache) {
487 1 50       5 warn "scanning for directory cache\n" if $Verbose;
488 1         4 $tests = load_cache($dircache, $podpath, $podroot);
489             }
490              
491 26         115 return $tests;
492             }
493              
494             sub cache_key {
495 28     28 0 78 my($dircache, $podpath, $podroot, $recurse) = @_;
496 28         538 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         2 my $tests = 0;
506 1         2 local $_;
507              
508 1 50       4 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         4 $/ = "\n";
512              
513             # is it the same podpath?
514 1         19 $_ = <$cachefh>;
515 1         4 chomp($_);
516 1 50       6 $tests++ if (join(":", @$podpath) eq $_);
517              
518             # is it the same podroot?
519 1         3 $_ = <$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       3 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         9 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 84 my $rest = $_[0];
545 28         139 $rest =~ s/&/&/g;
546 28         81 $rest =~ s/
547 28         65 $rest =~ s/>/>/g;
548 28         57 $rest =~ s/"/"/g;
549             # ' is only in XHTML, not HTML4. Be conservative
550             #$rest =~ s/'/'/g;
551 28         74 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   303327 my ($modspec, $modname) = @_;
590              
591             # Remove Podroot from path
592 422 100       20877 $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         1502 $modspec = Pod::L10N::Html::_unixify($modspec);
599              
600 422         9504 my ($file, $dir) = fileparse($modspec, qr/\.[^.]*/); # strip .ext
601 422         2169 $Pages{$modname} = $dir.$file;
602             }
603              
604             sub _unixify {
605 647     647   134897 my $full_path = shift;
606 647 100       1968 return '' unless $full_path;
607 602 100       1285 return $full_path if $full_path eq '/';
608              
609 575         7358 my ($vol, $dirs, $file) = File::Spec->splitpath($full_path);
610 575 50       3920 my @dirs = $dirs eq File::Spec->curdir()
611             ? (File::Spec::Unix->curdir())
612             : File::Spec->splitdir($dirs);
613 575 50 33     2507 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       2733 unshift @dirs, '' if File::Spec->file_name_is_absolute($full_path);
625 575 100       1290 return $file unless scalar(@dirs);
626 555         3248 $full_path = File::Spec::Unix->catfile(File::Spec::Unix->catdir(@dirs),
627             $file);
628 555 50       1957 $full_path =~ s|^\/|| if $^O eq 'MSWin32'; # C:/foo works, /C:/foo doesn't
629 555 50       1057 $full_path =~ s/\^\././g if $^O eq 'VMS'; # unescape dots
630 555         1282 return $full_path;
631             }
632              
633             sub arrange {
634 28     28 0 78 my $fn = shift;
635 28         141 my $base;
636             my $ret;
637 28         0 my $encoding;
638              
639 28         268 $base = Pod::L10N::Model::decode_file($fn);
640              
641 28         8029 for (@$base){
642 354         589 my($o, $t) = @$_;
643 354 100       707 if($o =~ /^=encoding (.+)/){
644 3         58 $encoding = $1;
645 3         18 $ret .= $o . "\n\n";
646 3         6 next;
647             }
648 351 100       731 if($o =~ /^=/){
649 141 100       395 if(defined $t){
650 4         24 $t =~ /\((.+)\)/;
651 4         26 $ret .= $o . '@@@@@@@@@@' . $1;
652             } else {
653 137         224 $ret .= $o;
654             }
655             } else {
656 210 50       291 if(defined $t){
657 0         0 $ret .= $t;
658             } else {
659 210         290 $ret .= $o;
660             }
661             }
662 351         490 $ret .= "\n\n";
663             }
664              
665 28         160 return ($ret, $encoding);
666             }
667              
668             package Pod::L10N::Html::LocalPodLinks;
669 20     20   60598 use strict;
  20         41  
  20         557  
670 20     20   94 use warnings;
  20         36  
  20         912  
671 20     20   8039 use parent 'Pod::Simple::XHTML';
  20         4864  
  20         95  
672              
673 20     20   213385 use File::Spec;
  20         42  
  20         426  
674 20     20   90 use File::Spec::Unix;
  20         39  
  20         24089  
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   282 my ($self, $t, $not_unique) = @_;
688 159         292 for ($t) {
689 159         434 s/<[^>]+>//g; # Strip HTML.
690 159         1077 s/&[^;]+;//g; # Strip entities.
691 159         1178 s/^\s+//; s/\s+$//; # Strip white space.
  159         1079  
692 159         1082 s/^([^a-zA-Z]+)$/pod$1/; # Prepend "pod" if no valid chars.
693 159         1081 s/^[^a-zA-Z]+//; # First char must be a letter.
694 159         949 s/[^-a-zA-Z0-9_:.]+/-/g; # All other chars must be valid.
695 159         1731 s/[-:.]+$//; # Strip trailing punctuation.
696             }
697 159 100       658 return $t if $not_unique;
698 83         168 my $i = '';
699 83         483 $i++ while $self->{ids}{"$t$i"}++;
700 83         252 return "$t$i";
701             }
702              
703             sub resolve_pod_page_link {
704 118     118   81033 my ($self, $to, $section) = @_;
705              
706 118 50 66     384 return undef unless defined $to || defined $section;
707 118 100       217 if (defined $section) {
708 76         149 $section = '#' . $self->idify($section, 1);
709 76 100       423 return $section unless defined $to;
710             } else {
711 42         109 $section = '';
712             }
713              
714 71         100 my $path; # path to $to according to %Pages
715 71 100       175 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         485 my @matches;
721 27         41 foreach my $modname (keys %{$self->pages}) {
  27         82  
722 598 100       7547 push @matches, $modname if $modname =~ /::\Q$to\E\z/;
723             }
724              
725 27 100       378 if ($#matches == -1) {
    50          
726 22 50       75 warn "Cannot find \"$to\" in podpath: " .
727             "cannot find suitable replacement path, cannot resolve link\n"
728             unless $self->quiet;
729 22         228 return '';
730             } elsif ($#matches == 0) {
731 5 50       14 warn "Cannot find \"$to\" in podpath: " .
732             "using $matches[0] as replacement path to $to\n"
733             unless $self->quiet;
734 5         31 $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         761 $path = $self->pages->{$to};
744             }
745              
746 49         700 my $url = File::Spec::Unix->catfile(Pod::L10N::Html::_unixify($self->htmlroot),
747             $path);
748              
749 49 100       182 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         174 $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         412 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   173 my ($dest, $source) = @_;
768              
769             # Remove each file from its path
770 23         190 my ($dest_volume, $dest_directory, $dest_file) =
771             File::Spec::Unix->splitpath( $dest );
772 23         165 $dest = File::Spec::Unix->catpath( $dest_volume, $dest_directory, '' );
773              
774 23         157 my ($source_volume, $source_directory, $source_file) =
775             File::Spec::Unix->splitpath( $source );
776 23         117 $source = File::Spec::Unix->catpath( $source_volume, $source_directory, '' );
777              
778 23         41 my $rel_path = '';
779 23 50       49 if ($dest ne '') {
780 23         1131 $rel_path = File::Spec::Unix->abs2rel( $dest, $source );
781             }
782              
783 23 50 33     156 if ($rel_path ne '' && substr( $rel_path, -1 ) ne '/') {
784 23         53 $rel_path .= "/$dest_file";
785             } else {
786 0         0 $rel_path .= "$dest_file";
787             }
788              
789 23         55 return $rel_path;
790             }
791              
792             sub _end_head {
793 67     67   50518 my $h = delete $_[0]{in_head};
794              
795 67         316 my $add = $_[0]->html_h_level;
796 67 50       466 $add = 1 unless defined $add;
797 67         146 $h += $add - 1;
798              
799 67         280 my ($orig, $trans) = split /@@@@@@@@@@/, $_[0]{scratch};
800 67 100       157 if(!defined $trans){
801 65         98 $trans = $orig;
802             }
803 67         200 my $id = $_[0]->idify($orig);
804 67         124 my $text = $trans;
805 67 100 66     228 $_[0]{'scratch'} = $_[0]->backlink && ($h - $add == 0)
806             # backlinks enabled && =head1
807             ? qq{$text}
808             : qq{$text};
809 67         770 $_[0]->emit;
810 67         809 push @{ $_[0]{'to_index'} }, [$h, $id, $text];
  67         304  
811             }
812              
813             sub end_item_text {
814 16     16   19770 my ($orig, $trans) = split /@@@@@@@@@@/, $_[0]{scratch};
815 16 100       72 if(!defined $trans){
816 14         23 $trans = $orig;
817             }
818              
819             # idify and anchor =item content if wanted
820 16 50       69 my $dt_id = $_[0]{'anchor_items'}
821             ? ' id="'. $_[0]->idify($orig) .'"'
822             : '';
823              
824             # reset scratch
825 16         28 my $text = $trans;
826 16         39 $_[0]{'scratch'} = '';
827              
828 16 100       48 if ($_[0]{'in_dd'}[ $_[0]{'dl_level'} ]) {
829 6         12 $_[0]{'scratch'} = "\n";
830 6         13 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 0;
831             }
832              
833 16         63 $_[0]{'scratch'} .= qq{$text\n
};
834 16         41 $_[0]{'in_dd'}[ $_[0]{'dl_level'} ] = 1;
835 16         57 $_[0]->emit;
836             }
837              
838             1;