# Copyright (c) 1999-2007 by Steven McDougall.  This module is free
# software; you can redistribute it and/or modify it under the same
# terms as Perl itself.

use strict;
use HTML::Stream;
use IO::File;
use IO::String;
use Pod::Tree;
use Text::Template;


package Pod::Tree::BitBucket;

sub new      { bless {}, shift }
sub AUTOLOAD { shift }


package Pod::Tree::StrStream;

sub new
{
    my($class, $ref) = @_;

    if ($ref)
    {
	return bless $ref, $class
    }
    else
    {
	my $st = '';
	return bless \$st, $class;
    }
}

sub print
{
    my $st = shift;
    $$st  .= join('', @_);
}

sub get
{
    my $st = shift;
    my $s  = $$st;
    $$st   = '';
    $s
}


package Pod::Tree::HTML;

use constant BGCOLOR => '#ffffff';
use constant TEXT    => '#000000';

our $VERSION = '1.10';

sub new
{
    my($class, $source, $dest, %options) = @_;
    defined $dest or die "Pod::Tree::HTML::new: not enough arguments\n";

    my $tree         = _resolve_source($source);
    my($fh, $stream) = _resolve_dest  ($dest  , $tree, \%options);

    my $options = { bgcolor     => BGCOLOR,
		    depth       => 0,
		    hr          => 1,
		    link_map    => Pod::Tree::HTML::LinkMap->new(),
		    text        => TEXT,
		    toc         => 1,
		    };

    my $HTML = { tree        => $tree,
		 root        => $tree->get_root,
		 stream      => $stream,
		 fh          => $fh,
		 text_method => 'text',
		 options     => $options,
		 };

    bless $HTML, $class;

    $HTML->set_options(%options);
    $HTML
}


sub _resolve_source
{
    my $source = shift;
    my $ref    = ref $source;
    local *isa = \&UNIVERSAL::isa;

    isa($source, 'Pod::Tree') and return $source;

    my $tree = new Pod::Tree;
    not $ref		     and $tree->load_file      ( $source);
    isa($source, 'IO::File') and $tree->load_fh	       ( $source);
    $ref eq 'SCALAR'         and $tree->load_string    ($$source);
    $ref eq 'ARRAY'          and $tree->load_paragraphs( $source);

    $tree->loaded or 
	die "Pod::Tree::HTML::_resolve_source: Can't load POD from $source\n";

    $tree
}


sub _resolve_dest
{
    my($dest, $tree, $options) = @_;

    $tree->has_pod or $options->{empty} or
	return (undef, new Pod::Tree::BitBucket);

    local *isa = \&UNIVERSAL::isa;
    local *can = \&UNIVERSAL::can;

    isa($dest, 'HTML::Stream') and return (undef, 		   $dest);
    isa($dest, 'IO::File'    ) and return ($dest, new HTML::Stream $dest);
    can($dest, 'print'       ) and return ($dest, new HTML::Stream $dest);

    if (ref $dest eq 'SCALAR')
    {
	my $fh = new IO::String $$dest;
	return ($fh, new HTML::Stream $fh);
    }

    if (ref $dest eq '' and $dest)
    {
	my $fh = new IO::File;
	$fh->open($dest, '>') or die "Pod::Tree::HTML::new: Can't open $dest: $!\n";
	return ($fh, new HTML::Stream $fh);
    }

    die "Pod::Tree::HTML::_resolve_dest: Can't write HTML to $dest\n";
}


sub set_options
{
    my($html, %options) = @_;

    my($key, $value);
    while (($key, $value) = each %options)
    {
	$html->{options}{$key} = $value;
    }
}


sub get_options
{
    my($html, @options) = @_;

    map { $html->{options}{$_} } @options
}


sub get_stream { shift->{stream} }


sub translate
{
    my($html, $template) = @_;

    if ($template)
    {
	$html->_template($template);
    }
    else
    {
	$html->_translate;
    }
}


sub _translate
{
    my $html    = shift;
    my $stream  = $html->{stream};
    my $bgcolor = $html->{options}{bgcolor};
    my $text 	= $html->{options}{text};
    my $title   = $html->_make_title;
    my $base    = $html->{options}{base};
    my $css     = $html->{options}{css};

    $stream->HTML->HEAD;

    defined $title and $stream->TITLE->text($title)->_TITLE;
    defined $base  and $stream->BASE(href => $base);
    defined $css   and $stream->LINK(href => $css,
				     type => "text/css",
				     rel  => "stylesheet");

    $stream->_HEAD
	   ->BODY(BGCOLOR => $bgcolor, TEXT => $text);

    $html->emit_toc;
    $html->emit_body;

    $stream->nl
	   ->_BODY
	   ->_HTML
}


sub _template
{
    my ($html, $tSource) = @_;

    my 	$fh	    = $html->{fh};
    my  $sStream    = new Pod::Tree::StrStream;
    $html->{stream} = new HTML::Stream $sStream;

    our $bgcolor = $html->{options}{bgcolor};
    our $text 	 = $html->{options}{text};
    our $title   = $html->_make_title;
    our $base    = $html->{options}{base};
    our $css     = $html->{options}{css};

    $html->emit_toc;
    our $toc = $sStream->get;

    $html->emit_body;
    our $body = $sStream->get;

    my $template = new Text::Template SOURCE => $tSource or
	die "Can't create Text::Template object: $Text::Template::ERROR\n";

    $template->fill_in(OUTPUT => $fh) or
	die $Text::Template::ERROR;
}


sub _make_title
{
    my $html  = shift;

    my $title = $html->{options}{title};
    defined $title and return $title;

    my $children = $html->{root}->get_children;
    my $node1;
    my $i = 0;
    for my $child (@$children)
    {
	is_pod $child or next;
	$i++ and $node1 = $child;
	$node1 and last;
    }

    $node1 or return undef;

    my $text = $node1->get_deep_text;
    ($title) = split m(\s+-), $text;

    $title  or return undef;      # to quiet -w
    $title =~ s(\s+$)();

    $title
}


sub emit_toc
{
    my $html = shift;
    $html->{options}{toc} or return;

    my $root  = $html->{root};
    my $nodes = $root->get_children;
    my @nodes = @$nodes;

    $html->_emit_toc_1(\@nodes);

    $html->{options}{hr} > 0 and $html->{stream}->HR;
}


sub _emit_toc_1
{
    my($html, $nodes) = @_;
    my $stream = $html->{stream};

    $stream->UL;

    while (@$nodes)
    {
	my $node = $nodes->[0];
	is_c_head2 $node and $html->_emit_toc_2   ($nodes), next;
	is_c_head1 $node and $html->_emit_toc_item($node );
	shift @$nodes;
    }

    $stream->_UL;
}


sub _emit_toc_2
{
    my($html, $nodes) = @_;
    my $stream = $html->{stream};

    $stream->UL;

    while (@$nodes)
    {
	my $node = $nodes->[0];
	is_c_head1 $node and last;
	is_c_head2 $node and $html->_emit_toc_item($node);
	shift @$nodes;
    }

    $stream->_UL;
}


sub _emit_toc_item
{
    my($html, $node) = @_;
    my $stream = $html->{stream};
    my $target = $html->_make_anchor($node);

    $stream->LI->A(HREF => "#$target");
    $html->_emit_children($node);
    $stream->_A;
}


sub emit_body
{
    my $html = shift;
    my $root = $html->{root};
    $html->_emit_children($root);
}


sub _emit_children
{
    my($html, $node) = @_;

    my $children = $node->get_children;

    for my $child (@$children)
    {
	$html->_emit_node($child);
    }
}


sub _emit_siblings
{
    my($html, $node) = @_;

    my $siblings = $node->get_siblings;

    if (@$siblings==1 and $siblings->[0]{type} eq 'ordinary')
    {
	# don't put <p></p> around a single ordinary paragraph
	$html->_emit_children($siblings->[0]);
    }
    else
    {
	for my $sibling (@$siblings)
	{
	    $html->_emit_node($sibling);
	}
    }
    
}


sub _emit_node
{
    my($html, $node) = @_;
    my $type = $node->{type};

    for ($type)
    {
	/command/  and $html->_emit_command ($node);
	/for/      and $html->_emit_for     ($node);
	/item/     and $html->_emit_item    ($node);
	/list/     and $html->_emit_list    ($node);
	/ordinary/ and $html->_emit_ordinary($node);
	/sequence/ and $html->_emit_sequence($node);
	/text/     and $html->_emit_text    ($node);
	/verbatim/ and $html->_emit_verbatim($node);
    }
}


my %HeadTag = ( head1 => { 'open' => 'H1', 'close' => '_H1', level => 1 },
	        head2 => { 'open' => 'H2', 'close' => '_H2', level => 2 },
	        head3 => { 'open' => 'H3', 'close' => '_H3', level => 3 },
	        head4 => { 'open' => 'H4', 'close' => '_H4', level => 4 } );

sub _emit_command
{
    my($html, $node) = @_;
    my $stream   = $html->{stream};
    my $command  = $node->get_command;
    my $head_tag = $HeadTag{$command};
    $head_tag or return;
    my $anchor   = $html->_make_anchor($node);

    $html->_emit_hr($head_tag->{level});

    my $tag;
    $tag = $head_tag->{'open'};
    $stream->$tag()->A(NAME => $anchor);

    $html->_emit_children($node);

    $tag = $head_tag->{'close'};
    $stream->_A->$tag();
}


sub _emit_hr
{
    my($html, $level) = @_;
    $html->{options}{hr} > $level or return;
    $html->{skip_first}++ or return;
    $html->{stream}->HR;
}


sub _emit_for
{
    my($html, $node) = @_;
    
    my $interpreter = lc $node->get_arg;
    my $emit        = "_emit_for_$interpreter";

    $html->$emit($node) if $html->can($emit);
}

sub _emit_for_html
{
    my($html, $node) = @_;

    my $stream = $html->{stream};
    $stream->P;
    $stream->io->print($node->get_text);
    $stream->_P;
}

sub _emit_for_image
{
    my($html, $node) = @_;

    my $stream = $html->{stream};
    my $link    = $node->get_text;
       $link    =~ s(\s+$)();

    $stream->IMG(src => $link);
}


sub _emit_item
{
    my($html, $node) = @_;

    my $stream    = $html->{stream};
    my $item_type = $node->get_item_type;
    for ($item_type)
    {
	/bullet/ and do
	{
	    $stream->LI();
	    $html->_emit_siblings($node);
	    $stream->_LI();
	};

	/number/ and do
	{
	    $stream->LI();
	    $html->_emit_siblings($node);
	    $stream->_LI();
	};

	/text/   and do
	{
	    my $anchor = $html->_make_anchor($node);
	    $stream->DT->A(NAME => "$anchor");
	    $html->_emit_children($node);
	    $stream->_A->_DT->DD;
	    $html->_emit_siblings($node);
	    $stream->_DD;
	};
    }

}


my %ListTag  = (bullet => { 'open' => 'UL', 'close' => '_UL' },
		number => { 'open' => 'OL', 'close' => '_OL' },
		text   => { 'open' => 'DL', 'close' => '_DL' } );

sub _emit_list
{
    my($html, $node) = @_;
    my($list_tag, $tag);    # to quiet -w, see beloew

    my $stream    = $html->{stream};
    my $list_type = $node->get_list_type;

    $list_type and $list_tag = $ListTag{$list_type};
    $list_tag  and $tag      = $list_tag->{'open'};
    $tag and $stream->$tag();

    $html->_emit_children($node);
    
    $list_tag and $tag = $list_tag->{'close'};
    $tag and $stream->$tag();
}


sub _emit_ordinary
{
    my($html, $node) = @_;
    my $stream = $html->{stream};

    $stream->P;
    $html->_emit_children($node);
    $stream->_P;
}


sub _emit_sequence
{
    my($html, $node) = @_;

    for ($node->get_letter)
    {
	/I|B|C|F/ and $html->_emit_element($node), last;
	/S/       and $html->_emit_nbsp   ($node), last;
	/L/       and $html->_emit_link   ($node), last;
	/X/       and $html->_emit_index  ($node), last;
	/E/       and $html->_emit_entity ($node), last;
    }
}


my %ElementTag = (I => { 'open' => 'I'   , 'close' => '_I'    },
		  B => { 'open' => 'B'   , 'close' => '_B'    },
		  C => { 'open' => 'CODE', 'close' => '_CODE' },
		  F => { 'open' => 'I'   , 'close' => '_I'    } );

sub _emit_element
{
    my($html, $node) = @_;

    my $letter = $node->get_letter;
    my $stream = $html->{stream};

    my $tag;
    $tag = $ElementTag{$letter}{'open'};
    $stream->$tag();
    $html->_emit_children($node);
    $tag = $ElementTag{$letter}{'close'};
    $stream->$tag();
}


sub _emit_nbsp
{
    my($html, $node) = @_;

    my $old_method = $html->{text_method};
    $html->{text_method} = 'text_nbsp';
    $html->_emit_children($node);
    $html->{text_method} = $old_method;
}


sub _emit_link
{
    my($html, $node) = @_;

    my $stream = $html->{stream};
    my $target = $node->get_target;
    my $domain = $target->get_domain;
    my $method = "make_${domain}_URL";
    my $url    = $html->$method($target);

    $stream->A(HREF=>$url);
    $html->_emit_children($node);
    $stream->_A;
}

sub make_POD_URL
{
    my($html, $target) = @_;

    my $link_map = $html->{options}{link_map};

    return $link_map->url($html, $target) if $link_map->can("url");

    $html->make_mapped_URL($target)
}


sub make_mapped_URL
{
    my($html, $target) = @_;

    my $link_map = $html->{options}{link_map};
    my $base     = $html->{options}{base} || '';
    my $page     = $target->get_page;
    my $section  = $target->get_section;
    my $depth    = $html->{options}{depth};

    ($base, $page, $section) = $link_map->map($base, $page, $section, $depth);

       $base     =~ s(/$)();
       $page    .= '.html' if $page;
    my $fragment = $html->escape_2396($section);
    my $url      = $html->assemble_url($base, $page, $fragment);

    $url
}

sub make_HTTP_URL
{
    my($html, $target) = @_;

    $target->get_page
}


sub _emit_index
{
    my($html, $node) = @_;

    my $stream = $html->{stream};
    my $anchor = $html->_make_anchor($node);
    $stream->A(NAME=>$anchor)->_A;
}


sub _emit_entity
{
    my($html, $node) = @_;

    my $stream = $html->{stream};
    my $entity  = $node->get_deep_text;
    $stream->ent($entity);
}


sub _emit_text
{
    my($html, $node) = @_;
    my $stream       = $html->{stream};
    my $text         = $node->get_text;
    my $text_method  = $html->{text_method};

    $stream->$text_method($text);
}


sub _emit_verbatim
{
    my($html, $node) = @_;
    my $stream = $html->{stream};
    my $text   = $node->get_text;
       $text   =~ s(\n\n$)();

    $stream->PRE->text($text)->_PRE;
}


sub _make_anchor
{
    my($html, $node) = @_;
    my $text = $node->get_deep_text;
       $text =~ s(   \s*\n\s*/  )( )xg;  # close line breaks
       $text =~ s( ^\s+ | \s+$  )()xg;   # clip leading and trailing WS
       $html->escape_2396($text)
}

 
sub bin { oct '0b' . join '', @_ }

my @LinkFormat = ( sub { my($b,$p,$f)=@_; ""         },
		   sub { my($b,$p,$f)=@_;      "#$f" },
                   sub { my($b,$p,$f)=@_;    "$p"    },
                   sub { my($b,$p,$f)=@_;    "$p#$f" },
                   sub { my($b,$p,$f)=@_; "$b/"      },
                   sub { my($b,$p,$f)=@_;      "#$f" },
                   sub { my($b,$p,$f)=@_; "$b/$p"    },
                   sub { my($b,$p,$f)=@_; "$b/$p#$f" } );

sub assemble_url
{
    my($html, $base, $page, $fragment) = @_;

    my $i    = bin map { length($_) ? 1 : 0 } ($base, $page, $fragment);
    my $url  = $LinkFormat[$i]($base, $page, $fragment);

    $url
}

sub escape_2396
{
    my($html, $text) = @_;
    $text =~ s(([^\w\-.!~*'()]))(sprintf("%%%02x", ord($1)))eg;
    $text
}


package Pod::Tree::HTML::LinkMap;

sub new
{
    my $class = shift;
    bless {}, $class
}

sub url
{
    my($link_map, $html, $target) = @_;

    my $depth    = $html->{options}{depth};
    my $base     = join '/', ('..') x $depth;

    my $page     = $target->get_page;
       $page     =~ s(::)(/)g;
       $page    .= '.html' if $page;

    my $section  = $target->get_section;
    my $fragment = $html->escape_2396 ($section);

    my $url      = $html->assemble_url($base, $page, $fragment);
    $url
}


__END__

=head1 NAME

Pod::Tree::HTML - Generate HTML from a Pod::Tree

=head1 SYNOPSIS

  use Pod::Tree::HTML;
  
  $source   =   new Pod::Tree %options;
  $source   =  "file.pod";
  $source   =   new IO::File;
  $source   = \$pod;
  $source   = \@pod;
  
  $dest     =   new HTML::Stream;
  $dest     =   new IO::File;
  $dest     =  "file.html";
  
  $html     =   new Pod::Tree::HTML $source, $dest, %options;
  
              $html->set_options(%options);
  @values   = $html->get_options(@keys);
  
              $html->translate;
              $html->translate($template);
              $html->emit_toc;
              $html->emit_body;
  
  $fragment = $html->escape_2396 ($section);
  $url      = $html->assemble_url($base, $page, $fragment);


=head1 REQUIRES

C<HTML::Stream>, C<Text::Template>


=head1 DESCRIPTION

C<Pod::Tree::HTML> reads a POD and translates it to HTML.
The source and destination are fixed when the object is created.
Options are provided for controlling details of the translation.

The C<translate> method does the actual translation.

For convenience, 
C<Pod::Tree::HTML> can read PODs from a variety of sources,
and write HTML to a variety of destinations.
The C<new> method resolves the I<$source> and I<$dest> arguments.

C<Pod::Tree::HTML> can also use C<Text::Template> to fill in an HTML
template file.


=head2 Source resolution

C<Pod::Tree::HTML> can obtain a POD from any of 5 sources.
C<new> resolves I<$source> by checking these things,
in order:

=over 4

=item 1

If I<$source> C<isa> C<POD::Tree>, 
then the POD is taken from that tree.

=item 2

If I<$source> is not a reference, 
then it is taken to be the name of a file containing a POD.

=item 3

If I<$source> C<isa> C<IO::File>, 
then it is taken to be an C<IO::File> object that is already
open on a file containing a POD.

=item 4

If I<$source> is a SCALAR reference,
then the text of the POD is taken from that scalar.

=item 5

if I<$source> is an ARRAY reference,
then the paragraphs of the POD are taken from that array.

=back

If I<$source> isn't any of these things,
C<new> C<die>s.
 

=head2 Destination resolution

C<Pod::Tree::HTML> can write HTML to any of 5 destinations.
C<new> resolves I<$dest> by checking these things,
in order:

=over 4

=item 1

If I<$dest> C<isa> C<HTML::Stream>,
then C<Pod::Tree::HTML> writes HTML to that stream.

=item 2

If I<$dest> C<isa> C<IO::File>,
then C<Pod::Tree::HTML> writes HTML to that file.

=item 3

If I<$dest> has a C<print> method,
then C<Pod::Tree::HTML> passes HTML to that method.

=item 4

If I<$dest> is a SCALAR reference,
then C<Pod::Tree::HTML> writes HTML to that scalar.

=item 5

If I<$dest> is a string,
then C<Pod::Tree::HTML> writes HTML to the file with that name.

=back

If I<$dest> isn't any of these things,
C<new> C<die>s.


=head1 METHODS

=over 4

=item I<$html> = C<new> C<Pod::Tree::HTML> I<$source>, I<$dest>, I<%options>

Creates a new C<Pod::Tree::HTML> object.

I<$html> reads a POD from I<$source>,
and writes HTML to I<$dest>.
See L</Source resolution> and L</Destination resolution> for details.

Options controlling the translation may be passed in the I<%options> hash.
See L</OPTIONS> for details.

=item I<$html>->C<set_options>(I<%options>)

Sets options controlling the translation.
See L</OPTIONS> for details.

=item I<@values> = I<$html>->C<get_options>(I<@keys>)

Returns the current values of the options specified in I<@keys>.
See L</OPTIONS> for details.

=item I<$html>->C<translate>

=item I<$html>->C<translate>(I<$template>)

Translates the POD to HTML.
This method should only be called once.

In the second form,
I<$template> is the name of a file containing a template.
The template will be filled in by the C<Text::Template> module.
Here is a minimal template,
showing example usage of all the variables that are set by C<Pod::Tree::HTML>.

  <html>
   <head>
    <base href="{$base}">
    <link href="{$css}" rel="stylesheet" type="text/css">
    <title>{$title}</title>
   </head>
   <body bgcolor="{$bgcolor}" text="{$text}">
    {$toc}
    {$body}
   </body>
  </html>

The program fragments in the template are evaulted in the C<Pod::Tree::HTML> package.
Any variables that you set in this package will be available to your template.

When a template is used, the destination must not be an C<HTML::Stream> object.

C<translate> doesn't return anything.
The first form always returns.
The second form C<die>s if there is an error creating or filling in the template.


=item I<$html>->C<emit_toc>

=item I<$html>->C<emit_body>

Emits the table of contents and body of the HTML document.

These methods are called automatically by C<translate>.
They are exposed in the API for applications that wish to embed the 
HTML inside a larger document.

=back

=head2 Utility methods

These methods are provided for implementors who write their own link
mapper objects.

=over 4

=item I<$fragment> = I<$html>->C<escape_2396>(I<$section>)

Escapes I<$section> according to RFC 2396. For example, the section

    some section

is returned as

    some%20section

=item I<$url> = I<$html>->C<assemble_url>(I<$base>, I<$page>, I<$fragment>)

Assembles I<$base>, I<$page>, and I<$fragment> into a URL, of the form

    $base/$page#$fragment

Attempts to construct a valid URL, even if some of I<$base>, I<$page>,
and I<$fragment> are empty.

=back


=head1 OPTIONS

=over 4

=item C<base> => I<$url>

Specifies a base URL for relative HTML links.


=item C<bgcolor> => I<#rrggbb>

Set the background color to I<#rrggbb>.
Default is white.


=item C<css> => I<$url>

Specifies a Cascading Style Sheet for the generated HTML page.


=item C<depth> => I<$depth>

Specifies the depth of the generated HTML page in a directory tree.
See L</LINK MAPPING> for details.


=item C<empty> => C<1>

Causes the C<translate> method to emit an HTML file, even if the POD is empty.
If this option is not provided, then no HTML file is created for empty PODs.


=item C<hr> => I<$level>

Controls the profusion of horizontal lines in the output, as follows:

    $level   horizontal lines
    0 	     none
    1 	     between TOC and body
    2 	     after each =head1
    3 	     after each =head1 and =head2

Default is level 1.


=item C<link_map> => I<$link_map>

Sets the link mapper. 
See L</LINK MAPPING> for details.


=item C<text> => I<#rrggbb>

Set the text color to I<#rrggbb>.
Default is black.


=item C<title> => I<title>

Set the page title to I<title>.
If no C<title> option is given, 
C<Pod::Tree::HTML> will attempt construct a title from the 
second paragrah of the POD.
This supports the following style:

    =head1 NAME
    
    ls - list contents of directory


=item C<toc> => [C<0>|C<1>]

Includes or omits the table of contents.
Default is to include the TOC.

=back


=head1 LINKS and TARGETS

C<Pod::Tree::HTML> automatically generates HTML destination anchors for
all =headI<n> command paragraphs,
and for text items in =over lists.
The text of the paragraph becomes the C<name> attribute of the anchor.
Markups are ignored and the text is escaped according to RFC 2396.

For example, the paragraph

	=head1 C<Foo> Bar

is translated to 

	<h1><a name="Foo%20Bar"><code>Foo</code> Bar</a></h1>

To link to a heading, 
simply give the text of the heading in an C<< LZ<><> >> markup.
The text must match exactly; 
markups may vary.
Either of these would link to the heading shown above

	L</C<Foo> Bar>
	L</Foo Bar>

To generate destination anchors in other places,
use the index (C<< XZ<><> >>) markup

	We can link to X<this text> this text.

and link to it as usual

	L</this text> uses the index markup.

Earlier versions of this module also emitted the content of the XZ<><>
markup as visible text. However, L<perlpod> now specifies that XZ<><>
markups render as an empty string, so C<Pod::Tree::HTML> has been
changed to do that.


=head1 LINK MAPPING

The POD specification provides the C<< LZ<><> >> markup to link from
one document to another. HTML provides anchors (C<< <a href=""></a> >>) 
for the same purpose. Obviously, a POD2HTML translator should
convert the first to the second.

In general, this is a hard problem.
In particular, the POD format is not powerful enough to support the kind
of hyper-linking that people want in a complex documentation system.

Rather than try to be all things to all people,
C<Pod::Tree::HTML> uses a I<link mapper> object to translate 
the target of a POD link to a URL.
The default link mapper does a simple translation, described below.
If you don't like the default translation,
you can provide your own link mapper
with the L<< C<link_map> => I<$link_map> >> option.


=head2 Default

The default link mapper obtains the I<page> and I<section> from the target.
It translates C<::> sequences in the I<page> to C</>,
and returns a URL of the form [C<../>...][I<page>C<.html>][C<#>I<section>]

If the L<< C<depth> => I<$depth> >> option is given,
a corresponding number of C<../> sequences are prepended to I<page>.

This is a relative URL, 
so it will be interpreted relative to the L<< C<base> => I<$base> >> option,
if any.


=head2 Custom

To use your own link mapper,
create a link mapper object and provide it to C<Pod::Tree::HTML>
with the C<link_map> option

    sub MyMapper::new { bless {}, shift }
    
    sub MyMapper::url
    {
        my($mapper, $html, $target) = @_;
        ...
	return $url;
    }
    
    $mapper = new MyMapper;
    $html   = new Pod::Tree::HTML link_map => $mapper;

Your object should implement one method

=over 4

=item I<$url> = I<$mapper>->C<url>(I<$html>, I<$target>)

When I<$html>->C<translate>() encounters an C<< LZ<><> >> markup, 
it calls I<$mapper>->C<url>. 
I<$html> is the C<Pod::Tree::HTML> object itself.
I<$target> is a C<Pod::Tree::Node> object representing the 
the target of the link. 
See L<Pod::Tree::Node/target nodes> for information on interpreting I<$target>.

The C<url> method must return a string, 
which will be emitted as the value of the C<href> attribute of an HTML 
anchor: C<< <a href=" >>I<$url>C<< "> >>...C<< </a> >>

C<Pod:Tree:HTML> provides the C<escape_2396> and C<assemble_url>
methods for convenience in implementing link mappers.

=back

If the link mapper does not provide a C<url> method,
C<Pod::Tree::HTML> will call C<map> 

=over 4

=item (I<$base>, I<$page>, I<$section>) = I<$mapper>-E<gt>C<map>(I<$base>, I<$page>, I<$section>, I<$depth>);

Where

=over 4

=item I<$base>

is the URL given in the C<base> option.

=item I<$page>

is the man page named in the LE<lt>E<gt> markup.

=item I<$section>

is the man page section given in the LE<lt>E<gt> markup.

=item I<$depth>

is the value of the C<depth> option.

=back

The C<map> method may perform arbitrary mappings on its arguments.
C<Pod::Tree::HTML> takes the returned values and constructs a URL 
of the form [I<$base>/][I<$page>C<.html>][C<#>I<$fragment>]

=back

The C<map> method is 

=over 4

=item *

deprecated

=item *

less flexible than the C<url> method

=item *

supported for backwards compatability with 
older versions of C<Pod::Tree::HTML>

=back


=head1 DIAGNOSTICS

=over 4

=item C<Pod::Tree::HTML::new: not enough arguments>

(F) C<new> called with fewer than 2 arguments.

=item C<Pod::Tree::HTML::new: Can't load POD from $source>

(F) C<new> couldn't resolve the I<$source> argument.
See L</Source resolution> for details.

=item C<Pod::Tree::HTML::new: Can't write HTML to $dest>

(F) C<new> couldn't resolve the I<$dest> argument.
See L</Destination resolution> for details.

=item C<Pod::Tree::HTML::new: Can't open $dest: $!>

(F) The destination file couldn't be opened.

=back


=head1 SEE ALSO

perl(1), L<C<Pod::Tree>>, L<C<Pod::Tree::Node>>,  L<C<Text::Template>>


=head1 AUTHOR

Steven McDougall, swmcd@world.std.com


=head1 COPYRIGHT

Copyright (c) 1999-2009 by Steven McDougall. This module is free
software; you can redistribute it and/or modify it under the same
terms as Perl itself.
