Refresh CGI to 2.34
[p5sagit/p5-mst-13.2.git] / lib / Pod / Html.pm
1 package Pod::Html;
2
3 use Pod::Functions;
4 use Getopt::Long;       # package for handling command-line parameters
5 require Exporter;
6 @ISA = Exporter;
7 @EXPORT = qw(pod2html htmlify);
8 use Cwd;
9
10 use Carp;
11
12 use strict;
13
14 =head1 NAME
15
16 Pod::HTML - module to convert pod files to HTML
17
18 =head1 SYNOPSIS
19
20     use Pod::HTML;
21     pod2html([options]);
22
23 =head1 DESCRIPTION
24
25 Converts files from pod format (see L<perlpod>) to HTML format.  It
26 can automatically generate indexes and cross-references, and it keeps
27 a cache of things it knows how to cross-reference.
28
29 =head1 ARGUMENTS
30
31 Pod::Html takes the following arguments:
32
33 =over 4
34
35 =item help
36
37     --help
38
39 Displays the usage message.
40
41 =item htmlroot
42
43     --htmlroot=name
44
45 Sets the base URL for the HTML files.  When cross-references are made,
46 the HTML root is prepended to the URL.
47
48 =item infile
49
50     --infile=name
51
52 Specify the pod file to convert.  Input is taken from STDIN if no
53 infile is specified.
54
55 =item outfile
56
57     --outfile=name
58
59 Specify the HTML file to create.  Output goes to STDOUT if no outfile
60 is specified.
61
62 =item podroot
63
64     --podroot=name
65
66 Specify the base directory for finding library pods.
67
68 =item podpath
69
70     --podpath=name:...:name
71
72 Specify which subdirectories of the podroot contain pod files whose
73 HTML converted forms can be linked-to in cross-references.
74
75 =item libpods
76
77     --libpods=name:...:name
78
79 List of page names (eg, "perlfunc") which contain linkable C<=item>s.
80
81 =item netscape
82
83     --netscape
84
85 Use Netscape HTML directives when applicable.
86
87 =item nonetscape
88
89     --nonetscape
90
91 Do not use Netscape HTML directives (default).
92
93 =item index
94
95     --index
96
97 Generate an index at the top of the HTML file (default behaviour).
98
99 =item noindex
100
101     --noindex
102
103 Do not generate an index at the top of the HTML file.
104
105
106 =item recurse
107
108     --recurse
109
110 Recurse into subdirectories specified in podpath (default behaviour).
111
112 =item norecurse
113
114     --norecurse
115
116 Do not recurse into subdirectories specified in podpath.
117
118 =item title
119
120     --title=title
121
122 Specify the title of the resulting HTML file.
123
124 =item verbose
125
126     --verbose
127
128 Display progress messages.
129
130 =back
131
132 =head1 EXAMPLE
133
134     pod2html("pod2html",
135              "--podpath=lib:ext:pod:vms", 
136              "--podroot=/usr/src/perl",
137              "--htmlroot=/perl/nmanual",
138              "--libpods=perlfunc:perlguts:perlvar:perlrun:perlop",
139              "--recurse",
140              "--infile=foo.pod",
141              "--outfile=/perl/nmanual/foo.html");
142
143 =head1 AUTHOR
144
145 Tom Christiansen, E<lt>tchrist@perl.comE<gt>.
146
147 =head1 BUGS
148
149 Has trouble with C<> etc in = commands.
150
151 =head1 SEE ALSO
152
153 L<perlpod>
154
155 =head1 COPYRIGHT
156
157 This program is distributed under the Artistic License.
158
159 =cut
160
161 my $dircache = "pod2html-dircache";
162 my $itemcache = "pod2html-itemcache";
163
164 my @begin_stack = ();           # begin/end stack
165
166 my @libpods = ();               # files to search for links from C<> directives
167 my $htmlroot = "/";             # http-server base directory from which all
168                                 #   relative paths in $podpath stem.
169 my $htmlfile = "";              # write to stdout by default
170 my $podfile = "";               # read from stdin by default
171 my @podpath = ();               # list of directories containing library pods.
172 my $podroot = ".";              # filesystem base directory from which all
173                                 #   relative paths in $podpath stem.
174 my $recurse = 1;                # recurse on subdirectories in $podpath.
175 my $verbose = 0;                # not verbose by default
176 my $doindex = 1;                # non-zero if we should generate an index
177 my $listlevel = 0;              # current list depth
178 my @listitem = ();              # stack of HTML commands to use when a =item is
179                                 #   encountered.  the top of the stack is the
180                                 #   current list.
181 my @listdata = ();              # similar to @listitem, but for the text after
182                                 #   an =item
183 my @listend = ();               # similar to @listitem, but the text to use to
184                                 #   end the list.
185 my $ignore = 1;                 # whether or not to format text.  we don't
186                                 #   format text until we hit our first pod
187                                 #   directive.
188
189 my %items_named = ();           # for the multiples of the same item in perlfunc
190 my @items_seen = ();
191 my $netscape = 0;               # whether or not to use netscape directives.
192 my $title;                      # title to give the pod(s)
193 my $top = 1;                    # true if we are at the top of the doc.  used
194                                 #   to prevent the first <HR> directive.
195 my $paragraph;                  # which paragraph we're processing (used
196                                 #   for error messages)
197 my %pages = ();                 # associative array used to find the location
198                                 #   of pages referenced by L<> links.
199 my %sections = ();              # sections within this page
200 my %items = ();                 # associative array used to find the location
201                                 #   of =item directives referenced by C<> links
202 sub init_globals {
203 $dircache = "pod2html-dircache";
204 $itemcache = "pod2html-itemcache";
205
206 @begin_stack = ();              # begin/end stack
207
208 @libpods = ();          # files to search for links from C<> directives
209 $htmlroot = "/";                # http-server base directory from which all
210                                 #   relative paths in $podpath stem.
211 $htmlfile = "";         # write to stdout by default
212 $podfile = "";          # read from stdin by default
213 @podpath = ();          # list of directories containing library pods.
214 $podroot = ".";         # filesystem base directory from which all
215                                 #   relative paths in $podpath stem.
216 $recurse = 1;           # recurse on subdirectories in $podpath.
217 $verbose = 0;           # not verbose by default
218 $doindex = 1;                   # non-zero if we should generate an index
219 $listlevel = 0;         # current list depth
220 @listitem = ();         # stack of HTML commands to use when a =item is
221                                 #   encountered.  the top of the stack is the
222                                 #   current list.
223 @listdata = ();         # similar to @listitem, but for the text after
224                                 #   an =item
225 @listend = ();          # similar to @listitem, but the text to use to
226                                 #   end the list.
227 $ignore = 1;                    # whether or not to format text.  we don't
228                                 #   format text until we hit our first pod
229                                 #   directive.
230
231 @items_seen = ();
232 %items_named = ();
233 $netscape = 0;          # whether or not to use netscape directives.
234 $title = '';                    # title to give the pod(s)
235 $top = 1;                       # true if we are at the top of the doc.  used
236                                 #   to prevent the first <HR> directive.
237 $paragraph = '';                        # which paragraph we're processing (used
238                                 #   for error messages)
239 %pages = ();                    # associative array used to find the location
240                                 #   of pages referenced by L<> links.
241 %sections = ();         # sections within this page
242 %items = ();                    # associative array used to find the location
243                                 #   of =item directives referenced by C<> links
244
245 }
246
247 sub pod2html {
248     local(@ARGV) = @_;
249     local($/);
250     local $_;
251
252     init_globals();
253
254     # cache of %pages and %items from last time we ran pod2html
255     my $podpath = '';           
256
257     #undef $opt_help if defined $opt_help;
258
259     # parse the command-line parameters
260     parse_command_line();
261
262     # set some variables to their default values if necessary
263     local *POD;
264     unless (@ARGV && $ARGV[0]) { 
265         $podfile  = "-" unless $podfile;        # stdin
266         open(POD, "<$podfile")
267                 || die "$0: cannot open $podfile file for input: $!\n";
268     } else {
269         $podfile = $ARGV[0];  # XXX: might be more filenames
270         *POD = *ARGV;
271     } 
272     $htmlfile = "-" unless $htmlfile;   # stdout
273     $htmlroot = "" if $htmlroot eq "/"; # so we don't get a //
274
275     # read the pod a paragraph at a time
276     warn "Scanning for sections in input file(s)\n" if $verbose;
277     $/ = "";
278     my @poddata  = <POD>;
279     close(POD);
280
281     # scan the pod for =head[1-6] directives and build an index
282     my $index = scan_headings(\%sections, @poddata);
283
284     # open the output file
285     open(HTML, ">$htmlfile")
286             || die "$0: cannot open $htmlfile file for output: $!\n";
287
288     # put a title in the HTML file
289     $title = '';
290     TITLE_SEARCH: {
291         for (my $i = 0; $i < @poddata; $i++) { 
292             if ($poddata[$i] =~ /^=head1\s*NAME\b/m) {
293                 for my $para ( @poddata[$i, $i+1] ) { 
294                     last TITLE_SEARCH if ($title) = $para =~ /(\S+\s+-+\s*.*)/s;
295                 }
296             } 
297
298         } 
299     } 
300     unless ($title) { 
301         $podfile =~ /^(.*)(\.[^.\/]+)?$/;
302         $title = ($podfile eq "-" ? 'No Title' : $1);
303                 warn "found $title" if $verbose;
304     }
305     if ($title =~ /\.pm/) {
306         warn "$0: no title for $podfile";
307         $title = $podfile;
308     }
309     print HTML <<END_OF_HEAD;
310     <HTML> 
311         <HEAD> 
312             <TITLE>$title</TITLE> 
313         </HEAD>
314
315         <BODY>
316
317 END_OF_HEAD
318
319     # load a cache of %pages and %items if possible.  $tests will be
320     #  non-zero if successful.
321     my $tests = 0;
322     if (-f $dircache && -f $itemcache) {
323         warn "scanning for item cache\n" if $verbose;
324         $tests = find_cache($dircache, $itemcache, $podpath, $podroot);
325     }
326
327     # if we didn't succeed in loading the cache then we must (re)build
328     #  %pages and %items.
329     if (!$tests) {
330         warn "scanning directories in pod-path\n" if $verbose;
331         scan_podpath($podroot, $recurse);
332     }
333
334     # scan the pod for =item directives
335     scan_items("", \%items, @poddata);
336
337     # put an index at the top of the file.  note, if $doindex is 0 we
338     # still generate an index, but surround it with an html comment.
339     # that way some other program can extract it if desired.
340     $index =~ s/--+/-/g;
341     print HTML "<!-- INDEX BEGIN -->\n";
342     print HTML "<!--\n" unless $doindex;
343     print HTML $index;
344     print HTML "-->\n" unless $doindex;
345     print HTML "<!-- INDEX END -->\n\n";
346     print HTML "<HR>\n" if $doindex;
347
348     # now convert this file
349     warn "Converting input file\n" if $verbose;
350     foreach my $i (0..$#poddata) {
351         $_ = $poddata[$i];
352         $paragraph = $i+1;
353         if (/^(=.*)/s) {        # is it a pod directive?
354             $ignore = 0;
355             $_ = $1;
356             if (/^=begin\s+(\S+)\s*(.*)/si) {# =begin
357                 process_begin($1, $2);
358             } elsif (/^=end\s+(\S+)\s*(.*)/si) {# =end
359                 process_end($1, $2);
360             } elsif (/^=cut/) {                 # =cut
361                 process_cut();
362             } elsif (/^=pod/) {                 # =pod
363                 process_pod();
364             } else {
365                 next if @begin_stack && $begin_stack[-1] ne 'html';
366
367                 if (/^=(head[1-6])\s+(.*)/s) {  # =head[1-6] heading
368                     process_head($1, $2);
369                 } elsif (/^=item\s*(.*)/sm) {   # =item text
370                     process_item($1);
371                 } elsif (/^=over\s*(.*)/) {             # =over N
372                     process_over();
373                 } elsif (/^=back/) {            # =back
374                     process_back();
375                 } elsif (/^=for\s+(\S+)\s+(.*)/si) {# =for
376                     process_for($1,$2);
377                 } else {
378                     /^=(\S*)\s*/;
379                     warn "$0: $podfile: unknown pod directive '$1' in "
380                        . "paragraph $paragraph.  ignoring.\n";
381                 }
382             }
383             $top = 0;
384         }
385         else {
386             next if $ignore;
387             next if @begin_stack && $begin_stack[-1] ne 'html';
388             my $text = $_;
389             process_text(\$text, 1);
390             print HTML "$text\n<P>\n\n";
391         }
392     }
393
394     # finish off any pending directives
395     finish_list();
396     print HTML <<END_OF_TAIL;
397     </BODY>
398
399     </HTML>
400 END_OF_TAIL
401
402     # close the html file
403     close(HTML);
404
405     warn "Finished\n" if $verbose;
406 }
407
408 ##############################################################################
409
410 my $usage;                      # see below
411 sub usage {
412     my $podfile = shift;
413     warn "$0: $podfile: @_\n" if @_;
414     die $usage;
415 }
416
417 $usage =<<END_OF_USAGE;
418 Usage:  $0 --help --htmlroot=<name> --infile=<name> --outfile=<name>
419            --podpath=<name>:...:<name> --podroot=<name>
420            --libpods=<name>:...:<name> --recurse --verbose --index
421            --netscape --norecurse --noindex
422
423   --flush      - flushes the item and directory caches.
424   --help       - prints this message.
425   --htmlroot   - http-server base directory from which all relative paths
426                  in podpath stem (default is /).
427   --index      - generate an index at the top of the resulting html
428                  (default).
429   --infile     - filename for the pod to convert (input taken from stdin
430                  by default).
431   --libpods    - colon-separated list of pages to search for =item pod
432                  directives in as targets of C<> and implicit links (empty
433                  by default).  note, these are not filenames, but rather
434                  page names like those that appear in L<> links.
435   --netscape   - will use netscape html directives when applicable.
436   --nonetscape - will not use netscape directives (default).
437   --outfile    - filename for the resulting html file (output sent to
438                  stdout by default).
439   --podpath    - colon-separated list of directories containing library
440                  pods.  empty by default.
441   --podroot    - filesystem base directory from which all relative paths
442                  in podpath stem (default is .).
443   --noindex    - don't generate an index at the top of the resulting html.
444   --norecurse  - don't recurse on those subdirectories listed in podpath.
445   --recurse    - recurse on those subdirectories listed in podpath
446                  (default behavior).
447   --title      - title that will appear in resulting html file.
448   --verbose    - self-explanatory
449
450 END_OF_USAGE
451
452 sub parse_command_line {
453     my ($opt_flush,$opt_help,$opt_htmlroot,$opt_index,$opt_infile,$opt_libpods,$opt_netscape,$opt_outfile,$opt_podpath,$opt_podroot,$opt_norecurse,$opt_recurse,$opt_title,$opt_verbose);
454     my $result = GetOptions(
455                             'flush'      => \$opt_flush,
456                             'help'       => \$opt_help,
457                             'htmlroot=s' => \$opt_htmlroot,
458                             'index!'     => \$opt_index,
459                             'infile=s'   => \$opt_infile,
460                             'libpods=s'  => \$opt_libpods,
461                             'netscape!'  => \$opt_netscape,
462                             'outfile=s'  => \$opt_outfile,
463                             'podpath=s'  => \$opt_podpath,
464                             'podroot=s'  => \$opt_podroot,
465                             'norecurse'  => \$opt_norecurse,
466                             'recurse!'   => \$opt_recurse,
467                             'title=s'    => \$opt_title,
468                             'verbose'    => \$opt_verbose,
469                            );
470     usage("-", "invalid parameters") if not $result;
471
472     usage("-") if defined $opt_help;    # see if the user asked for help
473     $opt_help = "";                     # just to make -w shut-up.
474
475     $podfile  = $opt_infile if defined $opt_infile;
476     $htmlfile = $opt_outfile if defined $opt_outfile;
477
478     @podpath  = split(":", $opt_podpath) if defined $opt_podpath;
479     @libpods  = split(":", $opt_libpods) if defined $opt_libpods;
480
481     warn "Flushing item and directory caches\n"
482         if $opt_verbose && defined $opt_flush;
483     unlink($dircache, $itemcache) if defined $opt_flush;
484
485     $htmlroot = $opt_htmlroot if defined $opt_htmlroot;
486     $podroot  = $opt_podroot if defined $opt_podroot;
487
488     $doindex  = $opt_index if defined $opt_index;
489     $recurse  = $opt_recurse if defined $opt_recurse;
490     $title    = $opt_title if defined $opt_title;
491     $verbose  = defined $opt_verbose ? 1 : 0;
492     $netscape = $opt_netscape if defined $opt_netscape;
493 }
494
495 #
496 # find_cache - tries to find if the caches stored in $dircache and $itemcache
497 #  are valid caches of %pages and %items.  if they are valid then it loads
498 #  them and returns a non-zero value.
499 #
500 sub find_cache {
501     my($dircache, $itemcache, $podpath, $podroot) = @_;
502     my($tests);
503     local $_;
504
505     $tests = 0;
506
507     open(CACHE, "<$itemcache") ||
508         die "$0: error opening $itemcache for reading: $!\n";
509     $/ = "\n";
510
511     # is it the same podpath?
512     $_ = <CACHE>;
513     chomp($_);
514     $tests++ if (join(":", @podpath) eq $_);
515
516     # is it the same podroot?
517     $_ = <CACHE>;
518     chomp($_);
519     $tests++ if ($podroot eq $_);
520
521     # load the cache if its good
522     if ($tests != 2) {
523         close(CACHE);
524
525         %items = ();
526         return 0;
527     }
528
529     warn "loading item cache\n" if $verbose;
530     while (<CACHE>) {
531         /(.*?) (.*)$/;
532         $items{$1} = $2;
533     }
534     close(CACHE);
535
536     warn "scanning for directory cache\n" if $verbose;
537     open(CACHE, "<$dircache") ||
538         die "$0: error opening $dircache for reading: $!\n";
539     $/ = "\n";
540     $tests = 0;
541
542     # is it the same podpath?
543     $_ = <CACHE>;
544     chomp($_);
545     $tests++ if (join(":", @podpath) eq $_);
546
547     # is it the same podroot?
548     $_ = <CACHE>;
549     chomp($_);
550     $tests++ if ($podroot eq $_);
551
552     # load the cache if its good
553     if ($tests != 2) {
554         close(CACHE);
555
556         %pages = ();
557         %items = ();
558         return 0;
559     }
560
561     warn "loading directory cache\n" if $verbose;
562     while (<CACHE>) {
563         /(.*?) (.*)$/;
564         $pages{$1} = $2;
565     }
566
567     close(CACHE);
568
569     return 1;
570 }
571
572 #
573 # scan_podpath - scans the directories specified in @podpath for directories,
574 #  .pod files, and .pm files.  it also scans the pod files specified in
575 #  @libpods for =item directives.
576 #
577 sub scan_podpath {
578     my($podroot, $recurse) = @_;
579     my($pwd, $dir);
580     my($libpod, $dirname, $pod, @files, @poddata);
581
582     # scan each directory listed in @podpath
583     $pwd = getcwd();
584     chdir($podroot)
585         || die "$0: error changing to directory $podroot: $!\n";
586     foreach $dir (@podpath) {
587         scan_dir($dir, $recurse);
588     }
589
590     # scan the pods listed in @libpods for =item directives
591     foreach $libpod (@libpods) {
592         # if the page isn't defined then we won't know where to find it
593         # on the system.
594         next unless defined $pages{$libpod} && $pages{$libpod};
595
596         # if there is a directory then use the .pod and .pm files within it.
597         if ($pages{$libpod} =~ /([^:]*[^(\.pod|\.pm)]):/) {
598             #  find all the .pod and .pm files within the directory
599             $dirname = $1;
600             opendir(DIR, $dirname) ||
601                 die "$0: error opening directory $dirname: $!\n";
602             @files = grep(/(\.pod|\.pm)$/ && ! -d $_, readdir(DIR));
603             closedir(DIR);
604
605             # scan each .pod and .pm file for =item directives
606             foreach $pod (@files) {
607                 open(POD, "<$dirname/$pod") ||
608                     die "$0: error opening $dirname/$pod for input: $!\n";
609                 @poddata = <POD>;
610                 close(POD);
611
612                 scan_items("$dirname/$pod", @poddata);
613             }
614
615             # use the names of files as =item directives too.
616             foreach $pod (@files) {
617                 $pod =~ /^(.*)(\.pod|\.pm)$/;
618                 $items{$1} = "$dirname/$1.html" if $1;
619             }
620         } elsif ($pages{$libpod} =~ /([^:]*\.pod):/ ||
621                  $pages{$libpod} =~ /([^:]*\.pm):/) {
622             # scan the .pod or .pm file for =item directives
623             $pod = $1;
624             open(POD, "<$pod") ||
625                 die "$0: error opening $pod for input: $!\n";
626             @poddata = <POD>;
627             close(POD);
628
629             scan_items("$pod", @poddata);
630         } else {
631             warn "$0: shouldn't be here (line ".__LINE__."\n";
632         }
633     }
634     @poddata = ();      # clean-up a bit
635
636     chdir($pwd)
637         || die "$0: error changing to directory $pwd: $!\n";
638
639     # cache the item list for later use
640     warn "caching items for later use\n" if $verbose;
641     open(CACHE, ">$itemcache") ||
642         die "$0: error open $itemcache for writing: $!\n";
643
644     print CACHE join(":", @podpath) . "\n$podroot\n";
645     foreach my $key (keys %items) {
646         print CACHE "$key $items{$key}\n";
647     }
648
649     close(CACHE);
650
651     # cache the directory list for later use
652     warn "caching directories for later use\n" if $verbose;
653     open(CACHE, ">$dircache") ||
654         die "$0: error open $dircache for writing: $!\n";
655
656     print CACHE join(":", @podpath) . "\n$podroot\n";
657     foreach my $key (keys %pages) {
658         print CACHE "$key $pages{$key}\n";
659     }
660
661     close(CACHE);
662 }
663
664 #
665 # scan_dir - scans the directory specified in $dir for subdirectories, .pod
666 #  files, and .pm files.  notes those that it finds.  this information will
667 #  be used later in order to figure out where the pages specified in L<>
668 #  links are on the filesystem.
669 #
670 sub scan_dir {
671     my($dir, $recurse) = @_;
672     my($t, @subdirs, @pods, $pod, $dirname, @dirs);
673     local $_;
674
675     @subdirs = ();
676     @pods = ();
677
678     opendir(DIR, $dir) ||
679         die "$0: error opening directory $dir: $!\n";
680     while (defined($_ = readdir(DIR))) {
681         if (-d "$dir/$_" && $_ ne "." && $_ ne "..") {      # directory
682             $pages{$_}  = "" unless defined $pages{$_};
683             $pages{$_} .= "$dir/$_:";
684             push(@subdirs, $_);
685         } elsif (/\.pod$/) {                                # .pod
686             s/\.pod$//;
687             $pages{$_}  = "" unless defined $pages{$_};
688             $pages{$_} .= "$dir/$_.pod:";
689             push(@pods, "$dir/$_.pod");
690         } elsif (/\.pm$/) {                                 # .pm
691             s/\.pm$//;
692             $pages{$_}  = "" unless defined $pages{$_};
693             $pages{$_} .= "$dir/$_.pm:";
694             push(@pods, "$dir/$_.pm");
695         }
696     }
697     closedir(DIR);
698
699     # recurse on the subdirectories if necessary
700     if ($recurse) {
701         foreach my $subdir (@subdirs) {
702             scan_dir("$dir/$subdir", $recurse);
703         }
704     }
705 }
706
707 #
708 # scan_headings - scan a pod file for head[1-6] tags, note the tags, and
709 #  build an index.
710 #
711 sub scan_headings {
712     my($sections, @data) = @_;
713     my($tag, $which_head, $title, $listdepth, $index);
714
715     $listdepth = 0;
716     $index = "";
717
718     # scan for =head directives, note their name, and build an index
719     #  pointing to each of them.
720     foreach my $line (@data) {
721         if ($line =~ /^\s*=(head)([1-6])\s+(.*)/) {
722             ($tag,$which_head, $title) = ($1,$2,$3);
723             chomp($title);
724             $$sections{htmlify(0,$title)} = 1;
725
726             if ($which_head > $listdepth) {
727                 $index .= "\n" . ("\t" x $listdepth) . "<UL>\n";
728             } elsif ($which_head < $listdepth) {
729                 $listdepth--;
730                 $index .= "\n" . ("\t" x $listdepth) . "</UL>\n";
731             }
732             $listdepth = $which_head;
733
734             $index .= "\n" . ("\t" x $listdepth) . "<LI>" .
735                       "<A HREF=\"#" . htmlify(0,$title) . "\">$title</A>";
736         }
737     }
738
739     # finish off the lists
740     while ($listdepth--) {
741         $index .= "\n" . ("\t" x $listdepth) . "</UL>\n";
742     }
743
744     # get rid of bogus lists
745     $index =~ s,\t*<UL>\s*</UL>\n,,g;
746
747     return $index;
748 }
749
750 #
751 # scan_items - scans the pod specified by $pod for =item directives.  we
752 #  will use this information later on in resolving C<> links.
753 #
754 sub scan_items {
755     my($pod, @poddata) = @_;
756     my($i, $item);
757     local $_;
758
759     $pod =~ s/\.pod$//;
760     $pod .= ".html" if $pod;
761
762     foreach $i (0..$#poddata) {
763         $_ = $poddata[$i];
764
765         # remove any formatting instructions
766         s,[A-Z]<([^<>]*)>,$1,g;
767
768         # figure out what kind of item it is and get the first word of
769         #  it's name.
770         if (/^=item\s+(\w*)\s*.*$/s) {
771             if ($1 eq "*") {            # bullet list
772                 /\A=item\s+\*\s*(.*?)\s*\Z/s;
773                 $item = $1;
774             } elsif ($1 =~ /^[0-9]+/) { # numbered list
775                 /\A=item\s+[0-9]+\.?(.*?)\s*\Z/s;
776                 $item = $1;
777             } else {
778 #               /\A=item\s+(.*?)\s*\Z/s;
779                 /\A=item\s+(\w*)/s;
780                 $item = $1;
781             }
782
783             $items{$item} = "$pod" if $item;
784         }
785     }
786 }
787
788 #
789 # process_head - convert a pod head[1-6] tag and convert it to HTML format.
790 #
791 sub process_head {
792     my($tag, $heading) = @_;
793     my $firstword;
794
795     # figure out the level of the =head
796     $tag =~ /head([1-6])/;
797     my $level = $1;
798
799     # can't have a heading full of spaces and speechmarks and so on
800     $firstword = $heading; $firstword =~ s/\s*(\w+)\s.*/$1/;
801
802     print HTML "<P>\n" unless $listlevel;
803     print HTML "<HR>\n" unless $listlevel || $top;
804     print HTML "<H$level>"; # unless $listlevel;
805     #print HTML "<H$level>" unless $listlevel;
806     my $convert = $heading; process_text(\$convert);
807     print HTML '<A NAME="' . htmlify(0,$heading) . "\">$convert</A>";
808     print HTML "</H$level>"; # unless $listlevel;
809     print HTML "\n";
810 }
811
812 #
813 # process_item - convert a pod item tag and convert it to HTML format.
814 #
815 sub process_item {
816     my $text = $_[0];
817     my($i, $quote, $name);
818
819     my $need_preamble = 0;
820     my $this_entry;
821
822
823     # lots of documents start a list without doing an =over.  this is
824     # bad!  but, the proper thing to do seems to be to just assume
825     # they did do an =over.  so warn them once and then continue.
826     warn "$0: $podfile: unexpected =item directive in paragraph $paragraph.  ignoring.\n"
827         unless $listlevel;
828     process_over() unless $listlevel;
829
830     return unless $listlevel;
831
832     # remove formatting instructions from the text
833     1 while $text =~ s/[A-Z]<([^<>]*)>/$1/g;
834     pre_escape(\$text);
835
836     $need_preamble = $items_seen[$listlevel]++ == 0;
837
838     # check if this is the first =item after an =over
839     $i = $listlevel - 1;
840     my $need_new = $listlevel >= @listitem;
841
842     if ($text =~ /\A\*/) {              # bullet
843
844         if ($need_preamble) {
845             push(@listend,  "</UL>");
846             print HTML "<UL>\n";
847         }
848
849        print HTML "<LI><STRONG>";
850        $text =~ /\A\*\s*(.*)\Z/s;
851        print HTML "<A NAME=\"item_" . htmlify(1,$1) . "\">" if $1 && !$items_named{$1}++;
852        $quote = 1;
853        #print HTML process_puretext($1, \$quote);
854        print HTML $1;
855        print HTML "</A>" if $1;
856        print HTML "</STRONG>";
857
858     } elsif ($text =~ /\A[0-9#]+/) {    # numbered list
859
860         if ($need_preamble) {
861             push(@listend,  "</OL>");
862             print HTML "<OL>\n";
863         }
864
865        print HTML "<LI><STRONG>";
866        $text =~ /\A[0-9]+\.?(.*)\Z/s;
867        print HTML "<A NAME=\"item_" . htmlify(0,$1) . "\">" if $1;
868        $quote = 1;
869        #print HTML process_puretext($1, \$quote);
870        print HTML $1 if $1;
871        print HTML "</A>" if $1;
872        print HTML "</STRONG>";
873
874     } else {                    # all others
875
876         if ($need_preamble) {
877             push(@listend,  '</DL>');
878             print HTML "<DL>\n";
879         }
880
881        print HTML "<DT><STRONG>";
882        print HTML "<A NAME=\"item_" . htmlify(1,$text) . "\">" 
883             if $text && !$items_named{($text =~ /(\S+)/)[0]}++;
884             # preceding craziness so that the duplicate leading bits in 
885             # perlfunc work to find just the first one.  otherwise
886             # open etc would have many names
887        $quote = 1;
888        #print HTML process_puretext($text, \$quote);
889        print HTML $text;
890        print HTML "</A>" if $text;
891        print HTML "</STRONG>";
892
893        print HTML '<DD>';
894     }
895
896     print HTML "\n";
897 }
898
899 #
900 # process_over - process a pod over tag and start a corresponding HTML
901 # list.
902 #
903 sub process_over {
904     # start a new list
905     $listlevel++;
906 }
907
908 #
909 # process_back - process a pod back tag and convert it to HTML format.
910 #
911 sub process_back {
912     warn "$0: $podfile: unexpected =back directive in paragraph $paragraph.  ignorning.\n"
913         unless $listlevel;
914     return unless $listlevel;
915
916     # close off the list.  note, I check to see if $listend[$listlevel] is
917     # defined because an =item directive may have never appeared and thus
918     # $listend[$listlevel] may have never been initialized.
919     $listlevel--;
920     print HTML $listend[$listlevel] if defined $listend[$listlevel];
921     print HTML "\n";
922
923     # don't need the corresponding perl code anymore
924     pop(@listitem);
925     pop(@listdata);
926     pop(@listend);
927
928     pop(@items_seen);
929 }
930
931 #
932 # process_cut - process a pod cut tag, thus stop ignoring pod directives.
933 #
934 sub process_cut {
935     $ignore = 1;
936 }
937
938 #
939 # process_pod - process a pod pod tag, thus ignore pod directives until we see a
940 # corresponding cut.
941 #
942 sub process_pod {
943     # no need to set $ignore to 0 cause the main loop did it
944 }
945
946 #
947 # process_for - process a =for pod tag.  if it's for html, split
948 # it out verbatim, otherwise ignore it.
949 #
950 sub process_for {
951     my($whom, $text) = @_;
952     if ( $whom =~ /^(pod2)?html$/i) {
953         print HTML $text;
954     } 
955 }
956
957 #
958 # process_begin - process a =begin pod tag.  this pushes
959 # whom we're beginning on the begin stack.  if there's a
960 # begin stack, we only print if it us.
961 #
962 sub process_begin {
963     my($whom, $text) = @_;
964     $whom = lc($whom);
965     push (@begin_stack, $whom);
966     if ( $whom =~ /^(pod2)?html$/) {
967         print HTML $text if $text;
968     }
969 }
970
971 #
972 # process_end - process a =end pod tag.  pop the
973 # begin stack.  die if we're mismatched.
974 #
975 sub process_end {
976     my($whom, $text) = @_;
977     $whom = lc($whom);
978     if ($begin_stack[-1] ne $whom ) {
979         die "Unmatched begin/end at chunk $paragraph\n"
980     } 
981     pop @begin_stack;
982 }
983
984 #
985 # process_text - handles plaintext that appears in the input pod file.
986 # there may be pod commands embedded within the text so those must be
987 # converted to html commands.
988 #
989 sub process_text {
990     my($text, $escapeQuotes) = @_;
991     my($result, $rest, $s1, $s2, $s3, $s4, $match, $bf);
992     my($podcommand, $params, $tag, $quote);
993
994     return if $ignore;
995
996     $quote  = 0;                # status of double-quote conversion
997     $result = "";
998     $rest = $$text;
999
1000     if ($rest =~ /^\s+/) {      # preformatted text, no pod directives
1001         $rest   =~ s/\n+\Z//;
1002
1003         $rest   =~ s/&/&amp;/g;
1004         $rest   =~ s/</&lt;/g;
1005         $rest   =~ s/>/&gt;/g;
1006         $rest   =~ s/"/&quot;/g;
1007
1008         # try and create links for all occurrences of perl.* within
1009         # the preformatted text.
1010         $rest =~ s{
1011                     (\s*)(perl\w+)
1012                   }{
1013                     if (defined $pages{$2}) {   # is a link
1014                         qq($1<A HREF="$htmlroot/$pages{$2}">$2</A>);
1015                     } else {
1016                         "$1$2";
1017                     }
1018                   }xeg;
1019         $rest =~ s/(<A HREF=)([^>:]*:)?([^>:]*)\.pod:([^>:]*:)?/$1$3.html/g;
1020
1021   my $urls = '(' . join ('|', qw{
1022                 http
1023                 telnet
1024                 mailto
1025                 news
1026                 gopher
1027                 file
1028                 wais
1029                 ftp
1030             } ) 
1031         . ')';
1032   
1033   my $ltrs = '\w';
1034   my $gunk = '/#~:.?+=&%@!\-';
1035   my $punc = '.:?\-';
1036   my $any  = "${ltrs}${gunk}${punc}";
1037
1038   $rest =~ s{
1039         \b                          # start at word boundary
1040         (                           # begin $1  {
1041           $urls     :               # need resource and a colon
1042           [$any] +?                 # followed by on or more
1043                                     #  of any valid character, but
1044                                     #  be conservative and take only
1045                                     #  what you need to....
1046         )                           # end   $1  }
1047         (?=                         # look-ahead non-consumptive assertion
1048                 [$punc]*            # either 0 or more puntuation
1049                 [^$any]             #   followed by a non-url char
1050             |                       # or else
1051                 $                   #   then end of the string
1052         )
1053       }{<A HREF="$1">$1</A>}igox;
1054
1055         $result =   "<PRE>"     # text should be as it is (verbatim)
1056                   . "$rest\n"
1057                   . "</PRE>\n";
1058     } else {                    # formatted text
1059         # parse through the string, stopping each time we find a
1060         # pod-escape.  once the string has been throughly processed
1061         # we can output it.
1062         while ($rest) {
1063             # check to see if there are any possible pod directives in
1064             # the remaining part of the text.
1065             if ($rest =~ m/[BCEIFLSZ]</) {
1066                 warn "\$rest\t= $rest\n" unless
1067                     $rest =~ /\A
1068                            ([^<]*?)
1069                            ([BCEIFLSZ]?)
1070                            <
1071                            (.*)\Z/xs;
1072
1073                 $s1 = $1;       # pure text
1074                 $s2 = $2;       # the type of pod-escape that follows
1075                 $s3 = '<';      # '<'
1076                 $s4 = $3;       # the rest of the string
1077             } else {
1078                 $s1 = $rest;
1079                 $s2 = "";
1080                 $s3 = "";
1081                 $s4 = "";
1082             }
1083
1084             if ($s3 eq '<' && $s2) {    # a pod-escape
1085                 $result    .= ($escapeQuotes ? process_puretext($s1, \$quote) : $s1);
1086                 $podcommand = "$s2<";
1087                 $rest       = $s4;
1088
1089                 # find the matching '>'
1090                 $match = 1;
1091                 $bf = 0;
1092                 while ($match && !$bf) {
1093                     $bf = 1;
1094                     if ($rest =~ /\A([^<>]*[BCEIFLSZ]<)(.*)\Z/s) {
1095                         $bf = 0;
1096                         $match++;
1097                         $podcommand .= $1;
1098                         $rest        = $2;
1099                     } elsif ($rest =~ /\A([^>]*>)(.*)\Z/s) {
1100                         $bf = 0;
1101                         $match--;
1102                         $podcommand .= $1;
1103                         $rest        = $2;
1104                     }
1105                 }
1106
1107                 if ($match != 0) {
1108                     warn <<WARN;
1109 $0: $podfile: cannot find matching > for $s2 in paragraph $paragraph.
1110 WARN
1111                     $result .= substr $podcommand, 0, 2;
1112                     $rest = substr($podcommand, 2) . $rest;
1113                     next;
1114                 }
1115
1116                 # pull out the parameters to the pod-escape
1117                 $podcommand =~ /^([BCFEILSZ]?)<(.*)>$/s;
1118                 $tag    = $1;
1119                 $params = $2;
1120
1121                 # process the text within the pod-escape so that any escapes
1122                 # which must occur do.
1123                 process_text(\$params, 0) unless $tag eq 'L';
1124
1125                 $s1 = $params;
1126                 if (!$tag || $tag eq " ") {     #  <> : no tag
1127                     $s1 = "&lt;$params&gt;";
1128                 } elsif ($tag eq "L") {         # L<> : link 
1129                     $s1 = process_L($params);
1130                 } elsif ($tag eq "I" ||         # I<> : italicize text
1131                          $tag eq "B" ||         # B<> : bold text
1132                          $tag eq "F") {         # F<> : file specification
1133                     $s1 = process_BFI($tag, $params);
1134                 } elsif ($tag eq "C") {         # C<> : literal code
1135                     $s1 = process_C($params, 1);
1136                 } elsif ($tag eq "E") {         # E<> : escape
1137                     $s1 = process_E($params);
1138                 } elsif ($tag eq "Z") {         # Z<> : zero-width character
1139                     $s1 = process_Z($params);
1140                 } elsif ($tag eq "S") {         # S<> : non-breaking space
1141                     $s1 = process_S($params);
1142                 } elsif ($tag eq "X") {         # S<> : non-breaking space
1143                     $s1 = process_X($params);
1144                 } else {
1145                     warn "$0: $podfile: unhandled tag '$tag' in paragraph $paragraph\n";
1146                 }
1147
1148                 $result .= "$s1";
1149             } else {
1150                 # for pure text we must deal with implicit links and
1151                 # double-quotes among other things.
1152                 $result .= ($escapeQuotes ? process_puretext("$s1$s2$s3", \$quote) : "$s1$s2$s3");
1153                 $rest    = $s4;
1154             }
1155         }
1156     }
1157     $$text = $result;
1158 }
1159
1160 sub html_escape {
1161     my $rest = $_[0];
1162     $rest   =~ s/&/&amp;/g;
1163     $rest   =~ s/</&lt;/g;
1164     $rest   =~ s/>/&gt;/g;
1165     $rest   =~ s/"/&quot;/g;
1166     return $rest;
1167
1168
1169 #
1170 # process_puretext - process pure text (without pod-escapes) converting
1171 #  double-quotes and handling implicit C<> links.
1172 #
1173 sub process_puretext {
1174     my($text, $quote) = @_;
1175     my(@words, $result, $rest, $lead, $trail);
1176
1177     # convert double-quotes to single-quotes
1178     $text =~ s/\A([^"]*)"/$1''/s if $$quote;
1179     while ($text =~ s/\A([^"]*)["]([^"]*)["]/$1``$2''/sg) {}
1180
1181     $$quote = ($text =~ m/"/ ? 1 : 0);
1182     $text =~ s/\A([^"]*)"/$1``/s if $$quote;
1183
1184     # keep track of leading and trailing white-space
1185     $lead  = ($text =~ /\A(\s*)/s ? $1 : "");
1186     $trail = ($text =~ /(\s*)\Z/s ? $1 : "");
1187
1188     # collapse all white space into a single space
1189     $text =~ s/\s+/ /g;
1190     @words = split(" ", $text);
1191
1192     # process each word individually
1193     foreach my $word (@words) {
1194         # see if we can infer a link
1195         if ($word =~ /^\w+\(/) {
1196             # has parenthesis so should have been a C<> ref
1197             $word = process_C($word);
1198 #           $word =~ /^[^()]*]\(/;
1199 #           if (defined $items{$1} && $items{$1}) {
1200 #               $word =   "\n<CODE><A HREF=\"$htmlroot/$items{$1}#item_"
1201 #                       . htmlify(0,$word)
1202 #                       . "\">$word</A></CODE>";
1203 #           } elsif (defined $items{$word} && $items{$word}) {
1204 #               $word =   "\n<CODE><A HREF=\"$htmlroot/$items{$word}#item_"
1205 #                       . htmlify(0,$word)
1206 #                       . "\">$word</A></CODE>";
1207 #           } else {
1208 #               $word =   "\n<CODE><A HREF=\"#item_"
1209 #                       . htmlify(0,$word)
1210 #                       . "\">$word</A></CODE>";
1211 #           }
1212         } elsif ($word =~ /^[\$\@%&*]+\w+$/) {
1213             # perl variables, should be a C<> ref
1214             $word = process_C($word, 1);
1215         } elsif ($word =~ m,^\w+://\w,) {
1216             # looks like a URL
1217             $word = qq(<A HREF="$word">$word</A>);
1218         } elsif ($word =~ /[\w.-]+\@\w+\.\w/) {
1219             # looks like an e-mail address
1220             $word = qq(<A HREF="MAILTO:$word">$word</A>);
1221         } elsif ($word !~ /[a-z]/ && $word =~ /[A-Z]/) {  # all uppercase?
1222             $word = html_escape($word) if $word =~ /[&<>]/;
1223             $word = "\n<FONT SIZE=-1>$word</FONT>" if $netscape;
1224         } else { 
1225             $word = html_escape($word) if $word =~ /[&<>]/;
1226         }
1227     }
1228
1229     # build a new string based upon our conversion
1230     $result = "";
1231     $rest   = join(" ", @words);
1232     while (length($rest) > 75) {
1233         if ( $rest =~ m/^(.{0,75})\s(.*?)$/o ||
1234              $rest =~ m/^(\S*)\s(.*?)$/o) {
1235
1236             $result .= "$1\n";
1237             $rest    = $2;
1238         } else {
1239             $result .= "$rest\n";
1240             $rest    = "";
1241         }
1242     }
1243     $result .= $rest if $rest;
1244
1245     # restore the leading and trailing white-space
1246     $result = "$lead$result$trail";
1247
1248     return $result;
1249 }
1250
1251 #
1252 # pre_escape - convert & in text to $amp;
1253 #
1254 sub pre_escape {
1255     my($str) = @_;
1256
1257     $$str =~ s,&,&amp;,g;
1258 }
1259
1260 #
1261 # process_L - convert a pod L<> directive to a corresponding HTML link.
1262 #  most of the links made are inferred rather than known about directly
1263 #  (i.e it's not known whether the =head\d section exists in the target file,
1264 #   or whether a .pod file exists in the case of split files).  however, the
1265 #  guessing usually works.
1266 #
1267 # Unlike the other directives, this should be called with an unprocessed
1268 # string, else tags in the link won't be matched.
1269 #
1270 sub process_L {
1271     my($str) = @_;
1272     my($s1, $s2, $linktext, $page, $section, $link);    # work strings
1273
1274     $str =~ s/\n/ /g;                   # undo word-wrapped tags
1275     $s1 = $str;
1276     for ($s1) {
1277         # a :: acts like a /
1278         s,::,/,;
1279
1280         # make sure sections start with a /
1281         s,^",/",g;
1282         s,^,/,g if (!m,/, && / /);
1283
1284         # check if there's a section specified
1285         if (m,^(.*?)/"?(.*?)"?$,) {     # yes
1286             ($page, $section) = ($1, $2);
1287         } else {                        # no
1288             ($page, $section) = ($str, "");
1289         }
1290
1291         # check if we know that this is a section in this page
1292         if (!defined $pages{$page} && defined $sections{$page}) {
1293             $section = $page;
1294             $page = "";
1295         }
1296     }
1297
1298     if ($page eq "") {
1299         $link = "#" . htmlify(0,$section);
1300         $linktext = $section;
1301     } elsif (!defined $pages{$page}) {
1302         warn "$0: $podfile: cannot resolve L<$str> in paragraph $paragraph: no such page '$page'\n";
1303         $link = "";
1304         $linktext = $page;
1305     } else {
1306         $linktext  = ($section ? "$section" : "the $page manpage");
1307         $section = htmlify(0,$section) if $section ne "";
1308
1309         # if there is a directory by the name of the page, then assume that an
1310         # appropriate section will exist in the subdirectory
1311         if ($section ne "" && $pages{$page} =~ /([^:]*[^(\.pod|\.pm)]):/) {
1312             $link = "$htmlroot/$1/$section.html";
1313
1314         # since there is no directory by the name of the page, the section will
1315         # have to exist within a .html of the same name.  thus, make sure there
1316         # is a .pod or .pm that might become that .html
1317         } else {
1318             $section = "#$section";
1319             # check if there is a .pod with the page name
1320             if ($pages{$page} =~ /([^:]*)\.pod:/) {
1321                 $link = "$htmlroot/$1.html$section";
1322             } elsif ($pages{$page} =~ /([^:]*)\.pm:/) {
1323                 $link = "$htmlroot/$1.html$section";
1324             } else {
1325                 warn "$0: $podfile: cannot resolve L$str in paragraph $paragraph: ".
1326                              "no .pod or .pm found\n";
1327                 $link = "";
1328                 $linktext = $section;
1329             }
1330         }
1331     }
1332
1333     process_text(\$linktext, 0);
1334     if ($link) {
1335         $s1 = "<A HREF=\"$link\">$linktext</A>";
1336     } else {
1337         $s1 = "<EM>$linktext</EM>";
1338     }
1339     return $s1;
1340 }
1341
1342 #
1343 # process_BFI - process any of the B<>, F<>, or I<> pod-escapes and
1344 # convert them to corresponding HTML directives.
1345 #
1346 sub process_BFI {
1347     my($tag, $str) = @_;
1348     my($s1);                    # work string
1349     my(%repltext) = (   'B' => 'STRONG',
1350                         'F' => 'EM',
1351                         'I' => 'EM');
1352
1353     # extract the modified text and convert to HTML
1354     $s1 = "<$repltext{$tag}>$str</$repltext{$tag}>";
1355     return $s1;
1356 }
1357
1358 #
1359 # process_C - process the C<> pod-escape.
1360 #
1361 sub process_C {
1362     my($str, $doref) = @_;
1363     my($s1, $s2);
1364
1365     $s1 = $str;
1366     $s1 =~ s/\([^()]*\)//g;     # delete parentheses
1367     $str = $s2 = $s1;
1368     $s1 =~ s/\W//g;             # delete bogus characters
1369
1370     # if there was a pod file that we found earlier with an appropriate
1371     # =item directive, then create a link to that page.
1372     if ($doref && defined $items{$s1}) {
1373         $s1 = ($items{$s1} ?
1374                "<A HREF=\"$htmlroot/$items{$s1}#item_" . htmlify(0,$s2) .  "\">$str</A>" :
1375                "<A HREF=\"#item_" . htmlify(0,$s2) .  "\">$str</A>");
1376         $s1 =~ s,(perl\w+/(\S+)\.html)#item_\2\b,$1,; 
1377         confess "s1 has space: $s1" if $s1 =~ /HREF="[^"]*\s[^"]*"/;
1378     } else {
1379         $s1 = "<CODE>$str</CODE>";
1380         # warn "$0: $podfile: cannot resolve C<$str> in paragraph $paragraph\n" if $verbose
1381     }
1382
1383
1384     return $s1;
1385 }
1386
1387 #
1388 # process_E - process the E<> pod directive which seems to escape a character.
1389 #
1390 sub process_E {
1391     my($str) = @_;
1392
1393     for ($str) {
1394         s,([^/].*),\&$1\;,g;
1395     }
1396
1397     return $str;
1398 }
1399
1400 #
1401 # process_Z - process the Z<> pod directive which really just amounts to
1402 # ignoring it.  this allows someone to start a paragraph with an =
1403 #
1404 sub process_Z {
1405     my($str) = @_;
1406
1407     # there is no equivalent in HTML for this so just ignore it.
1408     $str = "";
1409     return $str;
1410 }
1411
1412 #
1413 # process_S - process the S<> pod directive which means to convert all
1414 # spaces in the string to non-breaking spaces (in HTML-eze).
1415 #
1416 sub process_S {
1417     my($str) = @_;
1418
1419     # convert all spaces in the text to non-breaking spaces in HTML.
1420     $str =~ s/ /&nbsp;/g;
1421     return $str;
1422 }
1423
1424 #
1425 # process_X - this is supposed to make an index entry.  we'll just 
1426 # ignore it.
1427 #
1428 sub process_X {
1429     return '';
1430 }
1431
1432
1433 #
1434 # finish_list - finish off any pending HTML lists.  this should be called
1435 # after the entire pod file has been read and converted.
1436 #
1437 sub finish_list {
1438     while ($listlevel >= 0) {
1439         print HTML "</DL>\n";
1440         $listlevel--;
1441     }
1442 }
1443
1444 #
1445 # htmlify - converts a pod section specification to a suitable section
1446 # specification for HTML.  if first arg is 1, only takes 1st word.
1447 #
1448 sub htmlify {
1449     my($compact, $heading) = @_;
1450
1451     if ($compact) {
1452       $heading =~ /^(\w+)/;
1453       $heading = $1;
1454     } 
1455
1456   # $heading = lc($heading);
1457   $heading =~ s/[^\w\s]/_/g;
1458   $heading =~ s/(\s+)/ /g;
1459   $heading =~ s/^\s*(.*?)\s*$/$1/s;
1460   $heading =~ s/ /_/g;
1461   $heading =~ s/\A(.{32}).*\Z/$1/s;
1462   $heading =~ s/\s+\Z//;
1463   $heading =~ s/_{2,}/_/g;
1464
1465   return $heading;
1466 }
1467
1468 BEGIN {
1469 }
1470
1471 1;
1472