00b7e8993e3fb2d703cbfe9b5a5b3becdc62a8d8
[p5sagit/p5-mst-13.2.git] / lib / Pod / Html.pm
1 package Pod::Html;
2 use strict;
3 require Exporter;
4
5 use vars qw($VERSION @ISA @EXPORT);
6 $VERSION = 1.03;
7 @ISA = qw(Exporter);
8 @EXPORT = qw(pod2html htmlify);
9
10 use Carp;
11 use Config;
12 use Cwd;
13 use File::Spec::Unix;
14 use Getopt::Long;
15 use Pod::Functions;
16
17 use locale;     # make \w work right in non-ASCII lands
18
19 =head1 NAME
20
21 Pod::Html - module to convert pod files to HTML
22
23 =head1 SYNOPSIS
24
25     use Pod::Html;
26     pod2html([options]);
27
28 =head1 DESCRIPTION
29
30 Converts files from pod format (see L<perlpod>) to HTML format.  It
31 can automatically generate indexes and cross-references, and it keeps
32 a cache of things it knows how to cross-reference.
33
34 =head1 ARGUMENTS
35
36 Pod::Html takes the following arguments:
37
38 =over 4
39
40 =item backlink
41
42     --backlink="Back to Top"
43
44 Adds "Back to Top" links in front of every HEAD1 heading (except for
45 the first).  By default, no backlink are being generated.
46
47 =item css
48
49     --css=stylesheet
50
51 Specify the URL of a cascading style sheet.
52
53 =item flush
54
55     --flush
56
57 Flushes the item and directory caches.
58
59 =item header
60
61     --header
62     --noheader
63
64 Creates header and footer blocks containing the text of the NAME
65 section.  By default, no headers are being generated.
66
67 =item help
68
69     --help
70
71 Displays the usage message.
72
73 =item htmldir
74
75     --htmldir=name
76
77 Sets the directory in which the resulting HTML file is placed.  This
78 is used to generate relative links to other files. Not passing this
79 causes all links to be absolute, since this is the value that tells
80 Pod::Html the root of the documentation tree.
81
82 =item htmlroot
83
84     --htmlroot=name
85
86 Sets the base URL for the HTML files.  When cross-references are made,
87 the HTML root is prepended to the URL.
88
89 =item index
90
91     --index
92     --noindex
93
94 Generate an index at the top of the HTML file.  This is the default
95 behaviour.
96
97 =item infile
98
99     --infile=name
100
101 Specify the pod file to convert.  Input is taken from STDIN if no
102 infile is specified.
103
104 =item libpods
105
106     --libpods=name:...:name
107
108 List of page names (eg, "perlfunc") which contain linkable C<=item>s.
109
110 =item netscape
111
112     --netscape
113     --nonetscape
114
115 Use Netscape HTML directives when applicable.  By default, they will
116 B<not> be used.
117
118 =item outfile
119
120     --outfile=name
121
122 Specify the HTML file to create.  Output goes to STDOUT if no outfile
123 is specified.
124
125 =item podpath
126
127     --podpath=name:...:name
128
129 Specify which subdirectories of the podroot contain pod files whose
130 HTML converted forms can be linked-to in cross-references.
131
132 =item podroot
133
134     --podroot=name
135
136 Specify the base directory for finding library pods.
137
138 =item quiet
139
140     --quiet
141     --noquiet
142
143 Don't display I<mostly harmless> warning messages.  These messages
144 will be displayed by default.  But this is not the same as C<verbose>
145 mode.
146
147 =item recurse
148
149     --recurse
150     --norecurse
151
152 Recurse into subdirectories specified in podpath (default behaviour).
153
154 =item title
155
156     --title=title
157
158 Specify the title of the resulting HTML file.
159
160 =item verbose
161
162     --verbose
163     --noverbose
164
165 Display progress messages.  By default, they won't be displayed.
166
167 =back
168
169 =head1 EXAMPLE
170
171     pod2html("pod2html",
172              "--podpath=lib:ext:pod:vms", 
173              "--podroot=/usr/src/perl",
174              "--htmlroot=/perl/nmanual",
175              "--libpods=perlfunc:perlguts:perlvar:perlrun:perlop",
176              "--recurse",
177              "--infile=foo.pod",
178              "--outfile=/perl/nmanual/foo.html");
179
180 =head1 ENVIRONMENT
181
182 Uses $Config{pod2html} to setup default options.
183
184 =head1 AUTHOR
185
186 Tom Christiansen, E<lt>tchrist@perl.comE<gt>.
187
188 =head1 SEE ALSO
189
190 L<perlpod>
191
192 =head1 COPYRIGHT
193
194 This program is distributed under the Artistic License.
195
196 =cut
197
198 my $cache_ext = $^O eq 'VMS' ? ".tmp" : ".x~~";
199 my $dircache = "pod2htmd$cache_ext";
200 my $itemcache = "pod2htmi$cache_ext";
201
202 my @begin_stack = ();           # begin/end stack
203
204 my @libpods = ();               # files to search for links from C<> directives
205 my $htmlroot = "/";             # http-server base directory from which all
206                                 #   relative paths in $podpath stem.
207 my $htmldir = "";               # The directory to which the html pages
208                                 # will (eventually) be written.
209 my $htmlfile = "";              # write to stdout by default
210 my $htmlfileurl = "" ;          # The url that other files would use to
211                                 # refer to this file.  This is only used
212                                 # to make relative urls that point to
213                                 # other files.
214 my $podfile = "";               # read from stdin by default
215 my @podpath = ();               # list of directories containing library pods.
216 my $podroot = ".";              # filesystem base directory from which all
217                                 #   relative paths in $podpath stem.
218 my $css = '';                   # Cascading style sheet
219 my $recurse = 1;                # recurse on subdirectories in $podpath.
220 my $quiet = 0;                  # not quiet by default
221 my $verbose = 0;                # not verbose by default
222 my $doindex = 1;                # non-zero if we should generate an index
223 my $backlink = '';              # text for "back to top" links
224 my $listlevel = 0;              # current list depth
225 my @listend = ();               # the text to use to end the list.
226 my $after_lpar = 0;             # set to true after a par in an =item
227 my $ignore = 1;                 # whether or not to format text.  we don't
228                                 #   format text until we hit our first pod
229                                 #   directive.
230
231 my %items_named = ();           # for the multiples of the same item in perlfunc
232 my @items_seen = ();
233 my $netscape = 0;               # whether or not to use netscape directives.
234 my $title;                      # title to give the pod(s)
235 my $header = 0;                 # produce block header/footer
236 my $top = 1;                    # true if we are at the top of the doc.  used
237                                 #   to prevent the first <HR> directive.
238 my $paragraph;                  # which paragraph we're processing (used
239                                 #   for error messages)
240 my $ptQuote = 0;                # status of double-quote conversion
241 my %pages = ();                 # associative array used to find the location
242                                 #   of pages referenced by L<> links.
243 my %sections = ();              # sections within this page
244 my %items = ();                 # associative array used to find the location
245                                 #   of =item directives referenced by C<> links
246 my %local_items = ();           # local items - avoid destruction of %items
247 my $Is83;                       # is dos with short filenames (8.3)
248
249 sub init_globals {
250 $dircache = "pod2htmd$cache_ext";
251 $itemcache = "pod2htmi$cache_ext";
252
253 @begin_stack = ();              # begin/end stack
254
255 @libpods = ();          # files to search for links from C<> directives
256 $htmlroot = "/";                # http-server base directory from which all
257                                 #   relative paths in $podpath stem.
258 $htmldir = "";          # The directory to which the html pages
259                                 # will (eventually) be written.
260 $htmlfile = "";         # write to stdout by default
261 $podfile = "";          # read from stdin by default
262 @podpath = ();          # list of directories containing library pods.
263 $podroot = ".";         # filesystem base directory from which all
264                                 #   relative paths in $podpath stem.
265 $css = '';                   # Cascading style sheet
266 $recurse = 1;           # recurse on subdirectories in $podpath.
267 $quiet = 0;             # not quiet by default
268 $verbose = 0;           # not verbose by default
269 $doindex = 1;                   # non-zero if we should generate an index
270 $backlink = '';         # text for "back to top" links
271 $listlevel = 0;         # current list depth
272 @listend = ();          # the text to use to end the list.
273 $after_lpar = 0;        # set to true after a par in an =item
274 $ignore = 1;                    # whether or not to format text.  we don't
275                                 #   format text until we hit our first pod
276                                 #   directive.
277
278 @items_seen = ();
279 %items_named = ();
280 $netscape = 0;          # whether or not to use netscape directives.
281 $header = 0;                    # produce block header/footer
282 $title = '';                    # title to give the pod(s)
283 $top = 1;                       # true if we are at the top of the doc.  used
284                                 #   to prevent the first <HR> directive.
285 $paragraph = '';                        # which paragraph we're processing (used
286                                 #   for error messages)
287 %sections = ();         # sections within this page
288
289 # These are not reinitialised here but are kept as a cache.
290 # See get_cache and related cache management code.
291 #%pages = ();                   # associative array used to find the location
292                                 #   of pages referenced by L<> links.
293 #%items = ();                   # associative array used to find the location
294                                 #   of =item directives referenced by C<> links
295 %local_items = ();
296 $Is83=$^O eq 'dos';
297 }
298
299 #
300 # clean_data: global clean-up of pod data
301 #
302 sub clean_data($){
303     my( $dataref ) = @_;
304     my $i;
305     for( $i = 0; $i <= $#$dataref; $i++ ){
306         ${$dataref}[$i] =~ s/\s+\Z//;
307
308         # have a look for all-space lines
309         if( ${$dataref}[$i] =~ /^\s+$/m ){
310             my @chunks = split( /^\s+$/m, ${$dataref}[$i] );
311             splice( @$dataref, $i, 1, @chunks );
312         }
313     }
314 }
315
316
317 sub pod2html {
318     local(@ARGV) = @_;
319     local($/);
320     local $_;
321
322     init_globals();
323
324     $Is83 = 0 if (defined (&Dos::UseLFN) && Dos::UseLFN());
325
326     # cache of %pages and %items from last time we ran pod2html
327
328     #undef $opt_help if defined $opt_help;
329
330     # parse the command-line parameters
331     parse_command_line();
332
333     # set some variables to their default values if necessary
334     local *POD;
335     unless (@ARGV && $ARGV[0]) { 
336         $podfile  = "-" unless $podfile;        # stdin
337         open(POD, "<$podfile")
338                 || die "$0: cannot open $podfile file for input: $!\n";
339     } else {
340         $podfile = $ARGV[0];  # XXX: might be more filenames
341         *POD = *ARGV;
342     } 
343     $htmlfile = "-" unless $htmlfile;   # stdout
344     $htmlroot = "" if $htmlroot eq "/"; # so we don't get a //
345     $htmldir =~ s#/\z## ;               # so we don't get a //
346     if (  $htmlroot eq ''
347        && defined( $htmldir ) 
348        && $htmldir ne ''
349        && substr( $htmlfile, 0, length( $htmldir ) ) eq $htmldir 
350        ) 
351     {
352         # Set the 'base' url for this file, so that we can use it
353         # as the location from which to calculate relative links 
354         # to other files. If this is '', then absolute links will
355         # be used throughout.
356         $htmlfileurl= "$htmldir/" . substr( $htmlfile, length( $htmldir ) + 1);
357     }
358
359     # read the pod a paragraph at a time
360     warn "Scanning for sections in input file(s)\n" if $verbose;
361     $/ = "";
362     my @poddata  = <POD>;
363     close(POD);
364     clean_data( \@poddata );
365
366     # scan the pod for =head[1-6] directives and build an index
367     my $index = scan_headings(\%sections, @poddata);
368
369     unless($index) {
370         warn "No headings in $podfile\n" if $verbose;
371     }
372
373     # open the output file
374     open(HTML, ">$htmlfile")
375             || die "$0: cannot open $htmlfile file for output: $!\n";
376
377     # put a title in the HTML file if one wasn't specified
378     if ($title eq '') {
379         TITLE_SEARCH: {
380             for (my $i = 0; $i < @poddata; $i++) { 
381                 if ($poddata[$i] =~ /^=head1\s*NAME\b/m) {
382                     for my $para ( @poddata[$i, $i+1] ) { 
383                         last TITLE_SEARCH
384                             if ($title) = $para =~ /(\S+\s+-+.*\S)/s;
385                     }
386                 } 
387
388             } 
389         }
390     }
391     if (!$title and $podfile =~ /\.pod\z/) {
392         # probably a split pod so take first =head[12] as title
393         for (my $i = 0; $i < @poddata; $i++) { 
394             last if ($title) = $poddata[$i] =~ /^=head[12]\s*(.*)/;
395         } 
396         warn "adopted '$title' as title for $podfile\n"
397             if $verbose and $title;
398     } 
399     if ($title) {
400         $title =~ s/\s*\(.*\)//;
401     } else {
402         warn "$0: no title for $podfile" unless $quiet;
403         $podfile =~ /^(.*)(\.[^.\/]+)?\z/s;
404         $title = ($podfile eq "-" ? 'No Title' : $1);
405         warn "using $title" if $verbose;
406     }
407     my $csslink = $css ? qq(\n<LINK REL="stylesheet" HREF="$css" TYPE="text/css">) : '';
408     $csslink =~ s,\\,/,g;
409     $csslink =~ s,(/.):,$1|,;
410
411     my $block = $header ? <<END_OF_BLOCK : '';
412 <TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH=100%>
413 <TR><TD CLASS=block VALIGN=MIDDLE WIDTH=100% BGCOLOR="#cccccc">
414 <FONT SIZE=+1><STRONG><P CLASS=block>&nbsp;$title</P></STRONG></FONT>
415 </TD></TR>
416 </TABLE>
417 END_OF_BLOCK
418
419     print HTML <<END_OF_HEAD;
420 <HTML>
421 <HEAD>
422 <TITLE>$title</TITLE>$csslink
423 <LINK REV="made" HREF="mailto:$Config{perladmin}">
424 </HEAD>
425
426 <BODY>
427 $block
428 END_OF_HEAD
429
430     # load/reload/validate/cache %pages and %items
431     get_cache($dircache, $itemcache, \@podpath, $podroot, $recurse);
432
433     # scan the pod for =item directives
434     scan_items( \%local_items, "", @poddata);
435
436     # put an index at the top of the file.  note, if $doindex is 0 we
437     # still generate an index, but surround it with an html comment.
438     # that way some other program can extract it if desired.
439     $index =~ s/--+/-/g;
440     print HTML "<A NAME=\"__index__\"></A>\n";
441     print HTML "<!-- INDEX BEGIN -->\n";
442     print HTML "<!--\n" unless $doindex;
443     print HTML $index;
444     print HTML "-->\n" unless $doindex;
445     print HTML "<!-- INDEX END -->\n\n";
446     print HTML "<HR>\n" if $doindex and $index;
447
448     # now convert this file
449     my $after_item;             # set to true after an =item
450     warn "Converting input file $podfile\n" if $verbose;
451     foreach my $i (0..$#poddata){
452         $ptQuote = 0; # status of quote conversion
453
454         $_ = $poddata[$i];
455         $paragraph = $i+1;
456         if (/^(=.*)/s) {        # is it a pod directive?
457             $ignore = 0;
458             $after_item = 0;
459             $_ = $1;
460             if (/^=begin\s+(\S+)\s*(.*)/si) {# =begin
461                 process_begin($1, $2);
462             } elsif (/^=end\s+(\S+)\s*(.*)/si) {# =end
463                 process_end($1, $2);
464             } elsif (/^=cut/) {                 # =cut
465                 process_cut();
466             } elsif (/^=pod/) {                 # =pod
467                 process_pod();
468             } else {
469                 next if @begin_stack && $begin_stack[-1] ne 'html';
470
471                 if (/^=(head[1-6])\s+(.*\S)/s) {        # =head[1-6] heading
472                     process_head( $1, $2, $doindex && $index );
473                 } elsif (/^=item\s*(.*\S)?/sm) {        # =item text
474                     warn "$0: $podfile: =item without bullet, number or text"
475                        . " in paragraph $paragraph.\n" if !defined($1) or $1 eq '';
476                     process_item( $1 );
477                     $after_item = 1;
478                 } elsif (/^=over\s*(.*)/) {             # =over N
479                     process_over();
480                 } elsif (/^=back/) {            # =back
481                     process_back();
482                 } elsif (/^=for\s+(\S+)\s*(.*)/si) {# =for
483                     process_for($1,$2);
484                 } else {
485                     /^=(\S*)\s*/;
486                     warn "$0: $podfile: unknown pod directive '$1' in "
487                        . "paragraph $paragraph.  ignoring.\n";
488                 }
489             }
490             $top = 0;
491         }
492         else {
493             next if $ignore;
494             next if @begin_stack && $begin_stack[-1] ne 'html';
495             my $text = $_;
496             if( $text =~ /\A\s+/ ){
497                 process_pre( \$text );
498                 print HTML "<PRE>\n$text</PRE>\n";
499
500             } else {
501                 process_text( \$text );
502
503                 # experimental: check for a paragraph where all lines
504                 # have some ...\t...\t...\n pattern
505                 if( $text =~ /\t/ ){
506                     my @lines = split( "\n", $text );
507                     if( @lines > 1 ){
508                         my $all = 2;
509                         foreach my $line ( @lines ){
510                             if( $line =~ /\S/ && $line !~ /\t/ ){
511                                 $all--;
512                                 last if $all == 0;
513                             }
514                         }
515                         if( $all > 0 ){
516                             $text =~ s/\t+/<TD>/g;
517                             $text =~ s/^/<TR><TD>/gm;
518                             $text = '<TABLE CELLSPACING=0 CELLPADDING=0>' .
519                                     $text . '</TABLE>';
520                         }
521                     }
522                 }
523                 ## end of experimental
524
525                 if( $after_item ){
526                     print HTML "$text\n";
527                     $after_lpar = 1;
528                 } else {
529                     print HTML "<P>$text</P>\n";
530                 }
531             }
532             $after_item = 0;
533         }
534     }
535
536     # finish off any pending directives
537     finish_list();
538
539     # link to page index
540     print HTML "<P><A HREF=\"#__index__\"><SMALL>$backlink</SMALL></A></P>\n"
541         if $doindex and $index and $backlink;
542
543     print HTML <<END_OF_TAIL;
544 $block
545 </BODY>
546
547 </HTML>
548 END_OF_TAIL
549
550     # close the html file
551     close(HTML);
552
553     warn "Finished\n" if $verbose;
554 }
555
556 ##############################################################################
557
558 my $usage;                      # see below
559 sub usage {
560     my $podfile = shift;
561     warn "$0: $podfile: @_\n" if @_;
562     die $usage;
563 }
564
565 $usage =<<END_OF_USAGE;
566 Usage:  $0 --help --htmlroot=<name> --infile=<name> --outfile=<name>
567            --podpath=<name>:...:<name> --podroot=<name>
568            --libpods=<name>:...:<name> --recurse --verbose --index
569            --netscape --norecurse --noindex
570
571   --backlink     - set text for "back to top" links (default: none).
572   --css          - stylesheet URL
573   --flush        - flushes the item and directory caches.
574   --[no]header   - produce block header/footer (default is no headers).
575   --help         - prints this message.
576   --htmldir      - directory for resulting HTML files.
577   --htmlroot     - http-server base directory from which all relative paths
578                    in podpath stem (default is /).
579   --[no]index    - generate an index at the top of the resulting html
580                    (default behaviour).
581   --infile       - filename for the pod to convert (input taken from stdin
582                    by default).
583   --libpods      - colon-separated list of pages to search for =item pod
584                    directives in as targets of C<> and implicit links (empty
585                    by default).  note, these are not filenames, but rather
586                    page names like those that appear in L<> links.
587   --[no]netscape - will use netscape html directives when applicable.
588                    (default is not to use them).
589   --outfile      - filename for the resulting html file (output sent to
590                    stdout by default).
591   --podpath      - colon-separated list of directories containing library
592                    pods (empty by default).
593   --podroot      - filesystem base directory from which all relative paths
594                    in podpath stem (default is .).
595   --[no]quiet    - supress some benign warning messages (default is off).
596   --[no]recurse  - recurse on those subdirectories listed in podpath
597                    (default behaviour).
598   --title        - title that will appear in resulting html file.
599   --[no]verbose  - self-explanatory (off by default).
600
601 END_OF_USAGE
602
603 sub parse_command_line {
604     my ($opt_backlink,$opt_css,$opt_flush,$opt_header,$opt_help,$opt_htmldir,
605         $opt_htmlroot,$opt_index,$opt_infile,$opt_libpods,$opt_netscape,
606         $opt_outfile,$opt_podpath,$opt_podroot,$opt_quiet,$opt_recurse,
607         $opt_title,$opt_verbose);
608
609     unshift @ARGV, split ' ', $Config{pod2html} if $Config{pod2html};
610     my $result = GetOptions(
611                             'backlink=s' => \$opt_backlink,
612                             'css=s'      => \$opt_css,
613                             'flush'      => \$opt_flush,
614                             'header!'    => \$opt_header,
615                             'help'       => \$opt_help,
616                             'htmldir=s'  => \$opt_htmldir,
617                             'htmlroot=s' => \$opt_htmlroot,
618                             'index!'     => \$opt_index,
619                             'infile=s'   => \$opt_infile,
620                             'libpods=s'  => \$opt_libpods,
621                             'netscape!'  => \$opt_netscape,
622                             'outfile=s'  => \$opt_outfile,
623                             'podpath=s'  => \$opt_podpath,
624                             'podroot=s'  => \$opt_podroot,
625                             'quiet!'     => \$opt_quiet,
626                             'recurse!'   => \$opt_recurse,
627                             'title=s'    => \$opt_title,
628                             'verbose!'   => \$opt_verbose,
629                            );
630     usage("-", "invalid parameters") if not $result;
631
632     usage("-") if defined $opt_help;    # see if the user asked for help
633     $opt_help = "";                     # just to make -w shut-up.
634
635     @podpath  = split(":", $opt_podpath) if defined $opt_podpath;
636     @libpods  = split(":", $opt_libpods) if defined $opt_libpods;
637
638     $backlink = $opt_backlink if defined $opt_backlink;
639     $css      = $opt_css      if defined $opt_css;
640     $header   = $opt_header   if defined $opt_header;
641     $htmldir  = $opt_htmldir  if defined $opt_htmldir;
642     $htmlroot = $opt_htmlroot if defined $opt_htmlroot;
643     $doindex  = $opt_index    if defined $opt_index;
644     $podfile  = $opt_infile   if defined $opt_infile;
645     $netscape = $opt_netscape if defined $opt_netscape;
646     $htmlfile = $opt_outfile  if defined $opt_outfile;
647     $podroot  = $opt_podroot  if defined $opt_podroot;
648     $quiet    = $opt_quiet    if defined $opt_quiet;
649     $recurse  = $opt_recurse  if defined $opt_recurse;
650     $title    = $opt_title    if defined $opt_title;
651     $verbose  = $opt_verbose  if defined $opt_verbose;
652
653     warn "Flushing item and directory caches\n"
654         if $opt_verbose && defined $opt_flush;
655     unlink($dircache, $itemcache) if defined $opt_flush;
656 }
657
658
659 my $saved_cache_key;
660
661 sub get_cache {
662     my($dircache, $itemcache, $podpath, $podroot, $recurse) = @_;
663     my @cache_key_args = @_;
664
665     # A first-level cache:
666     # Don't bother reading the cache files if they still apply
667     # and haven't changed since we last read them.
668
669     my $this_cache_key = cache_key(@cache_key_args);
670
671     return if $saved_cache_key and $this_cache_key eq $saved_cache_key;
672
673     # load the cache of %pages and %items if possible.  $tests will be
674     # non-zero if successful.
675     my $tests = 0;
676     if (-f $dircache && -f $itemcache) {
677         warn "scanning for item cache\n" if $verbose;
678         $tests = load_cache($dircache, $itemcache, $podpath, $podroot);
679     }
680
681     # if we didn't succeed in loading the cache then we must (re)build
682     #  %pages and %items.
683     if (!$tests) {
684         warn "scanning directories in pod-path\n" if $verbose;
685         scan_podpath($podroot, $recurse, 0);
686     }
687     $saved_cache_key = cache_key(@cache_key_args);
688 }
689
690 sub cache_key {
691     my($dircache, $itemcache, $podpath, $podroot, $recurse) = @_;
692     return join('!', $dircache, $itemcache, $recurse,
693         @$podpath, $podroot, stat($dircache), stat($itemcache));
694 }
695
696 #
697 # load_cache - tries to find if the caches stored in $dircache and $itemcache
698 #  are valid caches of %pages and %items.  if they are valid then it loads
699 #  them and returns a non-zero value.
700 #
701 sub load_cache {
702     my($dircache, $itemcache, $podpath, $podroot) = @_;
703     my($tests);
704     local $_;
705
706     $tests = 0;
707
708     open(CACHE, "<$itemcache") ||
709         die "$0: error opening $itemcache for reading: $!\n";
710     $/ = "\n";
711
712     # is it the same podpath?
713     $_ = <CACHE>;
714     chomp($_);
715     $tests++ if (join(":", @$podpath) eq $_);
716
717     # is it the same podroot?
718     $_ = <CACHE>;
719     chomp($_);
720     $tests++ if ($podroot eq $_);
721
722     # load the cache if its good
723     if ($tests != 2) {
724         close(CACHE);
725         return 0;
726     }
727
728     warn "loading item cache\n" if $verbose;
729     while (<CACHE>) {
730         /(.*?) (.*)$/;
731         $items{$1} = $2;
732     }
733     close(CACHE);
734
735     warn "scanning for directory cache\n" if $verbose;
736     open(CACHE, "<$dircache") ||
737         die "$0: error opening $dircache for reading: $!\n";
738     $/ = "\n";
739     $tests = 0;
740
741     # is it the same podpath?
742     $_ = <CACHE>;
743     chomp($_);
744     $tests++ if (join(":", @$podpath) eq $_);
745
746     # is it the same podroot?
747     $_ = <CACHE>;
748     chomp($_);
749     $tests++ if ($podroot eq $_);
750
751     # load the cache if its good
752     if ($tests != 2) {
753         close(CACHE);
754         return 0;
755     }
756
757     warn "loading directory cache\n" if $verbose;
758     while (<CACHE>) {
759         /(.*?) (.*)$/;
760         $pages{$1} = $2;
761     }
762
763     close(CACHE);
764
765     return 1;
766 }
767
768 #
769 # scan_podpath - scans the directories specified in @podpath for directories,
770 #  .pod files, and .pm files.  it also scans the pod files specified in
771 #  @libpods for =item directives.
772 #
773 sub scan_podpath {
774     my($podroot, $recurse, $append) = @_;
775     my($pwd, $dir);
776     my($libpod, $dirname, $pod, @files, @poddata);
777
778     unless($append) {
779         %items = ();
780         %pages = ();
781     }
782
783     # scan each directory listed in @podpath
784     $pwd = getcwd();
785     chdir($podroot)
786         || die "$0: error changing to directory $podroot: $!\n";
787     foreach $dir (@podpath) {
788         scan_dir($dir, $recurse);
789     }
790
791     # scan the pods listed in @libpods for =item directives
792     foreach $libpod (@libpods) {
793         # if the page isn't defined then we won't know where to find it
794         # on the system.
795         next unless defined $pages{$libpod} && $pages{$libpod};
796
797         # if there is a directory then use the .pod and .pm files within it.
798         # NOTE: Only finds the first so-named directory in the tree.
799 #       if ($pages{$libpod} =~ /([^:]*[^(\.pod|\.pm)]):/) {
800         if ($pages{$libpod} =~ /([^:]*(?<!\.pod)(?<!\.pm)):/) {
801             #  find all the .pod and .pm files within the directory
802             $dirname = $1;
803             opendir(DIR, $dirname) ||
804                 die "$0: error opening directory $dirname: $!\n";
805             @files = grep(/(\.pod|\.pm)\z/ && ! -d $_, readdir(DIR));
806             closedir(DIR);
807
808             # scan each .pod and .pm file for =item directives
809             foreach $pod (@files) {
810                 open(POD, "<$dirname/$pod") ||
811                     die "$0: error opening $dirname/$pod for input: $!\n";
812                 @poddata = <POD>;
813                 close(POD);
814                 clean_data( \@poddata );
815
816                 scan_items( \%items, "$dirname/$pod", @poddata);
817             }
818
819             # use the names of files as =item directives too.
820 ### Don't think this should be done this way - confuses issues.(WL)
821 ###         foreach $pod (@files) {
822 ###             $pod =~ /^(.*)(\.pod|\.pm)$/;
823 ###             $items{$1} = "$dirname/$1.html" if $1;
824 ###         }
825         } elsif ($pages{$libpod} =~ /([^:]*\.pod):/ ||
826                  $pages{$libpod} =~ /([^:]*\.pm):/) {
827             # scan the .pod or .pm file for =item directives
828             $pod = $1;
829             open(POD, "<$pod") ||
830                 die "$0: error opening $pod for input: $!\n";
831             @poddata = <POD>;
832             close(POD);
833             clean_data( \@poddata );
834
835             scan_items( \%items, "$pod", @poddata);
836         } else {
837             warn "$0: shouldn't be here (line ".__LINE__."\n";
838         }
839     }
840     @poddata = ();      # clean-up a bit
841
842     chdir($pwd)
843         || die "$0: error changing to directory $pwd: $!\n";
844
845     # cache the item list for later use
846     warn "caching items for later use\n" if $verbose;
847     open(CACHE, ">$itemcache") ||
848         die "$0: error open $itemcache for writing: $!\n";
849
850     print CACHE join(":", @podpath) . "\n$podroot\n";
851     foreach my $key (keys %items) {
852         print CACHE "$key $items{$key}\n";
853     }
854
855     close(CACHE);
856
857     # cache the directory list for later use
858     warn "caching directories for later use\n" if $verbose;
859     open(CACHE, ">$dircache") ||
860         die "$0: error open $dircache for writing: $!\n";
861
862     print CACHE join(":", @podpath) . "\n$podroot\n";
863     foreach my $key (keys %pages) {
864         print CACHE "$key $pages{$key}\n";
865     }
866
867     close(CACHE);
868 }
869
870 #
871 # scan_dir - scans the directory specified in $dir for subdirectories, .pod
872 #  files, and .pm files.  notes those that it finds.  this information will
873 #  be used later in order to figure out where the pages specified in L<>
874 #  links are on the filesystem.
875 #
876 sub scan_dir {
877     my($dir, $recurse) = @_;
878     my($t, @subdirs, @pods, $pod, $dirname, @dirs);
879     local $_;
880
881     @subdirs = ();
882     @pods = ();
883
884     opendir(DIR, $dir) ||
885         die "$0: error opening directory $dir: $!\n";
886     while (defined($_ = readdir(DIR))) {
887         if (-d "$dir/$_" && $_ ne "." && $_ ne "..") {      # directory
888             $pages{$_}  = "" unless defined $pages{$_};
889             $pages{$_} .= "$dir/$_:";
890             push(@subdirs, $_);
891         } elsif (/\.pod\z/) {                               # .pod
892             s/\.pod\z//;
893             $pages{$_}  = "" unless defined $pages{$_};
894             $pages{$_} .= "$dir/$_.pod:";
895             push(@pods, "$dir/$_.pod");
896         } elsif (/\.pm\z/) {                                # .pm
897             s/\.pm\z//;
898             $pages{$_}  = "" unless defined $pages{$_};
899             $pages{$_} .= "$dir/$_.pm:";
900             push(@pods, "$dir/$_.pm");
901         }
902     }
903     closedir(DIR);
904
905     # recurse on the subdirectories if necessary
906     if ($recurse) {
907         foreach my $subdir (@subdirs) {
908             scan_dir("$dir/$subdir", $recurse);
909         }
910     }
911 }
912
913 #
914 # scan_headings - scan a pod file for head[1-6] tags, note the tags, and
915 #  build an index.
916 #
917 sub scan_headings {
918     my($sections, @data) = @_;
919     my($tag, $which_head, $otitle, $listdepth, $index);
920
921     # here we need      local $ignore = 0;
922     #  unfortunately, we can't have it, because $ignore is lexical
923     $ignore = 0;
924
925     $listdepth = 0;
926     $index = "";
927
928     # scan for =head directives, note their name, and build an index
929     #  pointing to each of them.
930     foreach my $line (@data) {
931         if ($line =~ /^=(head)([1-6])\s+(.*)/) {
932             ($tag, $which_head, $otitle) = ($1,$2,$3);
933
934             my $title = depod( $otitle );
935             my $name = htmlify( $title );
936             $$sections{$name} = 1;
937             $title = process_text( \$otitle );
938
939             while ($which_head != $listdepth) {
940                 if ($which_head > $listdepth) {
941                     $index .= "\n" . ("\t" x $listdepth) . "<UL>\n";
942                     $listdepth++;
943                 } elsif ($which_head < $listdepth) {
944                     $listdepth--;
945                     $index .= "\n" . ("\t" x $listdepth) . "</UL>\n";
946                 }
947             }
948
949             $index .= "\n" . ("\t" x $listdepth) . "<LI>" .
950                       "<A HREF=\"#" . $name . "\">" .
951                       $title . "</A></LI>";
952         }
953     }
954
955     # finish off the lists
956     while ($listdepth--) {
957         $index .= "\n" . ("\t" x $listdepth) . "</UL>\n";
958     }
959
960     # get rid of bogus lists
961     $index =~ s,\t*<UL>\s*</UL>\n,,g;
962
963     $ignore = 1;        # restore old value;
964
965     return $index;
966 }
967
968 #
969 # scan_items - scans the pod specified by $pod for =item directives.  we
970 #  will use this information later on in resolving C<> links.
971 #
972 sub scan_items {
973     my( $itemref, $pod, @poddata ) = @_;
974     my($i, $item);
975     local $_;
976
977     $pod =~ s/\.pod\z//;
978     $pod .= ".html" if $pod;
979
980     foreach $i (0..$#poddata) {
981         my $txt = depod( $poddata[$i] );
982
983         # figure out what kind of item it is.
984         # Build string for referencing this item.
985         if ( $txt =~ /\A=item\s+\*\s*(.*)\Z/s ) { # bullet
986             next unless $1;
987             $item = $1;
988         } elsif( $txt =~ /\A=item\s+(?>\d+\.?)\s*(.*)\Z/s ) { # numbered list
989             $item = $1;
990         } elsif( $txt =~ /\A=item\s+(.*)\Z/s ) { # plain item
991             $item = $1;
992         } else {
993             next;
994         }
995         my $fid = fragment_id( $item );
996         $$itemref{$fid} = "$pod" if $fid;
997     }
998 }
999
1000 #
1001 # process_head - convert a pod head[1-6] tag and convert it to HTML format.
1002 #
1003 sub process_head {
1004     my($tag, $heading, $hasindex) = @_;
1005
1006     # figure out the level of the =head
1007     $tag =~ /head([1-6])/;
1008     my $level = $1;
1009
1010     if( $listlevel ){
1011         warn "$0: $podfile: unterminated list at =head in paragraph $paragraph.  ignoring.\n";
1012         while( $listlevel ){
1013             process_back();
1014         }
1015     }
1016
1017     print HTML "<P>\n";
1018     if( $level == 1 && ! $top ){
1019         print HTML "<A HREF=\"#__index__\"><SMALL>$backlink</SMALL></A>\n"
1020             if $hasindex and $backlink;
1021         print HTML "<HR>\n"
1022     }
1023
1024     my $name = htmlify( depod( $heading ) );
1025     my $convert = process_text( \$heading );
1026     print HTML "<H$level><A NAME=\"$name\">$convert</A></H$level>\n";
1027 }
1028
1029
1030 #
1031 # emit_item_tag - print an =item's text
1032 # Note: The global $EmittedItem is used for inhibiting self-references.
1033 #
1034 my $EmittedItem;
1035
1036 sub emit_item_tag($$$){
1037     my( $otext, $text, $compact ) = @_;
1038     my $item = fragment_id( $text );
1039
1040     $EmittedItem = $item;
1041     ### print STDERR "emit_item_tag=$item ($text)\n";
1042
1043     print HTML '<STRONG>';
1044     if ($items_named{$item}++) {
1045         print HTML process_text( \$otext );
1046     } else {
1047         my $name = 'item_' . $item;
1048         print HTML qq{<A NAME="$name">}, process_text( \$otext ), '</A>';
1049     }
1050     print HTML "</STRONG><BR>\n";
1051     undef( $EmittedItem );
1052 }
1053
1054 sub emit_li {
1055     my( $tag ) = @_;
1056     if( $items_seen[$listlevel]++ == 0 ){
1057         push( @listend, "</$tag>" );
1058         print HTML "<$tag>\n";
1059     }
1060     print HTML $tag eq 'DL' ? '<DT>' : '<LI>';
1061 }
1062
1063 #
1064 # process_item - convert a pod item tag and convert it to HTML format.
1065 #
1066 sub process_item {
1067     my( $otext ) = @_;
1068
1069     # lots of documents start a list without doing an =over.  this is
1070     # bad!  but, the proper thing to do seems to be to just assume
1071     # they did do an =over.  so warn them once and then continue.
1072     if( $listlevel == 0 ){
1073         warn "$0: $podfile: unexpected =item directive in paragraph $paragraph.  ignoring.\n";
1074         process_over();
1075     }
1076
1077     # formatting: insert a paragraph if preceding item has >1 paragraph
1078     if( $after_lpar ){
1079         print HTML "<P></P>\n";
1080         $after_lpar = 0;
1081     }
1082
1083     # remove formatting instructions from the text
1084     my $text = depod( $otext );
1085
1086     # all the list variants:
1087     if( $text =~ /\A\*/ ){ # bullet
1088         emit_li( 'UL' );
1089         if ($text =~ /\A\*\s+(.+)\Z/s ) { # with additional text
1090             my $tag = $1;
1091             $otext =~ s/\A\*\s+//;
1092             emit_item_tag( $otext, $tag, 1 );
1093         }
1094
1095     } elsif( $text =~ /\A\d+/ ){ # numbered list
1096         emit_li( 'OL' );
1097         if ($text =~ /\A(?>\d+\.?)\s*(.+)\Z/s ) { # with additional text
1098             my $tag = $1;
1099             $otext =~ s/\A\d+\.?\s*//;
1100             emit_item_tag( $otext, $tag, 1 );
1101         }
1102
1103     } else {                    # definition list
1104         emit_li( 'DL' );
1105         if ($text =~ /\A(.+)\Z/s ){ # should have text
1106             emit_item_tag( $otext, $text, 1 );
1107         }
1108        print HTML '<DD>';
1109     }
1110     print HTML "\n";
1111 }
1112
1113 #
1114 # process_over - process a pod over tag and start a corresponding HTML list.
1115 #
1116 sub process_over {
1117     # start a new list
1118     $listlevel++;
1119     push( @items_seen, 0 );
1120     $after_lpar = 0;
1121 }
1122
1123 #
1124 # process_back - process a pod back tag and convert it to HTML format.
1125 #
1126 sub process_back {
1127     if( $listlevel == 0 ){
1128         warn "$0: $podfile: unexpected =back directive in paragraph $paragraph.  ignoring.\n";
1129         return;
1130     }
1131
1132     # close off the list.  note, I check to see if $listend[$listlevel] is
1133     # defined because an =item directive may have never appeared and thus
1134     # $listend[$listlevel] may have never been initialized.
1135     $listlevel--;
1136     if( defined $listend[$listlevel] ){
1137         print HTML '<P></P>' if $after_lpar;
1138         print HTML $listend[$listlevel];
1139         print HTML "\n";
1140         pop( @listend );
1141     }
1142     $after_lpar = 0;
1143
1144     # clean up item count
1145     pop( @items_seen );
1146 }
1147
1148 #
1149 # process_cut - process a pod cut tag, thus start ignoring pod directives.
1150 #
1151 sub process_cut {
1152     $ignore = 1;
1153 }
1154
1155 #
1156 # process_pod - process a pod pod tag, thus stop ignoring pod directives
1157 # until we see a corresponding cut.
1158 #
1159 sub process_pod {
1160     # no need to set $ignore to 0 cause the main loop did it
1161 }
1162
1163 #
1164 # process_for - process a =for pod tag.  if it's for html, spit
1165 # it out verbatim, if illustration, center it, otherwise ignore it.
1166 #
1167 sub process_for {
1168     my($whom, $text) = @_;
1169     if ( $whom =~ /^(pod2)?html$/i) {
1170         print HTML $text;
1171     } elsif ($whom =~ /^illustration$/i) {
1172         1 while chomp $text;
1173         for my $ext (qw[.png .gif .jpeg .jpg .tga .pcl .bmp]) {
1174           $text .= $ext, last if -r "$text$ext";
1175         }
1176         print HTML qq{<p align = "center"><img src = "$text" alt = "$text illustration"></p>};
1177     }
1178 }
1179
1180 #
1181 # process_begin - process a =begin pod tag.  this pushes
1182 # whom we're beginning on the begin stack.  if there's a
1183 # begin stack, we only print if it us.
1184 #
1185 sub process_begin {
1186     my($whom, $text) = @_;
1187     $whom = lc($whom);
1188     push (@begin_stack, $whom);
1189     if ( $whom =~ /^(pod2)?html$/) {
1190         print HTML $text if $text;
1191     }
1192 }
1193
1194 #
1195 # process_end - process a =end pod tag.  pop the
1196 # begin stack.  die if we're mismatched.
1197 #
1198 sub process_end {
1199     my($whom, $text) = @_;
1200     $whom = lc($whom);
1201     if ($begin_stack[-1] ne $whom ) {
1202         die "Unmatched begin/end at chunk $paragraph\n"
1203     } 
1204     pop( @begin_stack );
1205 }
1206
1207 #
1208 # process_pre - indented paragraph, made into <PRE></PRE>
1209 #
1210 sub process_pre {
1211     my( $text ) = @_;
1212     my( $rest );
1213     return if $ignore;
1214
1215     $rest = $$text;
1216
1217     # insert spaces in place of tabs
1218     $rest =~ s#.*#
1219             my $line = $&;
1220             1 while $line =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
1221             $line;
1222         #eg;
1223
1224     # convert some special chars to HTML escapes
1225     $rest =~ s/&/&amp;/g;
1226     $rest =~ s/</&lt;/g;
1227     $rest =~ s/>/&gt;/g;
1228     $rest =~ s/"/&quot;/g;
1229
1230     # try and create links for all occurrences of perl.* within
1231     # the preformatted text.
1232     $rest =~ s{
1233                  (\s*)(perl\w+)
1234               }{
1235                  if ( defined $pages{$2} ){     # is a link
1236                      qq($1<A HREF="$htmlroot/$pages{$2}">$2</A>);
1237                  } elsif (defined $pages{dosify($2)}) { # is a link
1238                      qq($1<A HREF="$htmlroot/$pages{dosify($2)}">$2</A>);
1239                  } else {
1240                      "$1$2";
1241                  }
1242               }xeg;
1243      $rest =~ s{
1244                  (<A\ HREF="?) ([^>:]*:)? ([^>:]*) \.pod: ([^>:]*:)?
1245                }{
1246                   my $url ;
1247                   if ( $htmlfileurl ne '' ){
1248                      # Here, we take advantage of the knowledge 
1249                      # that $htmlfileurl ne '' implies $htmlroot eq ''.
1250                      # Since $htmlroot eq '', we need to prepend $htmldir
1251                      # on the fron of the link to get the absolute path
1252                      # of the link's target. We check for a leading '/'
1253                      # to avoid corrupting links that are #, file:, etc.
1254                      my $old_url = $3 ;
1255                      $old_url = "$htmldir$old_url" if $old_url =~ m{^\/};
1256                      $url = relativize_url( "$old_url.html", $htmlfileurl );
1257                   } else {
1258                      $url = "$3.html" ;
1259                   }
1260                   "$1$url" ;
1261                }xeg;
1262
1263     # Look for embedded URLs and make them into links.  We don't
1264     # relativize them since they are best left as the author intended.
1265
1266     my $urls = '(' . join ('|', qw{
1267                 http
1268                 telnet
1269                 mailto
1270                 news
1271                 gopher
1272                 file
1273                 wais
1274                 ftp
1275             } ) 
1276         . ')';
1277   
1278     my $ltrs = '\w';
1279     my $gunk = '/#~:.?+=&%@!\-';
1280     my $punc = '.:?\-';
1281     my $any  = "${ltrs}${gunk}${punc}";
1282
1283     $rest =~ s{
1284         \b                          # start at word boundary
1285         (                           # begin $1  {
1286           $urls     :               # need resource and a colon
1287           (?!:)                     # Ignore File::, among others.
1288           [$any] +?                 # followed by on or more
1289                                     #  of any valid character, but
1290                                     #  be conservative and take only
1291                                     #  what you need to....
1292         )                           # end   $1  }
1293         (?=                         # look-ahead non-consumptive assertion
1294                 [$punc]*            # either 0 or more puntuation
1295                 [^$any]             #   followed by a non-url char
1296             |                       # or else
1297                 $                   #   then end of the string
1298         )
1299       }{<A HREF="$1">$1</A>}igox;
1300
1301     # text should be as it is (verbatim)
1302     $$text = $rest;
1303 }
1304
1305
1306 #
1307 # pure text processing
1308 #
1309 # pure_text/inIS_text: differ with respect to automatic C<> recognition.
1310 # we don't want this to happen within IS
1311 #
1312 sub pure_text($){
1313     my $text = shift();
1314     process_puretext( $text, \$ptQuote, 1 );
1315 }
1316
1317 sub inIS_text($){
1318     my $text = shift();
1319     process_puretext( $text, \$ptQuote, 0 );
1320 }
1321
1322 #
1323 # process_puretext - process pure text (without pod-escapes) converting
1324 #  double-quotes and handling implicit C<> links.
1325 #
1326 sub process_puretext {
1327     my($text, $quote, $notinIS) = @_;
1328
1329     ## Guessing at func() or [$@%&]*var references in plain text is destined
1330     ## to produce some strange looking ref's. uncomment to disable:
1331     ## $notinIS = 0;
1332
1333     my(@words, $lead, $trail);
1334
1335     # convert double-quotes to single-quotes
1336     if( $$quote && $text =~ s/"/''/s ){
1337         $$quote = 0;
1338     }
1339     while ($text =~ s/"([^"]*)"/``$1''/sg) {};
1340     $$quote = 1 if $text =~ s/"/``/s;
1341
1342     # keep track of leading and trailing white-space
1343     $lead  = ($text =~ s/\A(\s+)//s ? $1 : "");
1344     $trail = ($text =~ s/(\s+)\Z//s ? $1 : "");
1345
1346     # split at space/non-space boundaries
1347     @words = split( /(?<=\s)(?=\S)|(?<=\S)(?=\s)/, $text );
1348
1349     # process each word individually
1350     foreach my $word (@words) {
1351         # skip space runs
1352         next if $word =~ /^\s*$/;
1353         # see if we can infer a link
1354         if( $notinIS && $word =~ /^(\w+)\((.*)\)\W*$/ ) {
1355             # has parenthesis so should have been a C<> ref
1356             ## try for a pagename (perlXXX(1))?
1357             if( $2 =~ /^\d+$/ ){
1358                 my $url = page_sect( $word, '' );
1359                 if( defined $url ){
1360                     $word = "<A HREF=\"$url\">the $word manpage</A>";
1361                     next;
1362                 }
1363             }
1364             $word = emit_C( $word );
1365
1366 #### disabled. either all (including $\W, $\w+{.*} etc.) or nothing.
1367 ##      } elsif( $notinIS && $word =~ /^[\$\@%&*]+\w+$/) {
1368 ##          # perl variables, should be a C<> ref
1369 ##          $word = emit_C( $word );
1370
1371         } elsif ($word =~ m,^\w+://\w,) {
1372             # looks like a URL
1373             # Don't relativize it: leave it as the author intended
1374             $word = qq(<A HREF="$word">$word</A>);
1375         } elsif ($word =~ /[\w.-]+\@[\w-]+\.\w/) {
1376             # looks like an e-mail address
1377             my ($w1, $w2, $w3) = ("", $word, "");
1378             ($w1, $w2, $w3) = ("(", $1, ")$2") if $word =~ /^\((.*?)\)(,?)/;
1379             ($w1, $w2, $w3) = ("&lt;", $1, "&gt;$2") if $word =~ /^<(.*?)>(,?)/;
1380             $word = qq($w1<A HREF="mailto:$w2">$w2</A>$w3);
1381         } elsif ($word !~ /[a-z]/ && $word =~ /[A-Z]/) {  # all uppercase?
1382             $word = html_escape($word) if $word =~ /["&<>]/;
1383             $word = "\n<FONT SIZE=-1>$word</FONT>" if $netscape;
1384         } else { 
1385             $word = html_escape($word) if $word =~ /["&<>]/;
1386         }
1387     }
1388
1389     # put everything back together
1390     return $lead . join( '', @words ) . $trail;
1391 }
1392
1393
1394 #
1395 # process_text - handles plaintext that appears in the input pod file.
1396 # there may be pod commands embedded within the text so those must be
1397 # converted to html commands.
1398 #
1399
1400 sub process_text1($$;$$);
1401 sub pattern ($) { $_[0] ? '[^\S\n]+'.('>' x ($_[0] + 1)) : '>' }
1402 sub closing ($) { local($_) = shift; (defined && s/\s+$//) ? length : 0 }
1403
1404 sub process_text {
1405     return if $ignore;
1406     my( $tref ) = @_;
1407     my $res = process_text1( 0, $tref );
1408     $$tref = $res;
1409 }
1410
1411 sub process_text1($$;$$){
1412     my( $lev, $rstr, $func, $closing ) = @_;
1413     $lev++ unless defined $func;
1414     my $res = '';
1415
1416     $func ||= '';
1417     if( $func eq 'B' ){
1418         # B<text> - boldface
1419         $res = '<STRONG>' . process_text1( $lev, $rstr ) . '</STRONG>';
1420
1421     } elsif( $func eq 'C' ){
1422         # C<code> - can be a ref or <CODE></CODE>
1423         # need to extract text
1424         my $par = go_ahead( $rstr, 'C', $closing );
1425
1426         ## clean-up of the link target
1427         my $text = depod( $par );
1428
1429         ### my $x = $par =~ /[BI]</ ? 'yes' : 'no' ;
1430         ### print STDERR "-->call emit_C($par) lev=$lev, par with BI=$x\n"; 
1431
1432         $res = emit_C( $text, $lev > 1 || ($par =~ /[BI]</) );
1433
1434     } elsif( $func eq 'E' ){
1435         # E<x> - convert to character
1436         $$rstr =~ s/^(\w+)>//;
1437         $res = "&$1;";
1438
1439     } elsif( $func eq 'F' ){
1440         # F<filename> - italizice
1441         $res = '<EM>' . process_text1( $lev, $rstr ) . '</EM>';
1442
1443     } elsif( $func eq 'I' ){
1444         # I<text> - italizice
1445         $res = '<EM>' . process_text1( $lev, $rstr ) . '</EM>';
1446
1447     } elsif( $func eq 'L' ){
1448         # L<link> - link
1449         ## L<text|cross-ref> => produce text, use cross-ref for linking 
1450         ## L<cross-ref> => make text from cross-ref
1451         ## need to extract text
1452         my $par = go_ahead( $rstr, 'L', $closing );
1453
1454         # some L<>'s that shouldn't be:
1455         # a) full-blown URL's are emitted as-is
1456         if( $par =~ m{^\w+://}s ){
1457             return make_URL_href( $par );
1458         }
1459         # b) C<...> is stripped and treated as C<>
1460         if( $par =~ /^C<(.*)>$/ ){
1461             my $text = depod( $1 );
1462             return emit_C( $text, $lev > 1 || ($par =~ /[BI]</) );
1463         }
1464
1465         # analyze the contents
1466         $par =~ s/\n/ /g;   # undo word-wrapped tags
1467         my $opar = $par;
1468         my $linktext;
1469         if( $par =~ s{^([^|]+)\|}{} ){
1470             $linktext = $1;
1471         }
1472     
1473         # make sure sections start with a /
1474         $par =~ s{^"}{/"};
1475
1476         my( $page, $section, $ident );
1477
1478         # check for link patterns
1479         if( $par =~ m{^([^/]+?)/(?!")(.*?)$} ){     # name/ident
1480             # we've got a name/ident (no quotes) 
1481             ( $page, $ident ) = ( $1, $2 );
1482             ### print STDERR "--> L<$par> to page $page, ident $ident\n";
1483
1484         } elsif( $par =~ m{^(.*?)/"?(.*?)"?$} ){ # [name]/"section"
1485             # even though this should be a "section", we go for ident first
1486             ( $page, $ident ) = ( $1, $2 );
1487             ### print STDERR "--> L<$par> to page $page, section $section\n";
1488
1489         } elsif( $par =~ /\s/ ){  # this must be a section with missing quotes
1490             ( $page, $section ) = ( '', $par );
1491             ### print STDERR "--> L<$par> to void page, section $section\n";
1492
1493         } else {
1494             ( $page, $section ) = ( $par, '' );
1495             ### print STDERR "--> L<$par> to page $par, void section\n";
1496         }
1497
1498         # now, either $section or $ident is defined. the convoluted logic
1499         # below tries to resolve L<> according to what the user specified.
1500         # failing this, we try to find the next best thing...
1501         my( $url, $ltext, $fid );
1502
1503         RESOLVE: {
1504             if( defined $ident ){
1505                 ## try to resolve $ident as an item
1506                 ( $url, $fid ) = coderef( $page, $ident );
1507                 if( $url ){
1508                     if( ! defined( $linktext ) ){
1509                         $linktext = $ident;
1510                         $linktext .= " in " if $ident && $page;
1511                         $linktext .= "the $page manpage" if $page;
1512                     }
1513                     ###  print STDERR "got coderef url=$url\n";
1514                     last RESOLVE;
1515                 }
1516                 ## no luck: go for a section (auto-quoting!)
1517                 $section = $ident;
1518             }
1519             ## now go for a section
1520             my $htmlsection = htmlify( $section );
1521             $url = page_sect( $page, $htmlsection );
1522             if( $url ){
1523                 if( ! defined( $linktext ) ){
1524                     $linktext = $section;
1525                     $linktext .= " in " if $section && $page;
1526                     $linktext .= "the $page manpage" if $page;
1527                 }
1528                 ### print STDERR "got page/section url=$url\n";
1529                 last RESOLVE;
1530             }
1531             ## no luck: go for an ident 
1532             if( $section ){
1533                 $ident = $section;
1534             } else {
1535                 $ident = $page;
1536                 $page  = undef();
1537             }
1538             ( $url, $fid ) = coderef( $page, $ident );
1539             if( $url ){
1540                 if( ! defined( $linktext ) ){
1541                     $linktext = $ident;
1542                     $linktext .= " in " if $ident && $page;
1543                     $linktext .= "the $page manpage" if $page;
1544                 }
1545                 ### print STDERR "got section=>coderef url=$url\n";
1546                 last RESOLVE;
1547             }
1548
1549             # warning; show some text.
1550             $linktext = $opar unless defined $linktext;
1551             warn "$0: $podfile: cannot resolve L<$opar> in paragraph $paragraph.";
1552         }
1553
1554         # now we have an URL or just plain code
1555         $$rstr = $linktext . '>' . $$rstr;
1556         if( defined( $url ) ){
1557             $res = "<A HREF=\"$url\">" . process_text1( $lev, $rstr ) . '</A>';
1558         } else {
1559             $res = '<EM>' . process_text1( $lev, $rstr ) . '</EM>';
1560         }
1561
1562     } elsif( $func eq 'S' ){
1563         # S<text> - non-breaking spaces
1564         $res = process_text1( $lev, $rstr );
1565         $res =~ s/ /&nbsp;/g;
1566
1567     } elsif( $func eq 'X' ){
1568         # X<> - ignore
1569         $$rstr =~ s/^[^>]*>//;
1570
1571     } elsif( $func eq 'Z' ){
1572         # Z<> - empty 
1573         warn "$0: $podfile: invalid X<> in paragraph $paragraph."
1574             unless $$rstr =~ s/^>//;
1575
1576     } else {
1577         my $term = pattern $closing;
1578         while( $$rstr =~ s/\A(.*?)(([BCEFILSXZ])<(<+[^\S\n]+)?|$term)//s ){
1579             # all others: either recurse into new function or
1580             # terminate at closing angle bracket(s)
1581             my $pt = $1;
1582             $pt .= $2 if !$3 &&  $lev == 1;
1583             $res .= $lev == 1 ? pure_text( $pt ) : inIS_text( $pt );
1584             return $res if !$3 && $lev > 1;
1585             if( $3 ){
1586                 $res .= process_text1( $lev, $rstr, $3, closing $4 );
1587             }
1588
1589         }
1590         if( $lev == 1 ){
1591             $res .= pure_text( $$rstr );
1592         } else {
1593             warn "$0: $podfile: undelimited $func<> in paragraph $paragraph.";
1594         }
1595     }
1596     return $res;
1597 }
1598
1599 #
1600 # go_ahead: extract text of an IS (can be nested)
1601 #
1602 sub go_ahead($$$){
1603     my( $rstr, $func, $closing ) = @_;
1604     my $res = '';
1605     my @closing = ($closing);
1606     while( $$rstr =~
1607       s/\A(.*?)(([BCEFILSXZ])<(<+[^\S\n]+)?|@{[pattern $closing[0]]})//s ){
1608         $res .= $1;
1609         unless( $3 ){
1610             shift @closing;
1611             return $res unless @closing;
1612         } else {
1613             unshift @closing, closing $4;
1614         }
1615         $res .= $2;
1616     }
1617     warn "$0: $podfile: undelimited $func<> in paragraph $paragraph.";
1618     return $res;
1619 }
1620
1621 #
1622 # emit_C - output result of C<text>
1623 #    $text is the depod-ed text
1624 #
1625 sub emit_C($;$){
1626     my( $text, $nocode ) = @_;
1627     my $res;
1628     my( $url, $fid ) = coderef( undef(), $text );
1629
1630     # need HTML-safe text
1631     my $linktext = html_escape( $text );
1632
1633     if( defined( $url ) &&
1634         (!defined( $EmittedItem ) || $EmittedItem ne $fid ) ){
1635         $res = "<A HREF=\"$url\"><CODE>$linktext</CODE></A>";
1636     } elsif( 0 && $nocode ){
1637         $res = $linktext;
1638     } else {
1639         $res = "<CODE>$linktext</CODE>";
1640     }
1641     return $res;
1642 }
1643
1644 #
1645 # html_escape: make text safe for HTML
1646 #
1647 sub html_escape {
1648     my $rest = $_[0];
1649     $rest   =~ s/&/&amp;/g;
1650     $rest   =~ s/</&lt;/g;
1651     $rest   =~ s/>/&gt;/g;
1652     $rest   =~ s/"/&quot;/g;
1653     return $rest;
1654
1655
1656
1657 #
1658 # dosify - convert filenames to 8.3
1659 #
1660 sub dosify {
1661     my($str) = @_;
1662     return lc($str) if $^O eq 'VMS';     # VMS just needs casing
1663     if ($Is83) {
1664         $str = lc $str;
1665         $str =~ s/(\.\w+)/substr ($1,0,4)/ge;
1666         $str =~ s/(\w+)/substr ($1,0,8)/ge;
1667     }
1668     return $str;
1669 }
1670
1671 #
1672 # page_sect - make an URL from the text of a L<>
1673 #
1674 sub page_sect($$) {
1675     my( $page, $section ) = @_;
1676     my( $linktext, $page83, $link);     # work strings
1677
1678     # check if we know that this is a section in this page
1679     if (!defined $pages{$page} && defined $sections{$page}) {
1680         $section = $page;
1681         $page = "";
1682         ### print STDERR "reset page='', section=$section\n";
1683     }
1684
1685     $page83=dosify($page);
1686     $page=$page83 if (defined $pages{$page83});
1687     if ($page eq "") {
1688         $link = "#" . htmlify( $section );
1689     } elsif ( $page =~ /::/ ) {
1690         $page =~ s,::,/,g;
1691         # Search page cache for an entry keyed under the html page name,
1692         # then look to see what directory that page might be in.  NOTE:
1693         # this will only find one page. A better solution might be to produce
1694         # an intermediate page that is an index to all such pages.
1695         my $page_name = $page ;
1696         $page_name =~ s,^.*/,,s ;
1697         if ( defined( $pages{ $page_name } ) && 
1698              $pages{ $page_name } =~ /([^:]*$page)\.(?:pod|pm):/ 
1699            ) {
1700             $page = $1 ;
1701         }
1702         else {
1703             # NOTE: This branch assumes that all A::B pages are located in
1704             # $htmlroot/A/B.html . This is often incorrect, since they are
1705             # often in $htmlroot/lib/A/B.html or such like. Perhaps we could
1706             # analyze the contents of %pages and figure out where any
1707             # cousins of A::B are, then assume that.  So, if A::B isn't found,
1708             # but A::C is found in lib/A/C.pm, then A::B is assumed to be in
1709             # lib/A/B.pm. This is also limited, but it's an improvement.
1710             # Maybe a hints file so that the links point to the correct places
1711             # nonetheless?
1712
1713         }
1714         $link = "$htmlroot/$page.html";
1715         $link .= "#" . htmlify( $section ) if ($section);
1716     } elsif (!defined $pages{$page}) {
1717         $link = "";
1718     } else {
1719         $section = htmlify( $section ) if $section ne "";
1720         ### print STDERR "...section=$section\n";
1721
1722         # if there is a directory by the name of the page, then assume that an
1723         # appropriate section will exist in the subdirectory
1724 #       if ($section ne "" && $pages{$page} =~ /([^:]*[^(\.pod|\.pm)]):/) {
1725         if ($section ne "" && $pages{$page} =~ /([^:]*(?<!\.pod)(?<!\.pm)):/) {
1726             $link = "$htmlroot/$1/$section.html";
1727             ### print STDERR "...link=$link\n";
1728
1729         # since there is no directory by the name of the page, the section will
1730         # have to exist within a .html of the same name.  thus, make sure there
1731         # is a .pod or .pm that might become that .html
1732         } else {
1733             $section = "#$section" if $section;
1734             ### print STDERR "...section=$section\n";
1735
1736             # check if there is a .pod with the page name
1737             if ($pages{$page} =~ /([^:]*)\.pod:/) {
1738                 $link = "$htmlroot/$1.html$section";
1739             } elsif ($pages{$page} =~ /([^:]*)\.pm:/) {
1740                 $link = "$htmlroot/$1.html$section";
1741             } else {
1742                 $link = "";
1743             }
1744         }
1745     }
1746
1747     if ($link) {
1748         # Here, we take advantage of the knowledge that $htmlfileurl ne ''
1749         # implies $htmlroot eq ''. This means that the link in question
1750         # needs a prefix of $htmldir if it begins with '/'. The test for
1751         # the initial '/' is done to avoid '#'-only links, and to allow
1752         # for other kinds of links, like file:, ftp:, etc.
1753         my $url ;
1754         if (  $htmlfileurl ne '' ) {
1755             $link = "$htmldir$link" if $link =~ m{^/}s;
1756             $url = relativize_url( $link, $htmlfileurl );
1757 # print( "  b: [$link,$htmlfileurl,$url]\n" );
1758         }
1759         else {
1760             $url = $link ;
1761         }
1762         return $url;
1763
1764     } else {
1765         return undef();
1766     }
1767 }
1768
1769 #
1770 # relativize_url - convert an absolute URL to one relative to a base URL.
1771 # Assumes both end in a filename.
1772 #
1773 sub relativize_url {
1774     my ($dest,$source) = @_ ;
1775
1776     my ($dest_volume,$dest_directory,$dest_file) = 
1777         File::Spec::Unix->splitpath( $dest ) ;
1778     $dest = File::Spec::Unix->catpath( $dest_volume, $dest_directory, '' ) ;
1779
1780     my ($source_volume,$source_directory,$source_file) = 
1781         File::Spec::Unix->splitpath( $source ) ;
1782     $source = File::Spec::Unix->catpath( $source_volume, $source_directory, '' ) ;
1783
1784     my $rel_path = '' ;
1785     if ( $dest ne '' ) {
1786        $rel_path = File::Spec::Unix->abs2rel( $dest, $source ) ;
1787     }
1788
1789     if ( $rel_path ne ''                && 
1790          substr( $rel_path, -1 ) ne '/' &&
1791          substr( $dest_file, 0, 1 ) ne '#' 
1792         ) {
1793         $rel_path .= "/$dest_file" ;
1794     }
1795     else {
1796         $rel_path .= "$dest_file" ;
1797     }
1798
1799     return $rel_path ;
1800 }
1801
1802
1803 #
1804 # coderef - make URL from the text of a C<>
1805 #
1806 sub coderef($$){
1807     my( $page, $item ) = @_;
1808     my( $url );
1809
1810     my $fid = fragment_id( $item );
1811     if( defined( $page ) ){
1812         # we have been given a $page...
1813         $page =~ s{::}{/}g;
1814
1815         # Do we take it? Item could be a section!
1816         my $base = $items{$fid} || "";
1817         $base =~ s{[^/]*/}{};
1818         if( $base ne "$page.html" ){
1819             ###   print STDERR "coderef( $page, $item ): items{$fid} = $items{$fid} = $base => discard page!\n";
1820             $page = undef();
1821         }
1822
1823     } else {
1824         # no page - local items precede cached items
1825         if( defined( $fid ) ){
1826             if(  exists $local_items{$fid} ){
1827                 $page = $local_items{$fid};
1828             } else {
1829                 $page = $items{$fid};
1830             }
1831         }
1832     }
1833
1834     # if there was a pod file that we found earlier with an appropriate
1835     # =item directive, then create a link to that page.
1836     if( defined $page ){
1837         if( $page ){
1838             if( exists $pages{$page} and $pages{$page} =~ /([^:.]*)\.[^:]*:/){
1839                 $page = $1 . '.html';
1840             }
1841             my $link = "$htmlroot/$page#item_$fid";
1842
1843             # Here, we take advantage of the knowledge that $htmlfileurl
1844             # ne '' implies $htmlroot eq ''.
1845             if (  $htmlfileurl ne '' ) {
1846                 $link = "$htmldir$link" ;
1847                 $url = relativize_url( $link, $htmlfileurl ) ;
1848             } else {
1849                 $url = $link ;
1850             }
1851         } else {
1852             $url = "#item_" . $fid;
1853         }
1854
1855         confess "url has space: $url" if $url =~ /"[^"]*\s[^"]*"/;
1856     }       
1857     return( $url, $fid );
1858 }
1859
1860
1861
1862 #
1863 # Adapted from Nick Ing-Simmons' PodToHtml package.
1864 sub relative_url {
1865     my $source_file = shift ;
1866     my $destination_file = shift;
1867
1868     my $source = URI::file->new_abs($source_file);
1869     my $uo = URI::file->new($destination_file,$source)->abs;
1870     return $uo->rel->as_string;
1871 }
1872
1873
1874 #
1875 # finish_list - finish off any pending HTML lists.  this should be called
1876 # after the entire pod file has been read and converted.
1877 #
1878 sub finish_list {
1879     while ($listlevel > 0) {
1880         print HTML "</DL>\n";
1881         $listlevel--;
1882     }
1883 }
1884
1885 #
1886 # htmlify - converts a pod section specification to a suitable section
1887 # specification for HTML. Note that we keep spaces and special characters
1888 # except ", ? (Netscape problem) and the hyphen (writer's problem...).
1889 #
1890 sub htmlify {
1891     my( $heading) = @_;
1892     $heading =~ s/(\s+)/ /g;
1893     $heading =~ s/\s+\Z//;
1894     $heading =~ s/\A\s+//;
1895     # The hyphen is a disgrace to the English language.
1896     $heading =~ s/[-"?]//g;
1897     $heading = lc( $heading );
1898     return $heading;
1899 }
1900
1901 #
1902 # depod - convert text by eliminating all interior sequences
1903 # Note: can be called with copy or modify semantics
1904 #
1905 my %E2c;
1906 $E2c{lt}     = '<';
1907 $E2c{gt}     = '>';
1908 $E2c{sol}    = '/';
1909 $E2c{verbar} = '|';
1910 $E2c{amp}    = '&'; # in Tk's pods
1911
1912 sub depod1($;$$);
1913
1914 sub depod($){
1915     my $string;
1916     if( ref( $_[0] ) ){
1917         $string =  ${$_[0]};
1918         ${$_[0]} = depod1( \$string );
1919     } else {
1920         $string =  $_[0];
1921         depod1( \$string );
1922     }    
1923 }
1924
1925 sub depod1($;$$){
1926   my( $rstr, $func, $closing ) = @_;
1927   my $res = '';
1928   return $res unless defined $$rstr;
1929   if( ! defined( $func ) ){
1930       # skip to next begin of an interior sequence
1931       while( $$rstr =~ s/\A(.*?)([BCEFILSXZ])<(<+[^\S\n]+)?// ){
1932          # recurse into its text
1933          $res .= $1 . depod1( $rstr, $2, closing $3);
1934       }
1935       $res .= $$rstr;
1936   } elsif( $func eq 'E' ){
1937       # E<x> - convert to character
1938       $$rstr =~ s/^(\w+)>//;
1939       $res .= $E2c{$1} || "";
1940   } elsif( $func eq 'X' ){
1941       # X<> - ignore
1942       $$rstr =~ s/^[^>]*>//;
1943   } elsif( $func eq 'Z' ){
1944       # Z<> - empty 
1945       $$rstr =~ s/^>//;
1946   } else {
1947       # all others: either recurse into new function or
1948       # terminate at closing angle bracket
1949       my $term = pattern $closing; 
1950       while( $$rstr =~ s/\A(.*?)(([BCEFILSXZ])<(<+[^\S\n]+)?|$term)// ){
1951           $res .= $1;
1952           last unless $3;
1953           $res .= depod1( $rstr, $3, closing $4 );
1954       }
1955       ## If we're here and $2 ne '>': undelimited interior sequence.
1956       ## Ignored, as this is called without proper indication of where we are.
1957       ## Rely on process_text to produce diagnostics.
1958   }
1959   return $res;
1960 }
1961
1962 #
1963 # fragment_id - construct a fragment identifier from:
1964 #   a) =item text
1965 #   b) contents of C<...>
1966 #
1967 my @hc;
1968 sub fragment_id {
1969     my $text = shift();
1970     $text =~ s/\s+\Z//s;
1971     if( $text ){
1972         # a method or function?
1973         return $1 if $text =~ /(\w+)\s*\(/;
1974         return $1 if $text =~ /->\s*(\w+)\s*\(?/;
1975
1976         # a variable name?
1977         return $1 if $text =~ /^([$@%*]\S+)/;
1978
1979         # some pattern matching operator?
1980         return $1 if $text =~ m|^(\w+/).*/\w*$|;
1981
1982         # fancy stuff... like "do { }"
1983         return $1 if $text =~ m|^(\w+)\s*{.*}$|;
1984
1985         # honour the perlfunc manpage: func [PAR[,[ ]PAR]...]
1986         # and some funnies with ... Module ...
1987         return $1 if $text =~ m{^([a-z\d]+)(\s+[A-Z\d,/& ]+)?$};
1988         return $1 if $text =~ m{^([a-z\d]+)\s+Module(\s+[A-Z\d,/& ]+)?$};
1989
1990         # text? normalize!
1991         $text =~ s/\s+/_/sg;
1992         $text =~ s{(\W)}{
1993          defined( $hc[ord($1)] ) ? $hc[ord($1)]
1994                  : ( $hc[ord($1)] = sprintf( "%%%02X", ord($1) ) ) }gxe;
1995         $text = substr( $text, 0, 50 );
1996     } else {
1997         return undef();
1998     }
1999 }
2000
2001 #
2002 # make_URL_href - generate HTML href from URL
2003 # Special treatment for CGI queries.
2004 #
2005 sub make_URL_href($){
2006     my( $url ) = @_;
2007     if( $url !~ 
2008         s{^(http:[-\w/#~:.+=&%@!]+)(\?.*)$}{<A HREF="$1$2">$1</A>}i ){
2009         $url = "<A HREF=\"$url\">$url</A>";
2010     }
2011     return $url;
2012 }
2013
2014 1;