This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Bump $Pod::Html::VERSION to reflect the changes of commit 978af2c644ee9abf.
[perl5.git] / ext / Pod-Html / lib / Pod / Html.pm
CommitLineData
54310121 1package Pod::Html;
0e4548d5 2use strict;
54310121 3require Exporter;
0e4548d5 4
776d31fe 5use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
99565752 6$VERSION = 1.16;
0e4548d5 7@ISA = qw(Exporter);
54310121 8@EXPORT = qw(pod2html htmlify);
776d31fe 9@EXPORT_OK = qw(anchorify);
54310121 10
11use Carp;
0e4548d5
GS
12use Config;
13use Cwd;
99499175 14use File::Basename;
af75be11 15use File::Spec;
0e4548d5
GS
16use File::Spec::Unix;
17use Getopt::Long;
76589d6f 18use Pod::Simple::Search;
54310121 19
ed36117c 20use locale; # make \w work right in non-ASCII lands
3ec07288 21
54310121 22=head1 NAME
23
7b8d334a 24Pod::Html - module to convert pod files to HTML
54310121 25
26=head1 SYNOPSIS
27
7b8d334a 28 use Pod::Html;
54310121 29 pod2html([options]);
30
31=head1 DESCRIPTION
32
33Converts files from pod format (see L<perlpod>) to HTML format. It
b09e89a9
MG
34can automatically generate indexes and cross-references, and it keeps
35a cache of things it knows how to cross-reference.
54310121 36
95fb620e 37=head1 FUNCTIONS
54310121 38
95fb620e
SP
39=head2 pod2html
40
41 pod2html("pod2html",
42 "--podpath=lib:ext:pod:vms",
43 "--podroot=/usr/src/perl",
44 "--htmlroot=/perl/nmanual",
95fb620e
SP
45 "--recurse",
46 "--infile=foo.pod",
47 "--outfile=/perl/nmanual/foo.html");
48
49pod2html takes the following arguments:
54310121 50
51=over 4
52
0e4548d5
GS
53=item backlink
54
5c988a71 55 --backlink
0e4548d5 56
5c988a71
MG
57Turns every C<head1> heading into a link back to the top of the page.
58By default, no backlinks are generated.
0e4548d5 59
b09e89a9
MG
60=item cachedir
61
62 --cachedir=name
63
64Creates the directory cache in the given directory.
65
0e4548d5
GS
66=item css
67
68 --css=stylesheet
69
59ecbafa
VS
70Specify the URL of a cascading style sheet. Also disables all HTML/CSS
71C<style> attributes that are output by default (to avoid conflicts).
0e4548d5 72
b09e89a9
MG
73=item flush
74
75 --flush
76
77Flushes the directory cache.
78
3ada32a3
MG
79=item header
80
81 --header
82 --noheader
83
84Creates header and footer blocks containing the text of the C<NAME>
85section. By default, no headers are generated.
86
54310121 87=item help
88
89 --help
90
91Displays the usage message.
92
5a039dd3
BS
93=item htmldir
94
95 --htmldir=name
96
707a94fe
MG
97Sets the directory to which all cross references in the resulting
98html file will be relative. Not passing this causes all links to be
99absolute since this is the value that tells Pod::Html the root of the
100documentation tree.
101
102Do not use this and --htmlroot in the same call to pod2html; they are
103mutually exclusive.
5a039dd3 104
54310121 105=item htmlroot
106
107 --htmlroot=name
108
109Sets the base URL for the HTML files. When cross-references are made,
110the HTML root is prepended to the URL.
111
707a94fe
MG
112Do not use this if relative links are desired: use --htmldir instead.
113
114Do not pass both this and --htmldir to pod2html; they are mutually
115exclusive.
116
0e4548d5
GS
117=item index
118
119 --index
120 --noindex
121
122Generate an index at the top of the HTML file. This is the default
123behaviour.
124
54310121 125=item infile
126
127 --infile=name
128
129Specify the pod file to convert. Input is taken from STDIN if no
130infile is specified.
131
0e4548d5 132=item outfile
54310121 133
0e4548d5 134 --outfile=name
54310121 135
0e4548d5
GS
136Specify the HTML file to create. Output goes to STDOUT if no outfile
137is specified.
54310121 138
8ef5953c
MG
139=item poderrors
140
141 --poderrors
142 --nopoderrors
143
144Include a "POD ERRORS" section in the outfile if there were any POD
145errors in the infile. This section is included by default.
146
0e4548d5 147=item podpath
54310121 148
0e4548d5 149 --podpath=name:...:name
54310121 150
0e4548d5 151Specify which subdirectories of the podroot contain pod files whose
59ecbafa 152HTML converted forms can be linked to in cross references.
54310121 153
0e4548d5 154=item podroot
54310121 155
0e4548d5 156 --podroot=name
54310121 157
042c2bc8 158Specify the base directory for finding library pods. Default is the
f360b455 159current working directory.
54310121 160
0e4548d5 161=item quiet
54310121 162
0e4548d5
GS
163 --quiet
164 --noquiet
54310121 165
0e4548d5
GS
166Don't display I<mostly harmless> warning messages. These messages
167will be displayed by default. But this is not the same as C<verbose>
168mode.
54310121 169
0e4548d5 170=item recurse
54310121 171
0e4548d5 172 --recurse
54310121 173 --norecurse
174
0e4548d5 175Recurse into subdirectories specified in podpath (default behaviour).
54310121 176
177=item title
178
179 --title=title
180
181Specify the title of the resulting HTML file.
182
183=item verbose
184
185 --verbose
0e4548d5 186 --noverbose
54310121 187
0e4548d5 188Display progress messages. By default, they won't be displayed.
34db337b 189
54310121 190=back
191
95fb620e 192=head2 htmlify
54310121 193
95fb620e
SP
194 htmlify($heading);
195
196Converts a pod section specification to a suitable section specification
d42858b2 197for HTML. Note that we keep spaces and special characters except
95fb620e
SP
198C<", ?> (Netscape problem) and the hyphen (writer's problem...).
199
200=head2 anchorify
201
202 anchorify(@heading);
203
204Similar to C<htmlify()>, but turns non-alphanumerics into underscores. Note
205that C<anchorify()> is not exported by default.
54310121 206
34db337b
JD
207=head1 ENVIRONMENT
208
59ecbafa 209Uses C<$Config{pod2html}> to setup default options.
34db337b 210
54310121 211=head1 AUTHOR
212
e812cb3f
MG
213Marc Green, E<lt>marcgreen@cpan.orgE<gt>.
214
215Original version by Tom Christiansen, E<lt>tchrist@perl.comE<gt>.
54310121 216
54310121 217=head1 SEE ALSO
218
219L<perlpod>
220
221=head1 COPYRIGHT
222
223This program is distributed under the Artistic License.
224
225=cut
226
b09e89a9
MG
227my $Cachedir;
228my $Dircache;
8e1ba33c 229my($Htmlroot, $Htmldir, $Htmlfile, $Htmlfileurl);
69348ecf 230my($Podfile, @Podpath, $Podroot);
8ef5953c 231my $Poderrors;
69348ecf 232my $Css;
54310121 233
69348ecf
MS
234my $Recurse;
235my $Quiet;
236my $Verbose;
237my $Doindex;
54310121 238
69348ecf 239my $Backlink;
99cb6bd8 240
3ada32a3 241my($Title, $Header);
99cb6bd8 242
ed36117c 243my %Pages = (); # associative array used to find the location
4979c083 244 # of pages referenced by L<> links.
99cb6bd8
MS
245
246my $Curdir = File::Spec->curdir;
247
248init_globals();
39e571d4 249
54310121 250sub init_globals {
b09e89a9
MG
251 $Cachedir = "."; # The directory to which directory caches
252 # will be written.
253
254 $Dircache = "pod2htmd.tmp";
255
ed36117c 256 $Htmlroot = "/"; # http-server base directory from which all
4979c083 257 # relative paths in $podpath stem.
ed36117c 258 $Htmldir = ""; # The directory to which the html pages
707a94fe 259 # will (eventually) be written.
ed36117c
MG
260 $Htmlfile = ""; # write to stdout by default
261 $Htmlfileurl = ""; # The url that other files would use to
4979c083
MG
262 # refer to this file. This is only used
263 # to make relative urls that point to
264 # other files.
99cb6bd8 265
8ef5953c 266 $Poderrors = 1;
ed36117c
MG
267 $Podfile = ""; # read from stdin by default
268 @Podpath = (); # list of directories containing library pods.
269 $Podroot = $Curdir; # filesystem base directory from which all
4979c083 270 # relative paths in $podpath stem.
69348ecf 271 $Css = ''; # Cascading style sheet
ed36117c
MG
272 $Recurse = 1; # recurse on subdirectories in $podpath.
273 $Quiet = 0; # not quiet by default
274 $Verbose = 0; # not verbose by default
275 $Doindex = 1; # non-zero if we should generate an index
276 $Backlink = 0; # no backlinks added by default
277 $Header = 0; # produce block header/footer
278 $Title = ''; # title to give the pod(s)
2a28b791
GS
279}
280
54310121 281sub pod2html {
282 local(@ARGV) = @_;
54310121 283 local $_;
284
285 init_globals();
54310121 286 parse_command_line();
287
8e1ba33c 288 # prevent '//' in urls
a4fca471
MG
289 $Htmlroot = "" if $Htmlroot eq "/";
290 $Htmldir =~ s#/\z##;
8e1ba33c
MG
291
292 if ( $Htmlroot eq ''
293 && defined( $Htmldir )
294 && $Htmldir ne ''
295 && substr( $Htmlfile, 0, length( $Htmldir ) ) eq $Htmldir
ed36117c
MG
296 ) {
297 # Set the 'base' url for this file, so that we can use it
298 # as the location from which to calculate relative links
299 # to other files. If this is '', then absolute links will
300 # be used throughout.
7c41f1ea
MG
301 #$Htmlfileurl = "$Htmldir/" . substr( $Htmlfile, length( $Htmldir ) + 1);
302 # Is the above not just "$Htmlfileurl = $Htmlfile"?
b069bc5e 303 $Htmlfileurl = Pod::Html::_unixify($Htmlfile);
7c41f1ea 304
8e1ba33c 305 }
d5c3bc55 306
b09e89a9
MG
307 # load or generate/cache %Pages
308 unless (get_cache($Dircache, \@Podpath, $Podroot, $Recurse)) {
309 # generate %Pages
310 my $pwd = getcwd();
311 chdir($Podroot) ||
312 die "$0: error changing to directory $Podroot: $!\n";
313
314 # find all pod modules/pages in podpath, store in %Pages
315 # - callback used to remove Podroot and extension from each file
316 # - laborious to allow '.' in dirnames (e.g., /usr/share/perl/5.14.1)
317 Pod::Simple::Search->new->inc(0)->verbose($Verbose)->laborious(1)
318 ->callback(\&_save_page)->recurse($Recurse)->survey(@Podpath);
319
320 chdir($pwd) || die "$0: error changing to directory $pwd: $!\n";
321
322 # cache the directory list for later use
323 warn "caching directories for later use\n" if $Verbose;
324 open my $cache, '>', $Dircache
325 or die "$0: error open $Dircache for writing: $!\n";
326
327 print $cache join(":", @Podpath) . "\n$Podroot\n";
495a00a1 328 my $_updirs_only = ($Podroot =~ /\.\./) && !($Podroot =~ /[^\.\\\/]/);
b09e89a9 329 foreach my $key (keys %Pages) {
495a00a1
RS
330 if($_updirs_only) {
331 my $_dirlevel = $Podroot;
332 while($_dirlevel =~ /\.\./) {
333 $_dirlevel =~ s/\.\.//;
334 # Assume $Pages{$key} has '/' separators (html dir separators).
53a8d1b8 335 $Pages{$key} =~ s/^[\w\s\-\.]+\///;
495a00a1
RS
336 }
337 }
b09e89a9
MG
338 print $cache "$key $Pages{$key}\n";
339 }
042c2bc8 340
b09e89a9
MG
341 close $cache or die "error closing $Dircache: $!";
342 }
f6e6d881 343
5c988a71
MG
344 # set options for the parser
345 my $parser = Pod::Simple::XHTML::LocalPodLinks->new();
642ef7c1 346 $parser->codes_in_verbatim(0);
8ef5953c 347 $parser->anchor_items(1); # the old Pod::Html always did
f360b455 348 $parser->backlink($Backlink); # linkify =head1 directives
a4fca471 349 $parser->htmldir($Htmldir);
8e1ba33c 350 $parser->htmlfileurl($Htmlfileurl);
a4fca471 351 $parser->htmlroot($Htmlroot);
5c988a71 352 $parser->index($Doindex);
8ef5953c 353 $parser->no_errata_section(!$Poderrors); # note the inverse
5c988a71 354 $parser->output_string(\my $output); # written to file later
f360b455 355 $parser->pages(\%Pages);
d5c3bc55 356 $parser->quiet($Quiet);
99499175 357 $parser->verbose($Verbose);
3ada32a3 358
e7721e60
MG
359 # XXX: implement default title generator in pod::simple::xhtml
360 # copy the way the old Pod::Html did it
3ada32a3
MG
361 $Title = html_escape($Title);
362
f360b455 363 # We need to add this ourselves because we use our own header, not
e7721e60 364 # ::XHTML's header. We need to set $parser->backlink to linkify
f360b455 365 # the =head1 directives
73c2e3ad 366 my $bodyid = $Backlink ? ' id="_podtop_"' : '';
f360b455 367
401f7940
MG
368 my $csslink = '';
369 my $bodystyle = ' style="background-color: white"';
370 my $tdstyle = ' style="background-color: #cccccc"';
371
3ada32a3 372 if ($Css) {
ed36117c
MG
373 $csslink = qq(\n<link rel="stylesheet" href="$Css" type="text/css" />);
374 $csslink =~ s,\\,/,g;
375 $csslink =~ s,(/.):,$1|,;
376 $bodystyle = '';
377 $tdstyle= '';
3ada32a3
MG
378 }
379
401f7940 380 # header/footer block
3ada32a3
MG
381 my $block = $Header ? <<END_OF_BLOCK : '';
382<table border="0" width="100%" cellspacing="0" cellpadding="3">
401f7940
MG
383<tr><td class="_podblock_"$tdstyle valign="middle">
384<big><strong><span class="_podblock_">&nbsp;$Title</span></strong></big>
3ada32a3
MG
385</td></tr>
386</table>
387END_OF_BLOCK
388
a4fca471 389 # create own header/footer because of --header
3ada32a3
MG
390 $parser->html_header(<<"HTMLHEAD");
391<?xml version="1.0" ?>
392<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
393<html xmlns="http://www.w3.org/1999/xhtml">
394<head>
395<title>$Title</title>$csslink
396<meta http-equiv="content-type" content="text/html; charset=utf-8" />
397<link rev="made" href="mailto:$Config{perladmin}" />
398</head>
399
f360b455 400<body$bodyid$bodystyle>
3ada32a3
MG
401$block
402HTMLHEAD
59ecbafa 403
401f7940
MG
404 $parser->html_footer(<<"HTMLFOOT");
405$block
406</body>
407
408</html>
409HTMLFOOT
410
5c988a71 411 my $input;
59ecbafa 412 unless (@ARGV && $ARGV[0]) {
ed36117c
MG
413 if ($Podfile and $Podfile ne '-') {
414 $input = $Podfile;
415 } else {
e7721e60 416 $input = '-'; # XXX: make a test case for this
ed36117c 417 }
7b8d334a 418 } else {
ed36117c
MG
419 $Podfile = $ARGV[0];
420 $input = *ARGV;
54310121 421 }
59ecbafa 422
5c988a71
MG
423 warn "Converting input file $Podfile\n" if $Verbose;
424 $parser->parse_file($input);
425
953c3c52 426 # Write output to file
99499175
MG
427 $Htmlfile = "-" unless $Htmlfile; # stdout
428 my $fhout;
429 if($Htmlfile and $Htmlfile ne '-') {
430 open $fhout, ">", $Htmlfile
431 or die "$0: cannot open $Htmlfile file for output: $!\n";
432 } else {
433 open $fhout, ">-";
434 }
dad26a17 435 binmode $fhout, ":utf8";
99499175
MG
436 print $fhout $output;
437 close $fhout or die "Failed to close $Htmlfile: $!";
c960cc81 438 chmod 0644, $Htmlfile unless $Htmlfile eq '-';
54310121 439}
440
441##############################################################################
442
54310121 443sub usage {
444 my $podfile = shift;
445 warn "$0: $podfile: @_\n" if @_;
69348ecf 446 die <<END_OF_USAGE;
54310121 447Usage: $0 --help --htmlroot=<name> --infile=<name> --outfile=<name>
b09e89a9 448 --podpath=<name>:...:<name> --podroot=<name> --cachedir=<name>
4979c083 449 --recurse --verbose --index --norecurse --noindex
54310121 450
8ef5953c
MG
451 --[no]backlink - turn =head1 directives into links pointing to the top of
452 the page (off by default).
b09e89a9 453 --cachedir - directory for the directory cache files.
8ef5953c 454 --css - stylesheet URL
b09e89a9 455 --flush - flushes the directory cache.
8ef5953c
MG
456 --[no]header - produce block header/footer (default is no headers).
457 --help - prints this message.
458 --htmldir - directory for resulting HTML files.
459 --htmlroot - http-server base directory from which all relative paths
460 in podpath stem (default is /).
461 --[no]index - generate an index at the top of the resulting html
462 (default behaviour).
463 --infile - filename for the pod to convert (input taken from stdin
ed36117c 464 by default).
8ef5953c
MG
465 --outfile - filename for the resulting html file (output sent to
466 stdout by default).
467 --[no]poderrors - include a POD ERRORS section in the output if there were
468 any POD errors in the input (default behavior).
469 --podpath - colon-separated list of directories containing library
470 pods (empty by default).
471 --podroot - filesystem base directory from which all relative paths
472 in podpath stem (default is .).
473 --[no]quiet - suppress some benign warning messages (default is off).
474 --[no]recurse - recurse on those subdirectories listed in podpath
475 (default behaviour).
476 --title - title that will appear in resulting html file.
477 --[no]verbose - self-explanatory (off by default).
54310121 478
479END_OF_USAGE
480
69348ecf
MS
481}
482
54310121 483sub parse_command_line {
b09e89a9
MG
484 my ($opt_backlink,$opt_cachedir,$opt_css,$opt_flush,$opt_header,
485 $opt_help,$opt_htmldir,$opt_htmlroot,$opt_index,$opt_infile,
ed36117c 486 $opt_outfile,$opt_poderrors,$opt_podpath,$opt_podroot,
978af2c6 487 $opt_quiet,$opt_recurse,$opt_title,$opt_verbose,$opt_libpods);
0e4548d5 488
34db337b 489 unshift @ARGV, split ' ', $Config{pod2html} if $Config{pod2html};
54310121 490 my $result = GetOptions(
ed36117c 491 'backlink!' => \$opt_backlink,
b09e89a9 492 'cachedir=s' => \$opt_cachedir,
ed36117c 493 'css=s' => \$opt_css,
b09e89a9 494 'flush' => \$opt_flush,
ed36117c
MG
495 'help' => \$opt_help,
496 'header!' => \$opt_header,
497 'htmldir=s' => \$opt_htmldir,
498 'htmlroot=s' => \$opt_htmlroot,
499 'index!' => \$opt_index,
500 'infile=s' => \$opt_infile,
978af2c6 501 'libpods=s' => \$opt_libpods, # deprecated
ed36117c
MG
502 'outfile=s' => \$opt_outfile,
503 'poderrors!' => \$opt_poderrors,
504 'podpath=s' => \$opt_podpath,
505 'podroot=s' => \$opt_podroot,
506 'quiet!' => \$opt_quiet,
507 'recurse!' => \$opt_recurse,
508 'title=s' => \$opt_title,
509 'verbose!' => \$opt_verbose,
4979c083 510 );
54310121 511 usage("-", "invalid parameters") if not $result;
512
ed36117c
MG
513 usage("-") if defined $opt_help; # see if the user asked for help
514 $opt_help = ""; # just to make -w shut-up.
54310121 515
69348ecf 516 @Podpath = split(":", $opt_podpath) if defined $opt_podpath;
978af2c6 517 warn "--libpods is no longer supported" if defined $opt_libpods;
69348ecf 518
ac2b477c
RS
519 $Backlink = $opt_backlink if defined $opt_backlink;
520 $Cachedir = _unixify($opt_cachedir) if defined $opt_cachedir;
521 $Css = $opt_css if defined $opt_css;
522 $Header = $opt_header if defined $opt_header;
523 $Htmldir = _unixify($opt_htmldir) if defined $opt_htmldir;
524 $Htmlroot = _unixify($opt_htmlroot) if defined $opt_htmlroot;
525 $Doindex = $opt_index if defined $opt_index;
526 $Podfile = _unixify($opt_infile) if defined $opt_infile;
527 $Htmlfile = _unixify($opt_outfile) if defined $opt_outfile;
528 $Poderrors = $opt_poderrors if defined $opt_poderrors;
529 $Podroot = _unixify($opt_podroot) if defined $opt_podroot;
530 $Quiet = $opt_quiet if defined $opt_quiet;
531 $Recurse = $opt_recurse if defined $opt_recurse;
532 $Title = $opt_title if defined $opt_title;
533 $Verbose = $opt_verbose if defined $opt_verbose;
b09e89a9
MG
534
535 warn "Flushing directory caches\n"
536 if $opt_verbose && defined $opt_flush;
537 $Dircache = "$Cachedir/pod2htmd.tmp";
538 if (defined $opt_flush) {
539 1 while unlink($Dircache);
540 }
54310121 541}
542
b09e89a9
MG
543my $Saved_Cache_Key;
544
545sub get_cache {
546 my($dircache, $podpath, $podroot, $recurse) = @_;
547 my @cache_key_args = @_;
548
549 # A first-level cache:
550 # Don't bother reading the cache files if they still apply
551 # and haven't changed since we last read them.
552
553 my $this_cache_key = cache_key(@cache_key_args);
554 return 1 if $Saved_Cache_Key and $this_cache_key eq $Saved_Cache_Key;
555 $Saved_Cache_Key = $this_cache_key;
556
557 # load the cache of %Pages if possible. $tests will be
558 # non-zero if successful.
559 my $tests = 0;
560 if (-f $dircache) {
561 warn "scanning for directory cache\n" if $Verbose;
562 $tests = load_cache($dircache, $podpath, $podroot);
563 }
564
565 return $tests;
566}
567
568sub cache_key {
569 my($dircache, $podpath, $podroot, $recurse) = @_;
570 return join('!',$dircache,$recurse,@$podpath,$podroot,stat($dircache));
571}
572
573#
574# load_cache - tries to find if the cache stored in $dircache is a valid
575# cache of %Pages. if so, it loads them and returns a non-zero value.
576#
577sub load_cache {
578 my($dircache, $podpath, $podroot) = @_;
579 my $tests = 0;
580 local $_;
581
582 warn "scanning for directory cache\n" if $Verbose;
3fabeba8 583 open(my $cachefh, '<', $dircache) ||
b09e89a9
MG
584 die "$0: error opening $dircache for reading: $!\n";
585 $/ = "\n";
586
587 # is it the same podpath?
3fabeba8 588 $_ = <$cachefh>;
b09e89a9
MG
589 chomp($_);
590 $tests++ if (join(":", @$podpath) eq $_);
591
592 # is it the same podroot?
3fabeba8 593 $_ = <$cachefh>;
b09e89a9
MG
594 chomp($_);
595 $tests++ if ($podroot eq $_);
596
597 # load the cache if its good
598 if ($tests != 2) {
3fabeba8 599 close($cachefh);
b09e89a9
MG
600 return 0;
601 }
602
603 warn "loading directory cache\n" if $Verbose;
3fabeba8 604 while (<$cachefh>) {
b09e89a9
MG
605 /(.*?) (.*)$/;
606 $Pages{$1} = $2;
607 }
608
3fabeba8 609 close($cachefh);
b09e89a9
MG
610 return 1;
611}
612
613
54310121 614#
2a28b791
GS
615# html_escape: make text safe for HTML
616#
617sub html_escape {
618 my $rest = $_[0];
619 $rest =~ s/&/&amp;/g;
620 $rest =~ s/</&lt;/g;
621 $rest =~ s/>/&gt;/g;
622 $rest =~ s/"/&quot;/g;
5b25816d
RS
623 # &apos; is only in XHTML, not HTML4. Be conservative
624 #$rest =~ s/'/&apos;/g;
2a28b791 625 return $rest;
59ecbafa 626}
2a28b791 627
54310121 628#
629# htmlify - converts a pod section specification to a suitable section
2a28b791
GS
630# specification for HTML. Note that we keep spaces and special characters
631# except ", ? (Netscape problem) and the hyphen (writer's problem...).
54310121 632#
633sub htmlify {
2a28b791
GS
634 my( $heading) = @_;
635 $heading =~ s/(\s+)/ /g;
636 $heading =~ s/\s+\Z//;
637 $heading =~ s/\A\s+//;
638 # The hyphen is a disgrace to the English language.
bc75160c
JA
639 # $heading =~ s/[-"?]//g;
640 $heading =~ s/["?]//g;
2a28b791
GS
641 $heading = lc( $heading );
642 return $heading;
643}
54310121 644
2a28b791 645#
776d31fe 646# similar to htmlify, but turns non-alphanumerics into underscores
0d396dd4
SB
647#
648sub anchorify {
649 my ($anchor) = @_;
650 $anchor = htmlify($anchor);
776d31fe 651 $anchor =~ s/\W/_/g;
0d396dd4
SB
652 return $anchor;
653}
654
8e1ba33c
MG
655#
656# store POD files in %Pages
657#
99499175
MG
658sub _save_page {
659 my ($modspec, $modname) = @_;
660
042c2bc8 661 # Remove Podroot from path
1e33ffe4
CB
662 $modspec = $Podroot eq File::Spec->curdir
663 ? File::Spec->abs2rel($modspec)
664 : File::Spec->abs2rel($modspec,
665 File::Spec->canonpath($Podroot));
042c2bc8 666
7c41f1ea 667 # Convert path to unix style path
b069bc5e 668 $modspec = Pod::Html::_unixify($modspec);
7c41f1ea 669
042c2bc8 670 my ($file, $dir) = fileparse($modspec, qr/\.[^.]*/); # strip .ext
7c41f1ea 671 $Pages{$modname} = $dir.$file;
99499175
MG
672}
673
b069bc5e
RS
674sub _unixify {
675 my $full_path = shift;
676 return '' unless $full_path;
1e33ffe4 677 return $full_path if $full_path eq '/';
b069bc5e 678
1e33ffe4
CB
679 my ($vol, $dirs, $file) = File::Spec->splitpath($full_path);
680 my @dirs = $dirs eq File::Spec->curdir()
681 ? (File::Spec::Unix->curdir())
682 : File::Spec->splitdir($dirs);
683 if (defined($vol) && $vol) {
684 $vol =~ s/:$// if $^O eq 'VMS';
8376a7bf 685 $vol = uc $vol if $^O eq 'MSWin32';
1e33ffe4
CB
686
687 if( $dirs[0] ) {
688 unshift @dirs, $vol;
689 }
690 else {
691 $dirs[0] = $vol;
692 }
693 }
694 unshift @dirs, '' if File::Spec->file_name_is_absolute($full_path);
695 return $file unless scalar(@dirs);
696 $full_path = File::Spec::Unix->catfile(File::Spec::Unix->catdir(@dirs),
697 $file);
698 $full_path =~ s|^\/|| if $^O eq 'MSWin32'; # C:/foo works, /C:/foo doesn't
699 return $full_path;
b069bc5e 700}
8e1ba33c 701
1be43d30
MG
702package Pod::Simple::XHTML::LocalPodLinks;
703use strict;
704use warnings;
705use base 'Pod::Simple::XHTML';
706
707use File::Spec;
7c41f1ea 708use File::Spec::Unix;
1be43d30
MG
709
710__PACKAGE__->_accessorize(
711 'htmldir',
712 'htmlfileurl',
713 'htmlroot',
714 'pages', # Page name => relative/path/to/page from root POD dir
715 'quiet',
716 'verbose',
717);
718
719sub resolve_pod_page_link {
720 my ($self, $to, $section) = @_;
721
722 return undef unless defined $to || defined $section;
723 if (defined $section) {
724 $section = '#' . $self->idify($section, 1);
725 return $section unless defined $to;
726 } else {
727 $section = '';
728 }
729
4979c083
MG
730 my $path; # path to $to according to %Pages
731 unless (exists $self->pages->{$to}) {
ed36117c
MG
732 # Try to find a POD that ends with $to and use that.
733 # e.g., given L<XHTML>, if there is no $Podpath/XHTML in %Pages,
734 # look for $Podpath/*/XHTML in %Pages, with * being any path,
735 # as a substitute (e.g., $Podpath/Pod/Simple/XHTML)
736 my @matches;
737 foreach my $modname (keys %{$self->pages}) {
632c5d30 738 push @matches, $modname if $modname =~ /::\Q$to\E\z/;
ed36117c
MG
739 }
740
741 if ($#matches == -1) {
742 warn "Cannot find \"$to\" in podpath: " .
743 "cannot find suitable replacement path, cannot resolve link\n"
744 unless $self->quiet;
745 return '';
746 } elsif ($#matches == 0) {
747 warn "Cannot find \"$to\" in podpath: " .
748 "using $matches[0] as replacement path to $to\n"
749 unless $self->quiet;
750 $path = $self->pages->{$matches[0]};
751 } else {
752 warn "Cannot find \"$to\" in podpath: " .
753 "more than one possible replacement path to $to, " .
754 "using $matches[-1]\n" unless $self->quiet;
e7721e60 755 # Use [-1] so newer (higher numbered) perl PODs are used
ed36117c
MG
756 $path = $self->pages->{$matches[-1]};
757 }
4979c083 758 } else {
ed36117c 759 $path = $self->pages->{$to};
1be43d30
MG
760 }
761
707a94fe 762 my $url = File::Spec::Unix->catfile(Pod::Html::_unixify($self->htmlroot),
7c41f1ea 763 $path);
707a94fe 764
1be43d30 765 if ($self->htmlfileurl ne '') {
ed36117c
MG
766 # then $self->htmlroot eq '' (by definition of htmlfileurl) so
767 # $self->htmldir needs to be prepended to link to get the absolute path
768 # that will be relativized
7c41f1ea 769 $url = relativize_url(
707a94fe 770 File::Spec::Unix->catdir(Pod::Html::_unixify($self->htmldir), $url),
7c41f1ea
MG
771 $self->htmlfileurl # already unixified
772 );
4979c083
MG
773 }
774
1be43d30
MG
775 return $url . ".html$section";
776}
777
778#
779# relativize_url - convert an absolute URL to one relative to a base URL.
780# Assumes both end in a filename.
781#
782sub relativize_url {
783 my ($dest, $source) = @_;
784
785 # Remove each file from its path
786 my ($dest_volume, $dest_directory, $dest_file) =
ed36117c 787 File::Spec::Unix->splitpath( $dest );
1be43d30
MG
788 $dest = File::Spec::Unix->catpath( $dest_volume, $dest_directory, '' );
789
790 my ($source_volume, $source_directory, $source_file) =
791 File::Spec::Unix->splitpath( $source );
792 $source = File::Spec::Unix->catpath( $source_volume, $source_directory, '' );
793
794 my $rel_path = '';
795 if ($dest ne '') {
796 $rel_path = File::Spec::Unix->abs2rel( $dest, $source );
797 }
798
799 if ($rel_path ne '' && substr( $rel_path, -1 ) ne '/') {
800 $rel_path .= "/$dest_file";
801 } else {
802 $rel_path .= "$dest_file";
803 }
804
805 return $rel_path;
806}
807
8081;