summary refs log tree commit diff stats
path: root/plugins/perl/lib/Pod/Html.pm
diff options
context:
space:
mode:
authorberkeviktor@aol.com <berkeviktor@aol.com>2011-02-24 04:14:30 +0100
committerberkeviktor@aol.com <berkeviktor@aol.com>2011-02-24 04:14:30 +0100
commit4a6ceffb98a0b785494f680d3776c4bfc4052f9e (patch)
tree850703c1c841ccd99f58d0b06084615aaebe782c /plugins/perl/lib/Pod/Html.pm
parentf16af8be941b596dedac3bf4e371ee2d21f4b598 (diff)
add xchat r1489
Diffstat (limited to 'plugins/perl/lib/Pod/Html.pm')
-rw-r--r--plugins/perl/lib/Pod/Html.pm2399
1 files changed, 2399 insertions, 0 deletions
diff --git a/plugins/perl/lib/Pod/Html.pm b/plugins/perl/lib/Pod/Html.pm
new file mode 100644
index 00000000..3695564b
--- /dev/null
+++ b/plugins/perl/lib/Pod/Html.pm
@@ -0,0 +1,2399 @@
+package Pod::Html;
+use strict;
+require Exporter;
+
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+$VERSION   = 1.08;
+@ISA       = qw(Exporter);
+@EXPORT    = qw(pod2html htmlify);
+@EXPORT_OK = qw(anchorify);
+
+use Carp;
+use Config;
+use Cwd;
+use File::Spec;
+use File::Spec::Unix;
+use Getopt::Long;
+
+use locale;    # make \w work right in non-ASCII lands
+
+=head1 NAME
+
+Pod::Html - module to convert pod files to HTML
+
+=head1 SYNOPSIS
+
+    use Pod::Html;
+    pod2html([options]);
+
+=head1 DESCRIPTION
+
+Converts files from pod format (see L<perlpod>) to HTML format.  It
+can automatically generate indexes and cross-references, and it keeps
+a cache of things it knows how to cross-reference.
+
+=head1 FUNCTIONS
+
+=head2 pod2html
+
+    pod2html("pod2html",
+             "--podpath=lib:ext:pod:vms",
+             "--podroot=/usr/src/perl",
+             "--htmlroot=/perl/nmanual",
+             "--libpods=perlfunc:perlguts:perlvar:perlrun:perlop",
+             "--recurse",
+             "--infile=foo.pod",
+             "--outfile=/perl/nmanual/foo.html");
+
+pod2html takes the following arguments:
+
+=over 4
+
+=item backlink
+
+    --backlink="Back to Top"
+
+Adds "Back to Top" links in front of every C<head1> heading (except for
+the first).  By default, no backlinks are generated.
+
+=item cachedir
+
+    --cachedir=name
+
+Creates the item and directory caches in the given directory.
+
+=item css
+
+    --css=stylesheet
+
+Specify the URL of a cascading style sheet.  Also disables all HTML/CSS
+C<style> attributes that are output by default (to avoid conflicts).
+
+=item flush
+
+    --flush
+
+Flushes the item and directory caches.
+
+=item header
+
+    --header
+    --noheader
+
+Creates header and footer blocks containing the text of the C<NAME>
+section.  By default, no headers are generated.
+
+=item help
+
+    --help
+
+Displays the usage message.
+
+=item hiddendirs
+
+    --hiddendirs
+    --nohiddendirs
+
+Include hidden directories in the search for POD's in podpath if recurse
+is set.
+The default is not to traverse any directory whose name begins with C<.>.
+See L</"podpath"> and L</"recurse">.
+
+[This option is for backward compatibility only.
+It's hard to imagine that one would usefully create a module with a
+name component beginning with C<.>.]
+
+=item htmldir
+
+    --htmldir=name
+
+Sets the directory in which the resulting HTML file is placed.  This
+is used to generate relative links to other files. Not passing this
+causes all links to be absolute, since this is the value that tells
+Pod::Html the root of the documentation tree.
+
+=item htmlroot
+
+    --htmlroot=name
+
+Sets the base URL for the HTML files.  When cross-references are made,
+the HTML root is prepended to the URL.
+
+=item index
+
+    --index
+    --noindex
+
+Generate an index at the top of the HTML file.  This is the default
+behaviour.
+
+=item infile
+
+    --infile=name
+
+Specify the pod file to convert.  Input is taken from STDIN if no
+infile is specified.
+
+=item libpods
+
+    --libpods=name:...:name
+
+List of page names (eg, "perlfunc") which contain linkable C<=item>s.
+
+=item netscape
+
+    --netscape
+    --nonetscape
+
+B<Deprecated>, has no effect. For backwards compatibility only.
+
+=item outfile
+
+    --outfile=name
+
+Specify the HTML file to create.  Output goes to STDOUT if no outfile
+is specified.
+
+=item podpath
+
+    --podpath=name:...:name
+
+Specify which subdirectories of the podroot contain pod files whose
+HTML converted forms can be linked to in cross references.
+
+=item podroot
+
+    --podroot=name
+
+Specify the base directory for finding library pods.
+
+=item quiet
+
+    --quiet
+    --noquiet
+
+Don't display I<mostly harmless> warning messages.  These messages
+will be displayed by default.  But this is not the same as C<verbose>
+mode.
+
+=item recurse
+
+    --recurse
+    --norecurse
+
+Recurse into subdirectories specified in podpath (default behaviour).
+
+=item title
+
+    --title=title
+
+Specify the title of the resulting HTML file.
+
+=item verbose
+
+    --verbose
+    --noverbose
+
+Display progress messages.  By default, they won't be displayed.
+
+=back
+
+=head2 htmlify
+
+    htmlify($heading);
+
+Converts a pod section specification to a suitable section specification
+for HTML. Note that we keep spaces and special characters except 
+C<", ?> (Netscape problem) and the hyphen (writer's problem...).
+
+=head2 anchorify
+
+    anchorify(@heading);
+
+Similar to C<htmlify()>, but turns non-alphanumerics into underscores.  Note
+that C<anchorify()> is not exported by default.
+
+=head1 ENVIRONMENT
+
+Uses C<$Config{pod2html}> to setup default options.
+
+=head1 AUTHOR
+
+Tom Christiansen, E<lt>tchrist@perl.comE<gt>.
+
+=head1 SEE ALSO
+
+L<perlpod>
+
+=head1 COPYRIGHT
+
+This program is distributed under the Artistic License.
+
+=cut
+
+my ($Cachedir);
+my ( $Dircache, $Itemcache );
+my @Begin_Stack;
+my @Libpods;
+my ( $Htmlroot, $Htmldir, $Htmlfile, $Htmlfileurl );
+my ( $Podfile, @Podpath, $Podroot );
+my $Css;
+
+my $Recurse;
+my $Quiet;
+my $HiddenDirs;
+my $Verbose;
+my $Doindex;
+
+my $Backlink;
+my ( $Listlevel, @Listend );
+my $After_Lpar;
+use vars qw($Ignore);    # need to localize it later.
+
+my ( %Items_Named, @Items_Seen );
+my ( $Title,       $Header );
+
+my $Top;
+my $Paragraph;
+
+my %Sections;
+
+# Caches
+my %Pages = ();          # associative array used to find the location
+                         #   of pages referenced by L<> links.
+my %Items = ();          # associative array used to find the location
+                         #   of =item directives referenced by C<> links
+
+my %Local_Items;
+my $Is83;
+
+my $Curdir = File::Spec->curdir;
+
+init_globals();
+
+sub init_globals {
+	$Cachedir = ".";     # The directory to which item and directory
+	                     # caches will be written.
+
+	$Dircache  = "pod2htmd.tmp";
+	$Itemcache = "pod2htmi.tmp";
+
+	@Begin_Stack = ();    # begin/end stack
+
+	@Libpods     = ();    # files to search for links from C<> directives
+	$Htmlroot    = "/";   # http-server base directory from which all
+	                      #   relative paths in $podpath stem.
+	$Htmldir     = "";    # The directory to which the html pages
+	                      # will (eventually) be written.
+	$Htmlfile    = "";    # write to stdout by default
+	$Htmlfileurl = "";    # The url that other files would use to
+	                      # refer to this file.  This is only used
+	                      # to make relative urls that point to
+	                      # other files.
+
+	$Podfile    = "";         # read from stdin by default
+	@Podpath    = ();         # list of directories containing library pods.
+	$Podroot    = $Curdir;    # filesystem base directory from which all
+	                          #   relative paths in $podpath stem.
+	$Css        = '';         # Cascading style sheet
+	$Recurse    = 1;          # recurse on subdirectories in $podpath.
+	$Quiet      = 0;          # not quiet by default
+	$Verbose    = 0;          # not verbose by default
+	$Doindex    = 1;          # non-zero if we should generate an index
+	$Backlink   = '';         # text for "back to top" links
+	$Listlevel  = 0;          # current list depth
+	@Listend    = ();         # the text to use to end the list.
+	$After_Lpar = 0;          # set to true after a par in an =item
+	$Ignore     = 1;          # whether or not to format text.  we don't
+	                          #   format text until we hit our first pod
+	                          #   directive.
+
+	@Items_Seen  = ();        # for multiples of the same item in perlfunc
+	%Items_Named = ();
+	$Header      = 0;         # produce block header/footer
+	$Title       = '';        # title to give the pod(s)
+	$Top         = 1;         # true if we are at the top of the doc.  used
+	                          #   to prevent the first <hr /> directive.
+	$Paragraph   = '';        # which paragraph we're processing (used
+	                          #   for error messages)
+	%Sections    = ();        # sections within this page
+
+	%Local_Items = ();
+	$Is83 = $^O eq 'dos';    # Is it an 8.3 filesystem?
+}
+
+#
+# clean_data: global clean-up of pod data
+#
+sub clean_data($) {
+	my ($dataref) = @_;
+	for my $i ( 0 .. $#{$dataref} ) {
+		${$dataref}[$i] =~ s/\s+\Z//;
+
+		# have a look for all-space lines
+		if ( ${$dataref}[$i] =~ /^\s+$/m and $dataref->[$i] !~ /^\s/ ) {
+			my @chunks = split( /^\s+$/m, ${$dataref}[$i] );
+			splice( @$dataref, $i, 1, @chunks );
+		}
+	}
+}
+
+sub pod2html {
+	local (@ARGV) = @_;
+	local ($/);
+	local $_;
+
+	init_globals();
+
+	$Is83 = 0 if ( defined(&Dos::UseLFN) && Dos::UseLFN() );
+
+	# cache of %Pages and %Items from last time we ran pod2html
+
+	#undef $opt_help if defined $opt_help;
+
+	# parse the command-line parameters
+	parse_command_line();
+
+	# escape the backlink argument (same goes for title but is done later...)
+	$Backlink = html_escape($Backlink) if defined $Backlink;
+
+	# set some variables to their default values if necessary
+	local *POD;
+	unless ( @ARGV && $ARGV[0] ) {
+		$Podfile = "-" unless $Podfile;    # stdin
+		open( POD, "<$Podfile" )
+			|| die "$0: cannot open $Podfile file for input: $!\n";
+	} else {
+		$Podfile = $ARGV[0];               # XXX: might be more filenames
+		*POD     = *ARGV;
+	}
+	$Htmlfile = "-" unless $Htmlfile;      # stdout
+	$Htmlroot = "" if $Htmlroot eq "/";    # so we don't get a //
+	$Htmldir =~ s#/\z##;                   # so we don't get a //
+	if (   $Htmlroot eq ''
+		&& defined($Htmldir)
+		&& $Htmldir ne ''
+		&& substr( $Htmlfile, 0, length($Htmldir) ) eq $Htmldir )
+	{
+
+		# Set the 'base' url for this file, so that we can use it
+		# as the location from which to calculate relative links
+		# to other files. If this is '', then absolute links will
+		# be used throughout.
+		$Htmlfileurl = "$Htmldir/" . substr( $Htmlfile, length($Htmldir) + 1 );
+	}
+
+	# read the pod a paragraph at a time
+	warn "Scanning for sections in input file(s)\n" if $Verbose;
+	$/ = "";
+	my @poddata = <POD>;
+	close(POD);
+
+	# be eol agnostic
+	for (@poddata) {
+		if (/\r/) {
+			if (/\r\n/) {
+				@poddata = map {
+					s/\r\n/\n/g;
+					/\n\n/
+						? map { "$_\n\n" } split /\n\n/
+						: $_
+				} @poddata;
+			} else {
+				@poddata = map {
+					s/\r/\n/g;
+					/\n\n/
+						? map { "$_\n\n" } split /\n\n/
+						: $_
+				} @poddata;
+			}
+			last;
+		}
+	}
+
+	clean_data( \@poddata );
+
+	# scan the pod for =head[1-6] directives and build an index
+	my $index = scan_headings( \%Sections, @poddata );
+
+	unless ($index) {
+		warn "No headings in $Podfile\n" if $Verbose;
+	}
+
+	# open the output file
+	open( HTML, ">$Htmlfile" )
+		|| die "$0: cannot open $Htmlfile file for output: $!\n";
+
+	# put a title in the HTML file if one wasn't specified
+	if ( $Title eq '' ) {
+	TITLE_SEARCH: {
+			for ( my $i = 0 ; $i < @poddata ; $i++ ) {
+				if ( $poddata[$i] =~ /^=head1\s*NAME\b/m ) {
+					for my $para ( @poddata[ $i, $i + 1 ] ) {
+						last TITLE_SEARCH
+							if ($Title) = $para =~ /(\S+\s+-+.*\S)/s;
+					}
+				}
+
+			}
+		}
+	}
+	if ( !$Title and $Podfile =~ /\.pod\z/ ) {
+
+		# probably a split pod so take first =head[12] as title
+		for ( my $i = 0 ; $i < @poddata ; $i++ ) {
+			last if ($Title) = $poddata[$i] =~ /^=head[12]\s*(.*)/;
+		}
+		warn "adopted '$Title' as title for $Podfile\n"
+			if $Verbose and $Title;
+	}
+	if ($Title) {
+		$Title =~ s/\s*\(.*\)//;
+	} else {
+		warn "$0: no title for $Podfile.\n" unless $Quiet;
+		$Podfile =~ /^(.*)(\.[^.\/]+)?\z/s;
+		$Title = ( $Podfile eq "-" ? 'No Title' : $1 );
+		warn "using $Title" if $Verbose;
+	}
+	$Title = html_escape($Title);
+
+	my $csslink   = '';
+	my $bodystyle = ' style="background-color: white"';
+	my $tdstyle   = ' style="background-color: #cccccc"';
+
+	if ($Css) {
+		$csslink = qq(\n<link rel="stylesheet" href="$Css" type="text/css" />);
+		$csslink =~ s,\\,/,g;
+		$csslink =~ s,(/.):,$1|,;
+		$bodystyle = '';
+		$tdstyle   = '';
+	}
+
+	my $block = $Header ? <<END_OF_BLOCK : '';
+<table border="0" width="100%" cellspacing="0" cellpadding="3">
+<tr><td class="block"$tdstyle valign="middle">
+<big><strong><span class="block">&nbsp;$Title</span></strong></big>
+</td></tr>
+</table>
+END_OF_BLOCK
+
+	print HTML <<END_OF_HEAD;
+<?xml version="1.0" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>$Title</title>$csslink
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<link rev="made" href="mailto:$Config{perladmin}" />
+<style type="text/css">
+	.branch {
+		list-style: none;
+	}
+
+	li code {
+		padding-right: 0.5em;
+	}
+
+	.example {
+		width: 98%;
+		padding: 0.5em;
+		float: left;
+		font-family: monospace;
+	}
+
+	.example .line_number {
+		float: left;
+		text-align: right;
+		margin-right: 10px;
+		padding-right: 5px;
+		border-right: 1px solid white;
+	}
+
+	.example .content {
+		float: left;
+	}
+
+	.example .content pre {
+		margin: 0;
+	}
+
+	td > table {
+		margin: 0.5em;
+		border-collapse: collapse;
+	}
+
+	td > table td {
+		border: 1px solid black;
+	}
+
+	.synComment {
+		color: rgb(135,206,235);
+	}
+	.synPreProc {
+		color: rgb(205,92,92);
+	}
+	.synError {
+	}
+	.synConstant {
+		color: #ffa0a0;
+	}
+	.synSpecial {
+		color: rgb(255,222,173);
+	}
+	.synIgnore {
+		color: rgb(102,102,102);
+	}
+	.synNormal {
+		color: rgb(255,255,255);
+		background-color: rgb(51,51,51);
+	}
+	.synType {
+		color: rgb(189,183,107);
+	}
+	.synIdentifier {
+		color: rgb(152,251,152);
+	}
+	.synTodo {
+		color: rgb(255,69,0);
+		background-color: rgb(238,238,0);
+	}
+	.synStatement {
+		color: rgb(240,230,140);
+	}
+
+</style>
+</head>
+
+<body$bodystyle>
+$block
+END_OF_HEAD
+
+	# load/reload/validate/cache %Pages and %Items
+	get_cache( $Dircache, $Itemcache, \@Podpath, $Podroot, $Recurse );
+
+	# scan the pod for =item directives
+	scan_items( \%Local_Items, "", @poddata );
+
+	# put an index at the top of the file.  note, if $Doindex is 0 we
+	# still generate an index, but surround it with an html comment.
+	# that way some other program can extract it if desired.
+	$index =~ s/--+/-/g;
+
+	my $hr = ( $Doindex and $index ) ? qq(<hr />) : "";
+
+	unless ($Doindex) {
+		$index = qq(<!--\n$index\n-->\n);
+	}
+
+	print HTML << "END_OF_INDEX";
+
+<!-- INDEX BEGIN -->
+<div>
+<p><a name=\"__index__\"></a></p>
+$index
+$hr
+</div>
+<!-- INDEX END -->
+
+END_OF_INDEX
+
+	# now convert this file
+	my $after_item;    # set to true after an =item
+	my $need_dd = 0;
+	warn "Converting input file $Podfile\n" if $Verbose;
+	foreach my $i ( 0 .. $#poddata ) {
+		$_         = $poddata[$i];
+		$Paragraph = $i + 1;
+		if (/^(=.*)/s) {    # is it a pod directive?
+			$Ignore     = 0;
+			$after_item = 0;
+			$need_dd    = 0;
+			$_          = $1;
+			if (/^=begin\s+(\S+)\s*(.*)/si) {    # =begin
+				process_begin( $1, $2 );
+			} elsif (/^=end\s+(\S+)\s*(.*)/si) {    # =end
+				process_end( $1, $2 );
+			} elsif (/^=cut/) {                     # =cut
+				process_cut();
+			} elsif (/^=pod/) {                     # =pod
+				process_pod();
+			} else {
+				next if @Begin_Stack && $Begin_Stack[-1] ne 'html';
+
+				if (/^=(head[1-6])\s+(.*\S)/s) {    # =head[1-6] heading
+					process_head( $1, $2, $Doindex && $index );
+				} elsif (/^=item\s*(.*\S)?/sm) {    # =item text
+					$need_dd    = process_item($1);
+					$after_item = 1;
+				} elsif (/^=over\s*(.*)/) {         # =over N
+					process_over();
+				} elsif (/^=back/) {                # =back
+					process_back($need_dd);
+				} elsif (/^=for\s+(\S+)\s*(.*)/si) {    # =for
+					process_for( $1, $2 );
+				} else {
+					/^=(\S*)\s*/;
+					warn "$0: $Podfile: unknown pod directive '$1' in "
+						. "paragraph $Paragraph.  ignoring.\n"
+						unless $Quiet;
+				}
+			}
+			$Top = 0;
+		} else {
+			next if $Ignore;
+			next if @Begin_Stack && $Begin_Stack[-1] ne 'html';
+			print HTML and next if @Begin_Stack && $Begin_Stack[-1] eq 'html';
+			print HTML "<dd>\n" if $need_dd;
+			my $text = $_;
+			if ( $text =~ /\A\s+/ ) {
+				process_pre( \$text );
+				print HTML "<pre>\n$text</pre>\n";
+
+			} else {
+				process_text( \$text );
+
+				# experimental: check for a paragraph where all lines
+				# have some ...\t...\t...\n pattern
+				if ( $text =~ /\t/ ) {
+					my @lines = split( "\n", $text );
+					if ( @lines > 1 ) {
+						my $all = 2;
+						foreach my $line (@lines) {
+							if ( $line =~ /\S/ && $line !~ /\t/ ) {
+								$all--;
+								last if $all == 0;
+							}
+						}
+						if ( $all > 0 ) {
+							$text =~ s/\t+/<td>/g;
+							$text =~ s/^/<tr><td>/gm;
+							$text =
+								  '<table cellspacing="0" cellpadding="0">'
+								. $text
+								. '</table>';
+						}
+					}
+				}
+				## end of experimental
+
+				if ($after_item) {
+					$After_Lpar = 1;
+				}
+				print HTML "<p>$text</p>\n";
+			}
+			print HTML "</dd>\n" if $need_dd;
+			$after_item = 0;
+		}
+	}
+
+	# finish off any pending directives
+	finish_list();
+
+	# link to page index
+	print HTML "<p><a href=\"#__index__\"><small>$Backlink</small></a></p>\n"
+		if $Doindex
+			and $index
+			and $Backlink;
+
+	print HTML <<END_OF_TAIL;
+$block
+</body>
+
+</html>
+END_OF_TAIL
+
+	# close the html file
+	close(HTML);
+
+	warn "Finished\n" if $Verbose;
+}
+
+##############################################################################
+
+sub usage {
+	my $podfile = shift;
+	warn "$0: $podfile: @_\n" if @_;
+	die <<END_OF_USAGE;
+Usage:  $0 --help --htmlroot=<name> --infile=<name> --outfile=<name>
+           --podpath=<name>:...:<name> --podroot=<name>
+           --libpods=<name>:...:<name> --recurse --verbose --index
+           --netscape --norecurse --noindex --cachedir=<name>
+
+  --backlink     - set text for "back to top" links (default: none).
+  --cachedir     - directory for the item and directory cache files.
+  --css          - stylesheet URL
+  --flush        - flushes the item and directory caches.
+  --[no]header   - produce block header/footer (default is no headers).
+  --help         - prints this message.
+  --hiddendirs   - search hidden directories in podpath
+  --htmldir      - directory for resulting HTML files.
+  --htmlroot     - http-server base directory from which all relative paths
+                   in podpath stem (default is /).
+  --[no]index    - generate an index at the top of the resulting html
+                   (default behaviour).
+  --infile       - filename for the pod to convert (input taken from stdin
+                   by default).
+  --libpods      - colon-separated list of pages to search for =item pod
+                   directives in as targets of C<> and implicit links (empty
+                   by default).  note, these are not filenames, but rather
+                   page names like those that appear in L<> links.
+  --outfile      - filename for the resulting html file (output sent to
+                   stdout by default).
+  --podpath      - colon-separated list of directories containing library
+                   pods (empty by default).
+  --podroot      - filesystem base directory from which all relative paths
+                   in podpath stem (default is .).
+  --[no]quiet    - suppress some benign warning messages (default is off).
+  --[no]recurse  - recurse on those subdirectories listed in podpath
+                   (default behaviour).
+  --title        - title that will appear in resulting html file.
+  --[no]verbose  - self-explanatory (off by default).
+  --[no]netscape - deprecated, has no effect. for backwards compatibility only.
+
+END_OF_USAGE
+
+}
+
+sub parse_command_line {
+	my (
+		$opt_backlink, $opt_cachedir, $opt_css,     $opt_flush,
+		$opt_header,   $opt_help,     $opt_htmldir, $opt_htmlroot,
+		$opt_index,    $opt_infile,   $opt_libpods, $opt_netscape,
+		$opt_outfile,  $opt_podpath,  $opt_podroot, $opt_quiet,
+		$opt_recurse,  $opt_title,    $opt_verbose, $opt_hiddendirs
+	);
+
+	unshift @ARGV, split ' ', $Config{pod2html} if $Config{pod2html};
+	my $result = GetOptions(
+		'backlink=s'  => \$opt_backlink,
+		'cachedir=s'  => \$opt_cachedir,
+		'css=s'       => \$opt_css,
+		'flush'       => \$opt_flush,
+		'header!'     => \$opt_header,
+		'help'        => \$opt_help,
+		'hiddendirs!' => \$opt_hiddendirs,
+		'htmldir=s'   => \$opt_htmldir,
+		'htmlroot=s'  => \$opt_htmlroot,
+		'index!'      => \$opt_index,
+		'infile=s'    => \$opt_infile,
+		'libpods=s'   => \$opt_libpods,
+		'netscape!'   => \$opt_netscape,
+		'outfile=s'   => \$opt_outfile,
+		'podpath=s'   => \$opt_podpath,
+		'podroot=s'   => \$opt_podroot,
+		'quiet!'      => \$opt_quiet,
+		'recurse!'    => \$opt_recurse,
+		'title=s'     => \$opt_title,
+		'verbose!'    => \$opt_verbose,
+	);
+	usage( "-", "invalid parameters" ) if not $result;
+
+	usage("-") if defined $opt_help;    # see if the user asked for help
+	$opt_help = "";                     # just to make -w shut-up.
+
+	@Podpath = split( ":", $opt_podpath ) if defined $opt_podpath;
+	@Libpods = split( ":", $opt_libpods ) if defined $opt_libpods;
+
+	$Backlink   = $opt_backlink   if defined $opt_backlink;
+	$Cachedir   = $opt_cachedir   if defined $opt_cachedir;
+	$Css        = $opt_css        if defined $opt_css;
+	$Header     = $opt_header     if defined $opt_header;
+	$Htmldir    = $opt_htmldir    if defined $opt_htmldir;
+	$Htmlroot   = $opt_htmlroot   if defined $opt_htmlroot;
+	$Doindex    = $opt_index      if defined $opt_index;
+	$Podfile    = $opt_infile     if defined $opt_infile;
+	$HiddenDirs = $opt_hiddendirs if defined $opt_hiddendirs;
+	$Htmlfile   = $opt_outfile    if defined $opt_outfile;
+	$Podroot    = $opt_podroot    if defined $opt_podroot;
+	$Quiet      = $opt_quiet      if defined $opt_quiet;
+	$Recurse    = $opt_recurse    if defined $opt_recurse;
+	$Title      = $opt_title      if defined $opt_title;
+	$Verbose    = $opt_verbose    if defined $opt_verbose;
+
+	warn "Flushing item and directory caches\n"
+		if $opt_verbose && defined $opt_flush;
+	$Dircache  = "$Cachedir/pod2htmd.tmp";
+	$Itemcache = "$Cachedir/pod2htmi.tmp";
+	if ( defined $opt_flush ) {
+		1 while unlink( $Dircache, $Itemcache );
+	}
+}
+
+my $Saved_Cache_Key;
+
+sub get_cache {
+	my ( $dircache, $itemcache, $podpath, $podroot, $recurse ) = @_;
+	my @cache_key_args = @_;
+
+	# A first-level cache:
+	# Don't bother reading the cache files if they still apply
+	# and haven't changed since we last read them.
+
+	my $this_cache_key = cache_key(@cache_key_args);
+
+	return if $Saved_Cache_Key and $this_cache_key eq $Saved_Cache_Key;
+
+	# load the cache of %Pages and %Items if possible.  $tests will be
+	# non-zero if successful.
+	my $tests = 0;
+	if ( -f $dircache && -f $itemcache ) {
+		warn "scanning for item cache\n" if $Verbose;
+		$tests = load_cache( $dircache, $itemcache, $podpath, $podroot );
+	}
+
+	# if we didn't succeed in loading the cache then we must (re)build
+	#  %Pages and %Items.
+	if ( !$tests ) {
+		warn "scanning directories in pod-path\n" if $Verbose;
+		scan_podpath( $podroot, $recurse, 0 );
+	}
+	$Saved_Cache_Key = cache_key(@cache_key_args);
+}
+
+sub cache_key {
+	my ( $dircache, $itemcache, $podpath, $podroot, $recurse ) = @_;
+	return join( '!',
+		$dircache, $itemcache, $recurse, @$podpath, $podroot, stat($dircache),
+		stat($itemcache) );
+}
+
+#
+# load_cache - tries to find if the caches stored in $dircache and $itemcache
+#  are valid caches of %Pages and %Items.  if they are valid then it loads
+#  them and returns a non-zero value.
+#
+sub load_cache {
+	my ( $dircache, $itemcache, $podpath, $podroot ) = @_;
+	my ($tests);
+	local $_;
+
+	$tests = 0;
+
+	open( CACHE, "<$itemcache" )
+		|| die "$0: error opening $itemcache for reading: $!\n";
+	$/ = "\n";
+
+	# is it the same podpath?
+	$_ = <CACHE>;
+	chomp($_);
+	$tests++ if ( join( ":", @$podpath ) eq $_ );
+
+	# is it the same podroot?
+	$_ = <CACHE>;
+	chomp($_);
+	$tests++ if ( $podroot eq $_ );
+
+	# load the cache if its good
+	if ( $tests != 2 ) {
+		close(CACHE);
+		return 0;
+	}
+
+	warn "loading item cache\n" if $Verbose;
+	while (<CACHE>) {
+		/(.*?) (.*)$/;
+		$Items{$1} = $2;
+	}
+	close(CACHE);
+
+	warn "scanning for directory cache\n" if $Verbose;
+	open( CACHE, "<$dircache" )
+		|| die "$0: error opening $dircache for reading: $!\n";
+	$/     = "\n";
+	$tests = 0;
+
+	# is it the same podpath?
+	$_ = <CACHE>;
+	chomp($_);
+	$tests++ if ( join( ":", @$podpath ) eq $_ );
+
+	# is it the same podroot?
+	$_ = <CACHE>;
+	chomp($_);
+	$tests++ if ( $podroot eq $_ );
+
+	# load the cache if its good
+	if ( $tests != 2 ) {
+		close(CACHE);
+		return 0;
+	}
+
+	warn "loading directory cache\n" if $Verbose;
+	while (<CACHE>) {
+		/(.*?) (.*)$/;
+		$Pages{$1} = $2;
+	}
+
+	close(CACHE);
+
+	return 1;
+}
+
+#
+# scan_podpath - scans the directories specified in @podpath for directories,
+#  .pod files, and .pm files.  it also scans the pod files specified in
+#  @Libpods for =item directives.
+#
+sub scan_podpath {
+	my ( $podroot, $recurse, $append ) = @_;
+	my ( $pwd, $dir );
+	my ( $libpod, $dirname, $pod, @files, @poddata );
+
+	unless ($append) {
+		%Items = ();
+		%Pages = ();
+	}
+
+	# scan each directory listed in @Podpath
+	$pwd = getcwd();
+	chdir($podroot)
+		|| die "$0: error changing to directory $podroot: $!\n";
+	foreach $dir (@Podpath) {
+		scan_dir( $dir, $recurse );
+	}
+
+	# scan the pods listed in @Libpods for =item directives
+	foreach $libpod (@Libpods) {
+
+		# if the page isn't defined then we won't know where to find it
+		# on the system.
+		next unless defined $Pages{$libpod} && $Pages{$libpod};
+
+		# if there is a directory then use the .pod and .pm files within it.
+		# NOTE: Only finds the first so-named directory in the tree.
+		#	if ($Pages{$libpod} =~ /([^:]*[^(\.pod|\.pm)]):/) {
+		if ( $Pages{$libpod} =~ /([^:]*(?<!\.pod)(?<!\.pm)):/ ) {
+
+			#  find all the .pod and .pm files within the directory
+			$dirname = $1;
+			opendir( DIR, $dirname )
+				|| die "$0: error opening directory $dirname: $!\n";
+			@files = grep( /(\.pod|\.pm)\z/ && !-d $_, readdir(DIR) );
+			closedir(DIR);
+
+			# scan each .pod and .pm file for =item directives
+			foreach $pod (@files) {
+				open( POD, "<$dirname/$pod" )
+					|| die "$0: error opening $dirname/$pod for input: $!\n";
+				@poddata = <POD>;
+				close(POD);
+				clean_data( \@poddata );
+
+				scan_items( \%Items, "$dirname/$pod", @poddata );
+			}
+
+			# use the names of files as =item directives too.
+### Don't think this should be done this way - confuses issues.(WL)
+###	    foreach $pod (@files) {
+###		$pod =~ /^(.*)(\.pod|\.pm)$/;
+###		$Items{$1} = "$dirname/$1.html" if $1;
+###	    }
+		} elsif ( $Pages{$libpod} =~ /([^:]*\.pod):/
+			|| $Pages{$libpod} =~ /([^:]*\.pm):/ )
+		{
+
+			# scan the .pod or .pm file for =item directives
+			$pod = $1;
+			open( POD, "<$pod" )
+				|| die "$0: error opening $pod for input: $!\n";
+			@poddata = <POD>;
+			close(POD);
+			clean_data( \@poddata );
+
+			scan_items( \%Items, "$pod", @poddata );
+		} else {
+			warn "$0: shouldn't be here (line " . __LINE__ . "\n" unless $Quiet;
+		}
+	}
+	@poddata = ();    # clean-up a bit
+
+	chdir($pwd)
+		|| die "$0: error changing to directory $pwd: $!\n";
+
+	# cache the item list for later use
+	warn "caching items for later use\n" if $Verbose;
+	open( CACHE, ">$Itemcache" )
+		|| die "$0: error open $Itemcache for writing: $!\n";
+
+	print CACHE join( ":", @Podpath ) . "\n$podroot\n";
+	foreach my $key ( keys %Items ) {
+		print CACHE "$key $Items{$key}\n";
+	}
+
+	close(CACHE);
+
+	# cache the directory list for later use
+	warn "caching directories for later use\n" if $Verbose;
+	open( CACHE, ">$Dircache" )
+		|| die "$0: error open $Dircache for writing: $!\n";
+
+	print CACHE join( ":", @Podpath ) . "\n$podroot\n";
+	foreach my $key ( keys %Pages ) {
+		print CACHE "$key $Pages{$key}\n";
+	}
+
+	close(CACHE);
+}
+
+#
+# scan_dir - scans the directory specified in $dir for subdirectories, .pod
+#  files, and .pm files.  notes those that it finds.  this information will
+#  be used later in order to figure out where the pages specified in L<>
+#  links are on the filesystem.
+#
+sub scan_dir {
+	my ( $dir, $recurse ) = @_;
+	my ( $t, @subdirs, @pods, $pod, $dirname, @dirs );
+	local $_;
+
+	@subdirs = ();
+	@pods    = ();
+
+	opendir( DIR, $dir )
+		|| die "$0: error opening directory $dir: $!\n";
+	while ( defined( $_ = readdir(DIR) ) ) {
+		if (   -d "$dir/$_"
+			&& $_ ne "."
+			&& $_ ne ".."
+			&& ( $HiddenDirs || !/^\./ ) )
+		{    # directory
+			$Pages{$_} = "" unless defined $Pages{$_};
+			$Pages{$_} .= "$dir/$_:";
+			push( @subdirs, $_ );
+		} elsif (/\.pod\z/) {    # .pod
+			s/\.pod\z//;
+			$Pages{$_} = "" unless defined $Pages{$_};
+			$Pages{$_} .= "$dir/$_.pod:";
+			push( @pods, "$dir/$_.pod" );
+		} elsif (/\.html\z/) {    # .html
+			s/\.html\z//;
+			$Pages{$_} = "" unless defined $Pages{$_};
+			$Pages{$_} .= "$dir/$_.pod:";
+		} elsif (/\.pm\z/) {      # .pm
+			s/\.pm\z//;
+			$Pages{$_} = "" unless defined $Pages{$_};
+			$Pages{$_} .= "$dir/$_.pm:";
+			push( @pods, "$dir/$_.pm" );
+		} elsif ( -T "$dir/$_" ) {    # script(?)
+			local *F;
+			if ( open( F, "$dir/$_" ) ) {
+				my $line;
+				while ( defined( $line = <F> ) ) {
+					if ( $line =~ /^=(?:pod|head1)/ ) {
+						$Pages{$_} = "" unless defined $Pages{$_};
+						$Pages{$_} .= "$dir/$_.pod:";
+						last;
+					}
+				}
+				close(F);
+			}
+		}
+	}
+	closedir(DIR);
+
+	# recurse on the subdirectories if necessary
+	if ($recurse) {
+		foreach my $subdir (@subdirs) {
+			scan_dir( "$dir/$subdir", $recurse );
+		}
+	}
+}
+
+#
+# scan_headings - scan a pod file for head[1-6] tags, note the tags, and
+#  build an index.
+#
+sub scan_headings {
+	my ( $sections, @data ) = @_;
+	my ( $tag, $which_head, $otitle, $listdepth, $index );
+
+	local $Ignore = 0;
+
+	$listdepth = 0;
+	$index     = "";
+
+	# scan for =head directives, note their name, and build an index
+	#  pointing to each of them.
+	foreach my $line (@data) {
+		if ( $line =~ /^=(head)([1-6])\s+(.*)/ ) {
+			( $tag, $which_head, $otitle ) = ( $1, $2, $3 );
+
+			my $title = depod($otitle);
+			my $name  = anchorify($title);
+			$$sections{$name} = 1;
+			$title = process_text( \$otitle );
+
+			while ( $which_head != $listdepth ) {
+				if ( $which_head > $listdepth ) {
+					$index .= "\n"
+						. ( "\t" x ($listdepth) )
+						. ( $listdepth > 0 ? qq{<li class="branch">\n}
+							. "\t"x($listdepth + 1): "" )
+						. "<ul>";
+					$listdepth++;
+				} elsif ( $which_head < $listdepth ) {
+					$listdepth--;
+					$index .= "\n"
+						. ( "\t" x $listdepth ) 
+						. ( $listdepth > 0 ? "\t" : "" )
+						. "</ul>"
+						. ( $listdepth >= 0 ? "\n" . ("\t"x$listdepth)
+							. "</li>" : "" )
+						. "\n";
+				}
+			}
+
+			$index .= "\n"
+				. ( "\t" x $listdepth ) . "<li>"
+				. "<a href=\"#"
+				. $name . "\">"
+				. $title
+				. "</a></li>";
+		}
+	}
+
+	# finish off the lists
+	while ( $listdepth-- ) {
+		$index .= "\n" . ( "\t" x $listdepth )
+			. ($listdepth > 0 ? "\t" : "")
+			."</ul>\n"
+			. ($listdepth > 0 ? ("\t" x $listdepth) . "</li>" : "" );
+	}
+
+	# get rid of bogus lists
+	$index =~ s,\t*<ul>\s*</ul>\n,,g;
+
+	return $index;
+}
+
+#
+# scan_items - scans the pod specified by $pod for =item directives.  we
+#  will use this information later on in resolving C<> links.
+#
+sub scan_items {
+	my ( $itemref, $pod, @poddata ) = @_;
+	my ( $i, $item );
+	local $_;
+
+	$pod =~ s/\.pod\z//;
+	$pod .= ".html" if $pod;
+
+	foreach $i ( 0 .. $#poddata ) {
+		my $txt = depod( $poddata[$i] );
+
+		# figure out what kind of item it is.
+		# Build string for referencing this item.
+		if ( $txt =~ /\A=item\s+\*\s*(.*)\Z/s ) {    # bullet
+			next unless $1;
+			$item = $1;
+		} elsif ( $txt =~ /\A=item\s+(?>\d+\.?)\s*(.*)\Z/s ) {   # numbered list
+			$item = $1;
+		} elsif ( $txt =~ /\A=item\s+(.*)\Z/s ) {                # plain item
+			$item = $1;
+		} else {
+			next;
+		}
+		my $fid = fragment_id($item);
+		$$itemref{$fid} = "$pod" if $fid;
+	}
+}
+
+#
+# process_head - convert a pod head[1-6] tag and convert it to HTML format.
+#
+sub process_head {
+	my ( $tag, $heading, $hasindex ) = @_;
+
+	# figure out the level of the =head
+	$tag =~ /head([1-6])/;
+	my $level = $1;
+
+	if ($Listlevel) {
+		warn
+"$0: $Podfile: unterminated list at =head in paragraph $Paragraph.  ignoring.\n"
+			unless $Quiet;
+		while ($Listlevel) {
+			process_back();
+		}
+	}
+
+	print HTML "<p>\n";
+	if ( $level == 1 && !$Top ) {
+		print HTML "<a href=\"#__index__\"><small>$Backlink</small></a>\n"
+			if $hasindex and $Backlink;
+		print HTML "</p>\n<hr />\n";
+	} else {
+		print HTML "</p>\n";
+	}
+
+	my $name    = anchorify( depod($heading) );
+	my $convert = process_text( \$heading );
+	$convert =~ s{</?a[^>]+>}{}g;
+	print HTML "<h$level><a name=\"$name\" />$convert</h$level>\n";
+}
+
+#
+# emit_item_tag - print an =item's text
+# Note: The global $EmittedItem is used for inhibiting self-references.
+#
+my $EmittedItem;
+
+sub emit_item_tag($$$) {
+	my ( $otext, $text, $compact ) = @_;
+	my $item = fragment_id( depod($text), -generate );
+	Carp::confess( "Undefined fragment '$text' ("
+			. depod($text)
+			. ") from fragment_id() in emit_item_tag() in $Podfile" )
+		if !defined $item;
+	$EmittedItem = $item;
+	### print STDERR "emit_item_tag=$item ($text)\n";
+
+	print HTML '<strong>';
+	if ( $Items_Named{$item}++ ) {
+		print HTML process_text( \$otext );
+	} else {
+		my $name = $item;
+		$name = anchorify($name);
+		print HTML 
+#qq{<a name="$name" class="item">},
+		process_text( \$otext ),
+		#	'</a>'
+		;
+	}
+	print HTML "</strong>\n";
+	undef($EmittedItem);
+}
+
+sub emit_li {
+	my ($tag) = @_;
+	if ( $Items_Seen[$Listlevel]++ == 0 ) {
+		push( @Listend, "</$tag>" );
+		print HTML "<$tag>\n";
+	}
+	my $emitted = $tag eq 'dl' ? 'dt' : 'li';
+	print HTML "<$emitted>";
+	return $emitted;
+}
+
+#
+# process_item - convert a pod item tag and convert it to HTML format.
+#
+sub process_item {
+	my ($otext) = @_;
+	my $need_dd = 0;    # set to 1 if we need a <dd></dd> after an item
+
+	# lots of documents start a list without doing an =over.  this is
+	# bad!  but, the proper thing to do seems to be to just assume
+	# they did do an =over.  so warn them once and then continue.
+	if ( $Listlevel == 0 ) {
+		warn
+"$0: $Podfile: unexpected =item directive in paragraph $Paragraph.  ignoring.\n"
+			unless $Quiet;
+		process_over();
+	}
+
+	# formatting: insert a paragraph if preceding item has >1 paragraph
+	if ($After_Lpar) {
+		print HTML $need_dd ? "</dd>\n" : "</li>\n" if $After_Lpar;
+		$After_Lpar = 0;
+	}
+
+	# remove formatting instructions from the text
+	my $text = depod($otext);
+
+	my $emitted;    # the tag actually emitted, used for closing
+
+	# all the list variants:
+	if ( $text =~ /\A\*/ ) {    # bullet
+		$emitted = emit_li('ul');
+		if ( $text =~ /\A\*\s+(\S.*)\Z/s ) {    # with additional text
+			my $tag = $1;
+			$otext =~ s/\A\*\s+//;
+			emit_item_tag( $otext, $tag, 1 );
+		}
+		print HTML "</li>"
+	} elsif ( $text =~ /\A\d+/ ) {    # numbered list
+		$emitted = emit_li('ol');
+		if ( $text =~ /\A(?>\d+\.?)\s*(\S.*)\Z/s ) {    # with additional text
+			my $tag = $1;
+			$otext =~ s/\A\d+\.?\s*//;
+			emit_item_tag( $otext, $tag, 1 );
+		}
+		print HTML "</li>";
+	} else {    # definition list
+		$emitted = emit_li('dl');
+		if ( $text =~ /\A(.+)\Z/s ) {    # should have text
+			emit_item_tag( $otext, $text, 1 );
+		}
+		$need_dd = 1;
+	}
+	print HTML "\n";
+	return $need_dd;
+}
+
+#
+# process_over - process a pod over tag and start a corresponding HTML list.
+#
+sub process_over {
+
+	# start a new list
+	$Listlevel++;
+	push( @Items_Seen, 0 );
+	$After_Lpar = 0;
+}
+
+#
+# process_back - process a pod back tag and convert it to HTML format.
+#
+sub process_back {
+	my $need_dd = shift;
+	if ( $Listlevel == 0 ) {
+		warn
+"$0: $Podfile: unexpected =back directive in paragraph $Paragraph.  ignoring.\n"
+			unless $Quiet;
+		return;
+	}
+
+	# close off the list.  note, I check to see if $Listend[$Listlevel] is
+	# defined because an =item directive may have never appeared and thus
+	# $Listend[$Listlevel] may have never been initialized.
+	$Listlevel--;
+	if ( defined $Listend[$Listlevel] ) {
+		print HTML $need_dd ? "</dd>\n" : "</li>\n" if $After_Lpar;
+		print HTML $Listend[$Listlevel];
+		print HTML "\n";
+		pop(@Listend);
+	}
+	$After_Lpar = 0;
+
+	# clean up item count
+	pop(@Items_Seen);
+}
+
+#
+# process_cut - process a pod cut tag, thus start ignoring pod directives.
+#
+sub process_cut {
+	$Ignore = 1;
+}
+
+#
+# process_pod - process a pod tag, thus stop ignoring pod directives
+# until we see a corresponding cut.
+#
+sub process_pod {
+
+	# no need to set $Ignore to 0 cause the main loop did it
+}
+
+#
+# process_for - process a =for pod tag.  if it's for html, spit
+# it out verbatim, if illustration, center it, otherwise ignore it.
+#
+sub process_for {
+	my ( $whom, $text ) = @_;
+	if ( $whom =~ /^(pod2)?html$/i ) {
+		print HTML $text;
+	} elsif ( $whom =~ /^illustration$/i ) {
+		1 while chomp $text;
+		for my $ext (qw[.png .gif .jpeg .jpg .tga .pcl .bmp]) {
+			$text .= $ext, last if -r "$text$ext";
+		}
+		print HTML
+qq{<p align="center"><img src="$text" alt="$text illustration" /></p>};
+	}
+}
+
+#
+# process_begin - process a =begin pod tag.  this pushes
+# whom we're beginning on the begin stack.  if there's a
+# begin stack, we only print if it us.
+#
+sub process_begin {
+	my ( $whom, $text ) = @_;
+	$whom = lc($whom);
+	push( @Begin_Stack, $whom );
+	if ( $whom =~ /^(pod2)?html$/ ) {
+		print HTML $text if $text;
+	}
+}
+
+#
+# process_end - process a =end pod tag.  pop the
+# begin stack.  die if we're mismatched.
+#
+sub process_end {
+	my ( $whom, $text ) = @_;
+	$whom = lc($whom);
+	if ( !defined $Begin_Stack[-1] or $Begin_Stack[-1] ne $whom ) {
+		Carp::confess(
+			"Unmatched begin/end at chunk $Paragraph in pod $Podfile\n");
+	}
+	pop(@Begin_Stack);
+}
+
+#
+# process_pre - indented paragraph, made into <pre></pre>
+#
+sub process_pre {
+	my ($text) = @_;
+	my ($rest);
+	return if $Ignore;
+
+	$rest = $$text;
+
+	# insert spaces in place of tabs
+	$rest =~ s#(.+)#
+	    my $line = $1;
+            1 while $line =~ s/(\t+)/' ' x ((length($1) * 8) - $-[0] % 8)/e;
+	    $line;
+	#eg;
+
+	# convert some special chars to HTML escapes
+	$rest = html_escape($rest);
+
+	# try and create links for all occurrences of perl.* within
+	# the preformatted text.
+	$rest =~ s{
+	         (\s*)(perl\w+)
+	      }{
+		 if ( defined $Pages{$2} ){	# is a link
+		     qq($1<a href="$Htmlroot/$Pages{$2}">$2</a>);
+		 } elsif (defined $Pages{dosify($2)}) {	# is a link
+		     qq($1<a href="$Htmlroot/$Pages{dosify($2)}">$2</a>);
+		 } else {
+		     "$1$2";
+		 }
+	      }xeg;
+	$rest =~ s{
+		 (<a\ href="?) ([^>:]*:)? ([^>:]*) \.pod: ([^>:]*:)?
+               }{
+                  my $url ;
+                  if ( $Htmlfileurl ne '' ){
+		     # Here, we take advantage of the knowledge
+		     # that $Htmlfileurl ne '' implies $Htmlroot eq ''.
+		     # Since $Htmlroot eq '', we need to prepend $Htmldir
+		     # on the fron of the link to get the absolute path
+		     # of the link's target. We check for a leading '/'
+		     # to avoid corrupting links that are #, file:, etc.
+		     my $old_url = $3 ;
+		     $old_url = "$Htmldir$old_url" if $old_url =~ m{^\/};
+ 		     $url = relativize_url( "$old_url.html", $Htmlfileurl );
+	          } else {
+		     $url = "$3.html" ;
+		  }
+		  "$1$url" ;
+	       }xeg;
+
+	# Look for embedded URLs and make them into links.  We don't
+	# relativize them since they are best left as the author intended.
+
+	my $urls = '(' . join(
+		'|', qw{
+			http
+			telnet
+			mailto
+			news
+			gopher
+			file
+			wais
+			ftp
+			}
+	) . ')';
+
+	my $ltrs = '\w';
+	my $gunk = '/#~:.?+=&%@!\-';
+	my $punc = '.:!?\-;';
+	my $any  = "${ltrs}${gunk}${punc}";
+
+	$rest =~ s{
+	\b			# start at word boundary
+	(			# begin $1  {
+	    $urls :		# need resource and a colon
+	    (?!:)		# Ignore File::, among others.
+	    [$any] +?		# followed by one or more of any valid
+				#   character, but be conservative and
+				#   take only what you need to....
+	)			# end   $1  }
+	(?=
+	    &quot; &gt;		# maybe pre-quoted '<a href="...">'
+	|			# or:
+	    [$punc]*		# 0 or more punctuation
+	    (?:			#   followed
+		[^$any]		#   by a non-url char
+	    |			#   or
+		$		#   end of the string
+	    )			#
+	|			# or else
+	    $			#   then end of the string
+        )
+      }{<a href="$1">$1</a>}igox;
+
+	# text should be as it is (verbatim)
+	$$text = $rest;
+}
+
+#
+# pure text processing
+#
+# pure_text/inIS_text: differ with respect to automatic C<> recognition.
+# we don't want this to happen within IS
+#
+sub pure_text($) {
+	my $text = shift();
+	process_puretext( $text, 1 );
+}
+
+sub inIS_text($) {
+	my $text = shift();
+	process_puretext( $text, 0 );
+}
+
+#
+# process_puretext - process pure text (without pod-escapes) converting
+#  double-quotes and handling implicit C<> links.
+#
+sub process_puretext {
+	my ( $text, $notinIS ) = @_;
+
+	## Guessing at func() or [\$\@%&]*var references in plain text is destined
+	## to produce some strange looking ref's. uncomment to disable:
+	## $notinIS = 0;
+
+	my ( @words, $lead, $trail );
+
+	# keep track of leading and trailing white-space
+	$lead  = ( $text =~ s/\A(\s+)//s ? $1 : "" );
+	$trail = ( $text =~ s/(\s+)\Z//s ? $1 : "" );
+
+	# split at space/non-space boundaries
+	@words = split( /(?<=\s)(?=\S)|(?<=\S)(?=\s)/, $text );
+
+	# process each word individually
+	foreach my $word (@words) {
+
+		# skip space runs
+		next if $word =~ /^\s*$/;
+
+		# see if we can infer a link or a function call
+		#
+		# NOTE: This is a word based search, it won't automatically
+		# mark "substr($var, 1, 2)" because the 1st word would be "substr($var"
+		# User has to enclose those with proper C<>
+
+		if (
+			   $notinIS
+			&& $word =~ m/
+		^([a-z_]{2,})                 # The function name
+		\(
+		    ([0-9][a-z]*              # Manual page(1) or page(1M)
+		    |[^)]*[\$\@\%][^)]+       # ($foo), (1, @foo), (%hash)
+		    |                         # ()
+		    )
+		\)
+		([.,;]?)$                     # a possible punctuation follows
+	    /xi
+			)
+		{
+
+			# has parenthesis so should have been a C<> ref
+			## try for a pagename (perlXXX(1))?
+			my ( $func, $args, $rest ) = ( $1, $2, $3 || '' );
+			if ( $args =~ /^\d+$/ ) {
+				my $url = page_sect( $word, '' );
+				if ( defined $url ) {
+					$word =
+qq(<a href="$url" class="man">the $word manpage</a>$rest);
+					next;
+				}
+			}
+			## try function name for a link, append tt'ed argument list
+			$word = emit_C( $func, '', "($args)" ) . $rest;
+
+#### disabled. either all (including $\W, $\w+{.*} etc.) or nothing.
+##      } elsif( $notinIS && $word =~ /^[\$\@%&*]+\w+$/) {
+##	    # perl variables, should be a C<> ref
+##	    $word = emit_C( $word );
+
+		} elsif ( $word =~ m,^\w+://\w, ) {
+
+			# looks like a URL
+			# Don't relativize it: leave it as the author intended
+			$word = qq(<a href="$word">$word</a>);
+		} elsif ( $word =~ /[\w.-]+\@[\w-]+\.\w/ ) {
+
+			# looks like an e-mail address
+			my ( $w1, $w2, $w3 ) = ( "", $word, "" );
+			( $w1, $w2, $w3 ) = ( "(", $1, ")$2" ) if $word =~ /^\((.*?)\)(,?)/;
+			( $w1, $w2, $w3 ) = ( "&lt;", $1, "&gt;$2" )
+				if $word =~ /^<(.*?)>(,?)/;
+			$word = qq($w1<a href="mailto:$w2">$w2</a>$w3);
+		} else {
+			$word = html_escape($word) if $word =~ /["&<>]/;
+		}
+	}
+
+	# put everything back together
+	return $lead . join( '', @words ) . $trail;
+}
+
+#
+# process_text - handles plaintext that appears in the input pod file.
+# there may be pod commands embedded within the text so those must be
+# converted to html commands.
+#
+
+sub process_text1($$;$$);
+sub pattern ($) { $_[0] ? '\s+' . ( '>' x ( $_[0] + 1 ) ) : '>' }
+sub closing ($) { local ($_) = shift; ( defined && s/\s+\z// ) ? length : 0 }
+
+sub process_text {
+	return if $Ignore;
+	my ($tref) = @_;
+	my $res = process_text1( 0, $tref );
+	$res =~ s/\s+$//s;
+	$$tref = $res;
+}
+
+sub process_text_rfc_links {
+	my $text = shift;
+
+	# For every "RFCnnnn" or "RFC nnn", link it to the authoritative
+	# ource. Do not use the /i modifier here. Require "RFC" to be written in
+	#  in capital letters.
+
+	$text =~ s{
+	(?<=[^<>[:alpha:]])           # Make sure this is not an URL already
+	(RFC\s*([0-9]{1,5}))(?![0-9]) # max 5 digits
+    }
+    {<a href="http://www.ietf.org/rfc/rfc$2.txt" class="rfc">$1</a>}gx;
+
+	$text;
+}
+
+sub process_text1($$;$$) {
+	my ( $lev, $rstr, $func, $closing ) = @_;
+	my $res = '';
+
+	unless ( defined $func ) {
+		$func = '';
+		$lev++;
+	}
+
+	if ( $func eq 'B' ) {
+
+		# B<text> - boldface
+		$res = '<strong>' . process_text1( $lev, $rstr ) . '</strong>';
+
+	} elsif ( $func eq 'C' ) {
+
+		# C<code> - can be a ref or <code></code>
+		# need to extract text
+		my $par = go_ahead( $rstr, 'C', $closing );
+
+		## clean-up of the link target
+		my $text = depod($par);
+
+		### my $x = $par =~ /[BI]</ ? 'yes' : 'no' ;
+		### print STDERR "-->call emit_C($par) lev=$lev, par with BI=$x\n";
+
+		$res = emit_C( $text, $lev > 1 || ( $par =~ /[BI]</ ) );
+
+	} elsif ( $func eq 'E' ) {
+
+		# E<x> - convert to character
+		$$rstr =~ s/^([^>]*)>//;
+		my $escape = $1;
+		$escape =~ s/^(\d+|X[\dA-F]+)$/#$1/i;
+		$res = "&$escape;";
+
+	} elsif ( $func eq 'F' ) {
+
+		# F<filename> - italicize
+		$res = '<em class="file">' . process_text1( $lev, $rstr ) . '</em>';
+
+	} elsif ( $func eq 'I' ) {
+
+		# I<text> - italicize
+		$res = '<em>' . process_text1( $lev, $rstr ) . '</em>';
+
+	} elsif ( $func eq 'L' ) {
+
+		# L<link> - link
+		## L<text|cross-ref> => produce text, use cross-ref for linking
+		## L<cross-ref> => make text from cross-ref
+		## need to extract text
+		my $par = go_ahead( $rstr, 'L', $closing );
+
+		# some L<>'s that shouldn't be:
+		# a) full-blown URL's are emitted as-is
+		if ( $par =~ m{^\w+://}s ) {
+			return make_URL_href($par);
+		}
+
+		# b) C<...> is stripped and treated as C<>
+		if ( $par =~ /^C<(.*)>$/ ) {
+			my $text = depod($1);
+			return emit_C( $text, $lev > 1 || ( $par =~ /[BI]</ ) );
+		}
+
+		# analyze the contents
+		$par =~ s/\n/ /g;    # undo word-wrapped tags
+		my $opar = $par;
+		my $linktext;
+		if ( $par =~ s{^([^|]+)\|}{} ) {
+			$linktext = $1;
+		}
+
+		if( $par =~ m{^\w+://}s ) {
+			return make_URL_href( $par, $linktext );
+		}
+
+		# make sure sections start with a /
+		$par =~ s{^"}{/"};
+
+		my ( $page, $section, $ident );
+
+		# check for link patterns
+		if ( $par =~ m{^([^/]+?)/(?!")(.*?)$} ) {    # name/ident
+			    # we've got a name/ident (no quotes)
+			if ( length $2 ) {
+				( $page, $ident ) = ( $1, $2 );
+			} else {
+				( $page, $section ) = ( $1, $2 );
+			}
+			### print STDERR "--> L<$par> to page $page, ident $ident\n";
+
+		} elsif ( $par =~ m{^(.*?)/"?(.*?)"?$} ) {    # [name]/"section"
+			    # even though this should be a "section", we go for ident first
+			( $page, $ident ) = ( $1, $2 );
+			### print STDERR "--> L<$par> to page $page, section $section\n";
+
+		} elsif ( $par =~ /\s/ ) {  # this must be a section with missing quotes
+			( $page, $section ) = ( '', $par );
+			### print STDERR "--> L<$par> to void page, section $section\n";
+
+		} else {
+			( $page, $section ) = ( $par, '' );
+			### print STDERR "--> L<$par> to page $par, void section\n";
+		}
+
+		# now, either $section or $ident is defined. the convoluted logic
+		# below tries to resolve L<> according to what the user specified.
+		# failing this, we try to find the next best thing...
+		my ( $url, $ltext, $fid );
+
+	RESOLVE: {
+			if ( defined $ident ) {
+				## try to resolve $ident as an item
+				( $url, $fid ) = coderef( $page, $ident );
+				if ($url) {
+					if ( !defined($linktext) ) {
+						$linktext = $ident;
+						$linktext .= " in " if $ident && $page;
+						$linktext .= "the $page manpage" if $page;
+					}
+					###  print STDERR "got coderef url=$url\n";
+					last RESOLVE;
+				}
+				## no luck: go for a section (auto-quoting!)
+				$section = $ident;
+			}
+			## now go for a section
+			my $htmlsection = htmlify($section);
+			$url = page_sect( $page, $htmlsection );
+			if ($url) {
+				if ( !defined($linktext) ) {
+					$linktext = $section;
+					$linktext .= " in " if $section && $page;
+					$linktext .= "the $page manpage" if $page;
+				}
+				### print STDERR "got page/section url=$url\n";
+				last RESOLVE;
+			}
+			## no luck: go for an ident
+			if ($section) {
+				$ident = $section;
+			} else {
+				$ident = $page;
+				$page  = undef();
+			}
+			( $url, $fid ) = coderef( $page, $ident );
+			if ($url) {
+				if ( !defined($linktext) ) {
+					$linktext = $ident;
+					$linktext .= " in " if $ident && $page;
+					$linktext .= "the $page manpage" if $page;
+				}
+				### print STDERR "got section=>coderef url=$url\n";
+				last RESOLVE;
+			}
+
+			# warning; show some text.
+			$linktext = $opar unless defined $linktext;
+			warn
+"$0: $Podfile: cannot resolve L<$opar> in paragraph $Paragraph.\n"
+				unless $Quiet;
+		}
+
+		# now we have a URL or just plain code
+		$$rstr = $linktext . '>' . $$rstr;
+		if ( defined($url) ) {
+			$res = "<a href=\"$url\">" . process_text1( $lev, $rstr ) . '</a>';
+		} else {
+			$res = '<em>' . process_text1( $lev, $rstr ) . '</em>';
+		}
+
+	} elsif ( $func eq 'S' ) {
+
+		# S<text> - non-breaking spaces
+		$res = process_text1( $lev, $rstr );
+		$res =~ s/ /&nbsp;/g;
+
+	} elsif ( $func eq 'X' ) {
+
+		# X<> - ignore
+		warn "$0: $Podfile: invalid X<> in paragraph $Paragraph.\n"
+			unless $$rstr =~ s/^[^>]*>//
+				or $Quiet;
+	} elsif ( $func eq 'Z' ) {
+
+		# Z<> - empty
+		warn "$0: $Podfile: invalid Z<> in paragraph $Paragraph.\n"
+			unless $$rstr =~ s/^>//
+				or $Quiet;
+
+	} else {
+		my $term = pattern $closing;
+		while ( $$rstr =~ s/\A(.*?)(([BCEFILSXZ])<(<+[^\S\n]+)?|$term)//s ) {
+
+			# all others: either recurse into new function or
+			# terminate at closing angle bracket(s)
+			my $pt = $1;
+			$pt .= $2 if !$3 && $lev == 1;
+			$res .= $lev == 1 ? pure_text($pt) : inIS_text($pt);
+			return $res if !$3 && $lev > 1;
+			if ($3) {
+				$res .= process_text1( $lev, $rstr, $3, closing $4 );
+			}
+		}
+		if ( $lev == 1 ) {
+			$res .= pure_text($$rstr);
+		} elsif ( !$Quiet ) {
+			my $snippet = substr( $$rstr, 0, 60 );
+			warn
+"$0: $Podfile: undelimited $func<> in paragraph $Paragraph: '$snippet'.\n"
+
+		}
+		$res = process_text_rfc_links($res);
+	}
+	return $res;
+}
+
+#
+# go_ahead: extract text of an IS (can be nested)
+#
+sub go_ahead($$$) {
+	my ( $rstr, $func, $closing ) = @_;
+	my $res     = '';
+	my @closing = ($closing);
+	while ( $$rstr =~
+		s/\A(.*?)(([BCEFILSXZ])<(<+\s+)?|@{[pattern $closing[0]]})//s )
+	{
+		$res .= $1;
+		unless ($3) {
+			shift @closing;
+			return $res unless @closing;
+		} else {
+			unshift @closing, closing $4;
+		}
+		$res .= $2;
+	}
+	unless ($Quiet) {
+		my $snippet = substr( $$rstr, 0, 60 );
+		warn
+"$0: $Podfile: undelimited $func<> in paragraph $Paragraph (go_ahead): '$snippet'.\n";
+	}
+	return $res;
+}
+
+#
+# emit_C - output result of C<text>
+#    $text is the depod-ed text
+#
+sub emit_C($;$$) {
+	my ( $text, $nocode, $args ) = @_;
+	$args = '' unless defined $args;
+	my $res;
+	my ( $url, $fid ) = coderef( undef(), $text );
+
+	# need HTML-safe text
+	my $linktext = html_escape("$text$args");
+
+	if ( $text !~ /^[\$@%]/
+		&& defined($url)
+		&& ( !defined($EmittedItem) || $EmittedItem ne $fid ) )
+	{
+		$res = "<a href=\"$url\"><code>$linktext</code></a>";
+	} elsif ( 0 && $nocode ) {
+		$res = $linktext;
+	} else {
+		$res = "<code>$linktext</code>";
+	}
+	return $res;
+}
+
+#
+# html_escape: make text safe for HTML
+#
+sub html_escape {
+	my $rest = $_[0];
+	$rest =~ s/&/&amp;/g;
+	$rest =~ s/</&lt;/g;
+	$rest =~ s/>/&gt;/g;
+	$rest =~ s/"/&quot;/g;
+
+	# &apos; is only in XHTML, not HTML4.  Be conservative
+	#$rest   =~ s/'/&apos;/g;
+	return $rest;
+}
+
+#
+# dosify - convert filenames to 8.3
+#
+sub dosify {
+	my ($str) = @_;
+	return lc($str) if $^O eq 'VMS';    # VMS just needs casing
+	if ($Is83) {
+		$str = lc $str;
+		$str =~ s/(\.\w+)/substr ($1,0,4)/ge;
+		$str =~ s/(\w+)/substr ($1,0,8)/ge;
+	}
+	return $str;
+}
+
+#
+# page_sect - make a URL from the text of a L<>
+#
+sub page_sect($$) {
+	my ( $page, $section ) = @_;
+	my ( $linktext, $page83, $link );    # work strings
+
+	# check if we know that this is a section in this page
+	if ( !defined $Pages{$page} && defined $Sections{$page} ) {
+		$section = $page;
+		$page    = "";
+		### print STDERR "reset page='', section=$section\n";
+	}
+
+	$page83 = dosify($page);
+	$page = $page83 if ( defined $Pages{$page83} );
+	if ( $page eq "" ) {
+		$link = "#" . anchorify($section);
+	} elsif ( $page =~ /::/ ) {
+		$page =~ s,::,/,g;
+
+		# Search page cache for an entry keyed under the html page name,
+		# then look to see what directory that page might be in.  NOTE:
+		# this will only find one page. A better solution might be to produce
+		# an intermediate page that is an index to all such pages.
+		my $page_name = $page;
+		$page_name =~ s,^.*/,,s;
+		if ( defined( $Pages{$page_name} )
+			&& $Pages{$page_name} =~ /([^:]*$page)\.(?:pod|pm):/ )
+		{
+			$page = $1;
+		} else {
+
+			# NOTE: This branch assumes that all A::B pages are located in
+			# $Htmlroot/A/B.html . This is often incorrect, since they are
+			# often in $Htmlroot/lib/A/B.html or such like. Perhaps we could
+			# analyze the contents of %Pages and figure out where any
+			# cousins of A::B are, then assume that.  So, if A::B isn't found,
+			# but A::C is found in lib/A/C.pm, then A::B is assumed to be in
+			# lib/A/B.pm. This is also limited, but it's an improvement.
+			# Maybe a hints file so that the links point to the correct places
+			# nonetheless?
+
+		}
+		$link = "$Htmlroot/$page.html";
+		$link .= "#" . anchorify($section) if ($section);
+	} elsif ( !defined $Pages{$page} ) {
+		$link = "";
+	} else {
+		$section = anchorify($section) if $section ne "";
+		### print STDERR "...section=$section\n";
+
+		# if there is a directory by the name of the page, then assume that an
+		# appropriate section will exist in the subdirectory
+		#	if ($section ne "" && $Pages{$page} =~ /([^:]*[^(\.pod|\.pm)]):/) {
+		if ( $section ne "" && $Pages{$page} =~ /([^:]*(?<!\.pod)(?<!\.pm)):/ )
+		{
+			$link = "$Htmlroot/$1/$section.html";
+			### print STDERR "...link=$link\n";
+
+		 # since there is no directory by the name of the page, the section will
+		 # have to exist within a .html of the same name.  thus, make sure there
+		 # is a .pod or .pm that might become that .html
+		} else {
+			$section = "#$section" if $section;
+			### print STDERR "...section=$section\n";
+
+			# check if there is a .pod with the page name.
+			# for L<Foo>, Foo.(pod|pm) is preferred to A/Foo.(pod|pm)
+			if ( $Pages{$page} =~ /([^:]*)\.(?:pod|pm):/ ) {
+				$link = "$Htmlroot/$1.html$section";
+			} else {
+				$link = "";
+			}
+		}
+	}
+
+	if ($link) {
+
+		# Here, we take advantage of the knowledge that $Htmlfileurl ne ''
+		# implies $Htmlroot eq ''. This means that the link in question
+		# needs a prefix of $Htmldir if it begins with '/'. The test for
+		# the initial '/' is done to avoid '#'-only links, and to allow
+		# for other kinds of links, like file:, ftp:, etc.
+		my $url;
+		if ( $Htmlfileurl ne '' ) {
+			$link = "$Htmldir$link" if $link =~ m{^/}s;
+			$url = relativize_url( $link, $Htmlfileurl );
+
+			# print( "  b: [$link,$Htmlfileurl,$url]\n" );
+		} else {
+			$url = $link;
+		}
+		return $url;
+
+	} else {
+		return undef();
+	}
+}
+
+#
+# relativize_url - convert an absolute URL to one relative to a base URL.
+# Assumes both end in a filename.
+#
+sub relativize_url {
+	my ( $dest, $source ) = @_;
+
+	my ( $dest_volume, $dest_directory, $dest_file ) =
+		File::Spec::Unix->splitpath($dest);
+	$dest = File::Spec::Unix->catpath( $dest_volume, $dest_directory, '' );
+
+	my ( $source_volume, $source_directory, $source_file ) =
+		File::Spec::Unix->splitpath($source);
+	$source =
+		File::Spec::Unix->catpath( $source_volume, $source_directory, '' );
+
+	my $rel_path = '';
+	if ( $dest ne '' ) {
+		$rel_path = File::Spec::Unix->abs2rel( $dest, $source );
+	}
+
+	if (   $rel_path ne ''
+		&& substr( $rel_path, -1 ) ne '/'
+		&& substr( $dest_file, 0, 1 ) ne '#' )
+	{
+		$rel_path .= "/$dest_file";
+	} else {
+		$rel_path .= "$dest_file";
+	}
+
+	return $rel_path;
+}
+
+#
+# coderef - make URL from the text of a C<>
+#
+sub coderef($$) {
+	my ( $page, $item ) = @_;
+	my ($url);
+
+	my $fid = fragment_id($item);
+
+	if ( defined($page) && $page ne "" ) {
+
+		# we have been given a $page...
+		$page =~ s{::}{/}g;
+
+		Carp::confess(
+"Undefined fragment '$item' from fragment_id() in coderef() in $Podfile"
+		) if !defined $fid;
+
+		# Do we take it? Item could be a section!
+		my $base = $Items{$fid} || "";
+		$base =~ s{[^/]*/}{};
+		if ( $base ne "$page.html" ) {
+			###   print STDERR "coderef( $page, $item ): items{$fid} = $Items{$fid} = $base => discard page!\n";
+			$page = undef();
+		}
+
+	} else {
+
+		# no page - local items precede cached items
+		if ( defined($fid) ) {
+			if ( exists $Local_Items{$fid} ) {
+				$page = $Local_Items{$fid};
+			} else {
+				$page = $Items{$fid};
+			}
+		}
+	}
+
+	# if there was a pod file that we found earlier with an appropriate
+	# =item directive, then create a link to that page.
+	if ( defined $page ) {
+		if ($page) {
+			if ( exists $Pages{$page} and $Pages{$page} =~ /([^:.]*)\.[^:]*:/ )
+			{
+				$page = $1 . '.html';
+			}
+			my $link = "$Htmlroot/$page#" . anchorify($fid);
+
+			# Here, we take advantage of the knowledge that $Htmlfileurl
+			# ne '' implies $Htmlroot eq ''.
+			if ( $Htmlfileurl ne '' ) {
+				$link = "$Htmldir$link";
+				$url = relativize_url( $link, $Htmlfileurl );
+			} else {
+				$url = $link;
+			}
+		} else {
+			$url = "#" . anchorify($fid);
+		}
+
+		confess "url has space: $url" if $url =~ /"[^"]*\s[^"]*"/;
+	}
+	return ( $url, $fid );
+}
+
+#
+# Adapted from Nick Ing-Simmons' PodToHtml package.
+sub relative_url {
+	my $source_file      = shift;
+	my $destination_file = shift;
+
+	my $source = URI::file->new_abs($source_file);
+	my $uo = URI::file->new( $destination_file, $source )->abs;
+	return $uo->rel->as_string;
+}
+
+#
+# finish_list - finish off any pending HTML lists.  this should be called
+# after the entire pod file has been read and converted.
+#
+sub finish_list {
+	while ( $Listlevel > 0 ) {
+		print HTML "</dl>\n";
+		$Listlevel--;
+	}
+}
+
+#
+# htmlify - converts a pod section specification to a suitable section
+# specification for HTML. Note that we keep spaces and special characters
+# except ", ? (Netscape problem) and the hyphen (writer's problem...).
+#
+sub htmlify {
+	my ($heading) = @_;
+	$heading =~ s/(\s+)/ /g;
+	$heading =~ s/\s+\Z//;
+	$heading =~ s/\A\s+//;
+
+	# The hyphen is a disgrace to the English language.
+	# $heading =~ s/[-"?]//g;
+	$heading =~ s/["?]//g;
+	$heading = lc($heading);
+	return $heading;
+}
+
+#
+# similar to htmlify, but turns non-alphanumerics into underscores
+#
+sub anchorify {
+	my ($anchor) = @_;
+	$anchor =~ s/\([^)]*\)//;
+	$anchor = htmlify($anchor);
+	$anchor =~ s/\W/_/g;
+	$anchor =~ tr/_/_/s;
+	return $anchor;
+}
+
+#
+# depod - convert text by eliminating all interior sequences
+# Note: can be called with copy or modify semantics
+#
+my %E2c;
+$E2c{lt}     = '<';
+$E2c{gt}     = '>';
+$E2c{sol}    = '/';
+$E2c{verbar} = '|';
+$E2c{amp}    = '&';    # in Tk's pods
+
+sub depod1($;$$);
+
+sub depod($) {
+	my $string;
+	if ( ref( $_[0] ) ) {
+		$string = ${ $_[0] };
+		${ $_[0] } = depod1( \$string );
+	} else {
+		$string = $_[0];
+		depod1( \$string );
+	}
+}
+
+sub depod1($;$$) {
+	my ( $rstr, $func, $closing ) = @_;
+	my $res = '';
+	return $res unless defined $$rstr;
+	if ( !defined($func) ) {
+
+		# skip to next begin of an interior sequence
+		while ( $$rstr =~ s/\A(.*?)([BCEFILSXZ])<(<+[^\S\n]+)?//s ) {
+
+			# recurse into its text
+			$res .= $1 . depod1( $rstr, $2, closing $3);
+		}
+		$res .= $$rstr;
+	} elsif ( $func eq 'E' ) {
+
+		# E<x> - convert to character
+		$$rstr =~ s/^([^>]*)>//;
+		$res .= $E2c{$1} || "";
+	} elsif ( $func eq 'X' ) {
+
+		# X<> - ignore
+		$$rstr =~ s/^[^>]*>//;
+	} elsif ( $func eq 'Z' ) {
+
+		# Z<> - empty
+		$$rstr =~ s/^>//;
+	} else {
+
+		# all others: either recurse into new function or
+		# terminate at closing angle bracket
+		my $term = pattern $closing;
+		while ( $$rstr =~ s/\A(.*?)(([BCEFILSXZ])<(<+[^\S\n]+)?|$term)//s ) {
+			$res .= $1;
+			last unless $3;
+			$res .= depod1( $rstr, $3, closing $4 );
+		}
+		## If we're here and $2 ne '>': undelimited interior sequence.
+		## Ignored, as this is called without proper indication of where we are.
+		## Rely on process_text to produce diagnostics.
+	}
+	return $res;
+}
+
+{
+	my %seen;    # static fragment record hash
+
+	sub fragment_id_readable {
+		my $text     = shift;
+		my $generate = shift;    # optional flag
+
+		my $orig = $text;
+
+		# leave the words for the fragment identifier,
+		# change everything else to underbars.
+		$text =~
+			s/[^A-Za-z0-9_]+/_/g;    # do not use \W to avoid locale dependency.
+		$text =~ s/_{2,}/_/g;
+		$text =~ s/\A_//;
+		$text =~ s/_\Z//;
+
+		unless ($text) {
+
+			# Nothing left after removing punctuation, so leave it as is
+			# E.g. if option is named: "=item -#"
+
+			$text = $orig;
+		}
+
+		if ($generate) {
+			if ( exists $seen{$text} ) {
+
+				# This already exists, make it unique
+				$seen{$text}++;
+				$text = $text . $seen{$text};
+			} else {
+				$seen{$text} = 1;    # first time seen this fragment
+			}
+		}
+
+		$text;
+	}
+}
+
+my @HC;
+
+sub fragment_id_obfuscated {         # This was the old "_2d_2d__"
+	my $text     = shift;
+	my $generate = shift;            # optional flag
+
+	# text? Normalize by obfuscating the fragment id to make it unique
+	$text =~ s/\s+/_/sg;
+
+	$text =~ s{(\W)}{
+        defined( $HC[ord($1)] ) ? $HC[ord($1)]
+        : ( $HC[ord($1)] = sprintf( "%%%02X", ord($1) ) ) }gxe;
+	$text = substr( $text, 0, 50 );
+
+	$text;
+}
+
+#
+# fragment_id - construct a fragment identifier from:
+#   a) =item text
+#   b) contents of C<...>
+#
+
+sub fragment_id {
+	my $text     = shift;
+	my $generate = shift;    # optional flag
+
+	$text =~ s/\s+\Z//s;
+	if ($text) {
+
+		# a method or function?
+		return $1 if $text =~ /(\w+)\s*\(/;
+		return $1 if $text =~ /->\s*(\w+)\s*\(?/;
+
+		# a variable name?
+		return $1 if $text =~ /^([\$\@%*]\S+)/;
+
+		# some pattern matching operator?
+		return $1 if $text =~ m|^(\w+/).*/\w*$|;
+
+		# fancy stuff... like "do { }"
+		return $1 if $text =~ m|^(\w+)\s*{.*}$|;
+
+		# honour the perlfunc manpage: func [PAR[,[ ]PAR]...]
+		# and some funnies with ... Module ...
+		return $1 if $text =~ m{^([a-z\d_]+)(\s+[A-Z,/& ][A-Z\d,/& ]*)?$};
+		return $1 if $text =~ m{^([a-z\d]+)\s+Module(\s+[A-Z\d,/& ]+)?$};
+
+		return fragment_id_readable( $text, $generate );
+	} else {
+		return;
+	}
+}
+
+#
+# make_URL_href - generate HTML href from URL
+# Special treatment for CGI queries.
+#
+sub make_URL_href($;$) {
+	my ($url) = shift;
+	my $linktext = shift || $url;
+	if ( $url !~ s{^(http:[-\w/#~:.+=&%@!]+)(\?.*)$}{<a href="$1$2">$1</a>}i ) {
+		$url = "<a href=\"$url\">$linktext</a>";
+	}
+	return $url;
+}
+
+1;