package Pod::2::DocBook;

use 5.006001;
use strict;
use warnings;

use Digest::MD5 'md5_hex';
use Pod::Parser;
use Pod::ParseLink;
use Text::ParseWords;
use Text::Wrap;

our @ISA     = qw(Pod::Parser);
our $VERSION = '0.01_01';

#----------------------------------------------------------------------
# overridden Pod::Parser methods
#----------------------------------------------------------------------

sub initialize
{
    $_[0]->errorsub ('error_msg');
    $_[0]->{'Pod::2::DocBook::errors'} = [];
}

sub begin_pod
{
    my ($parser) = @_;
    my $out_fh = $parser->output_handle ();

    print $out_fh "<!DOCTYPE $parser->{doctype} ",
      qq#PUBLIC "-//OASIS//DTD DocBook V4.2//EN">\n# if $parser->{header};

    print $out_fh join ("\n",
			'<!--',
			"     Generated by Pod::2::DocBook v$VERSION, using:",
			"       Digest::MD5 v$Digest::MD5::VERSION",
			"       Pod::Parser v$Pod::Parser::VERSION",
			"       Pod::ParseLink v$Pod::ParseLink::VERSION",
			"       Text::ParseWords v$Text::ParseWords::VERSION",
			"       Text::Wrap v$Text::Wrap::VERSION",
			"-->"), "\n";

    $parser->{indentlevel} = 1;

    if ($parser->{doctype} eq 'refentry') {
	print $out_fh join ('',
			     "<refentry>\n",
			     $parser->_indent (),
			     "<refmeta>\n",
			     $parser->_current_indent (),
			     "<refentrytitle>$parser->{title}",
			     "</refentrytitle>\n",
			     $parser->_outdent (),
			     "</refmeta>\n");
    }

    else {
	print $out_fh "<$parser->{doctype}><title>$parser->{title}</title>\n";
    }
}

sub end_pod
{
    my ($parser) = @_;
    my $out_fh = $parser->output_handle ();

    $parser->_transition ('THE END');

    # end document
    print $out_fh "</$parser->{doctype}>\n";
    if (@{$parser->{'Pod::2::DocBook::errors'}}) {
	print $out_fh "\n<!--\n     POD ERRORS:\n";
	foreach my $msg (@{$parser->{'Pod::2::DocBook::errors'}}) {
	    chomp $msg;  # Pod::Parser hands us newlines in errors

	    print $out_fh wrap ("       ", "         ", "o $msg"), "\n";
	}

	print $out_fh "-->\n";
    }
}

sub command {
    my ($parser, $command, $paragraph, $line_num) = @_;
    my $out_fh = $parser->output_handle ();

    return if $command eq 'pod';

    $paragraph =~ s/\s+$//s;
    $paragraph = $parser->interpolate ($paragraph, $line_num);
    $paragraph = _fix_chars ($paragraph);

    if ($command =~ /^head[1-4]/) {
	$parser->_transition ($command);
	$parser->_handle_head ($command, $paragraph, $line_num);
    }

    elsif ($command eq 'begin') {
	$parser->_transition ("begin $paragraph");
	push (@{$parser->{'Pod::2::DocBook::state'}}, "begin $paragraph");
    }

    elsif ($command eq 'end') {
	$parser->_transition ("end $paragraph");
    }

    elsif ($command eq 'for') {
	$parser->_transition ('for');
	if ($paragraph =~ /^(:\S+|docbook)/) {
	    $paragraph =~ s/$1\s+//;
	    print $out_fh $paragraph, "\n";
	}
    }

    elsif ($command eq 'over') {
	$parser->_transition ('over');
	push @{$parser->{'Pod::2::DocBook::state'}}, 'over';
    }

    elsif ($command eq 'item') {
	$parser->_transition ('item');
	$parser->_handle_item ($paragraph, $line_num);
    }

    elsif ($command =~ /^back/) {
	$parser->_transition ('back');
    }

    else {
	my $file = $parser->input_file ();
	$parser->error_msg ("unknown command `$command' at",
			    "line $line_num in file $file");
    }
}

sub textblock {
    my ($parser, $paragraph, $line_num) = @_;
    my $out_fh   = $parser->output_handle ();
    my $state    = pop @{$parser->{'Pod::2::DocBook::state'}};
    my $para_out = '';

    $state = '' unless defined $state;
    $paragraph =~ s/\s+$//s unless $state eq 'begin docbook';

    unless ($state eq 'begin docbook' || $state eq 'begin table') {
	$paragraph = $parser->interpolate ($paragraph, $line_num);
	$paragraph = _fix_chars ($paragraph);
    }

    if ($state eq 'name') {
	my ($name, $purpose) = split (/\s*-\s*/, $paragraph, 2);

	$para_out = join ('',
			  $parser->_indent (),
			  "<refnamediv>\n",
			  $parser->_current_indent (),
			  "<refname>$name</refname>\n",
			  "<refpurpose>$purpose</refpurpose>\n",
			  $parser->_outdent (),
			  "</refnamediv>\n");
    }

    elsif ($state eq 'synopsis+') {
	$para_out = join ('',
			  $parser->_indent (),
			  "<refsynopsisdiv>\n",
			  "<synopsis>$paragraph</synopsis>\n");

	push @{$parser->{'Pod::2::DocBook::state'}}, 'synopsis';
    }

    elsif ($state eq 'synopsis') {
	$para_out = "<synopsis>$paragraph</synopsis>\n";
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state eq 'begin docbook') {
	push @{$parser->{'Pod::2::DocBook::dbpara'}}, $paragraph;
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state eq 'begin table') {
	$parser->_handle_table ($paragraph, $line_num);
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state =~ /^begin [^:]/) {
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state eq 'over') {
	local $Text::Wrap::huge  = 'overflow';   # don't break tags

	$paragraph =~ s/\s*\n\s*/ /g;           # don't just wrap, fill

	$para_out = join ('',
			  $parser->_indent (),
			  "<blockquote>\n",
			  $parser->_indent (),
			  "<para>\n",
			  wrap (' ' x ($parser->{spaces} *
				       $parser->{indentlevel}),
				' ' x ($parser->{spaces} *
				       $parser->{indentlevel}),
				$paragraph),
			  "\n",
			  $parser->_outdent (),
			  "</para>\n");

	push @{$parser->{'Pod::2::DocBook::state'}}, 'indent';
    }

    else {
	local $Text::Wrap::huge = 'overflow';   # don't break tags

	print $out_fh "]]></screen>\n" if $state eq 'verbatim';

	$paragraph =~ s/\s*\n\s*/ /g;           # don't just wrap, fill

	$para_out = join ('',
			  $parser->_indent (),
			  "<para>\n",
			  wrap (' ' x ($parser->{spaces} *
				       $parser->{indentlevel}),
				' ' x ($parser->{spaces} *
				       $parser->{indentlevel}),
				$paragraph),
			  "\n",
			  $parser->_outdent (),
			  "</para>\n");

	$state =~ s/\+$//;
	push @{$parser->{'Pod::2::DocBook::state'}}, $state
	  unless ($state eq 'verbatim' || $state eq '');
    }

    # fix double quotes in ordinary paragraphs if asked to
    if ($state !~ /^begin/ &&
	$parser->{fix_double_quotes} && $para_out =~ /"/) {
	my @protected;
	while ($para_out =~ m#(<[^>"]*".+?>)#s) {
	    # don't modify things that look like tags with quotes inside
	    my $protect = $1 || $2;
	    my $replace = quotemeta ($protect);

	    $para_out =~ s/$replace/\376/;
	    push @protected, $protect;
	}

	$para_out =~ s!"(.+?)"!<quote>$1</quote>!sg;
	foreach my $protect (@protected) {
	    $para_out =~ s/\376/$protect/;
	}
    }

    print $out_fh $para_out;
}

sub verbatim {
    my ($parser, $paragraph, $line_num) = @_;
    my $out_fh = $parser->output_handle ();
    my $state  = pop @{$parser->{'Pod::2::DocBook::state'}} || '';
    my (@lines, $min_leader);

    $paragraph =~ s/\s+$//s unless $state eq 'begin docbook';

    @lines = split (/\n/, $paragraph);
    foreach my $line (@lines) {
	# expand tabs (see perldoc -q 'expand tabs')
	1 while $line =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;

	# find the minimum-length whitespace leader for this paragraph
	my ($leader) = $line =~ /^( +)/;
	$leader ||= '';
	$min_leader  = $leader
	  if (!defined $min_leader ||
	      length ($leader) < length ($min_leader));
    }

    $paragraph = join ("\n", @lines);

    # strip the minimum-length whitespace leader from every line
    $min_leader ||= '';
    $paragraph =~ s/^$min_leader//mg;

    if (!defined $state) {
	print $out_fh $parser->_current_indent (),
	  "<screen><![CDATA[$paragraph";
	push @{$parser->{'Pod::2::DocBook::state'}}, 'verbatim';
    }

    elsif ($state eq 'name') {
	my ($name, $purpose) = split (/\s*-\s*/, $paragraph, 2);

	print $out_fh join ('',
			    $parser->_indent (),
			    "refnamediv>\n",
			    $parser->_current_indent (),
			    "<refname>$name</refname>\n",
			    $parser->_current_indent (),
			    "<refpurpose>$purpose</refpurpose>\n",
			    $parser->_outdent (),
			    "</refnamediv>\n");
    }

    elsif ($state eq 'synopsis+') {
	print $out_fh join ('',
			    $parser->_indent (),
			    "<refsynopsisdiv>\n",
			    "<synopsis>$paragraph</synopsis>\n");

	push @{$parser->{'Pod::2::DocBook::state'}}, 'synopsis';
    }

    elsif ($state eq 'synopsis') {
	print $out_fh "<synopsis>$paragraph</synopsis>\n";
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state eq 'begin docbook') {
	push @{$parser->{'Pod::2::DocBook::dbpara'}}, $paragraph;
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state =~ /^begin [^:]/) {
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    elsif ($state eq 'over') {
	print $out_fh join ('',
			    $parser->_indent (),
			    "<blockquote>\n",
			    $parser->_current_indent (),
			    "<screen><![CDATA[$paragraph");

	push @{$parser->{'Pod::2::DocBook::state'}}, 'indent';
	push @{$parser->{'Pod::2::DocBook::state'}}, 'verbatim';
    }

    elsif ($state eq 'verbatim') {
	print $out_fh "\n\n$paragraph";
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
    }

    else {
	print $out_fh $parser->_current_indent (),
	  "<screen><![CDATA[$paragraph";
	$state =~ s/\+$//;
	push @{$parser->{'Pod::2::DocBook::state'}}, $state;
	push @{$parser->{'Pod::2::DocBook::state'}}, 'verbatim';
    }
}

sub interior_sequence {
    my ($parser, $command, $argument, $seq) = @_;
    my $out_fh = $parser->output_handle ();
    my ($string, @parents);

    # nothing is ever allowed to be nested inside of E<>, or Z<>
    if (my $parent = $seq->nested ()) {
	if ($parent->cmd_name () eq 'E' || $parent->cmd_name () eq 'Z') {
	    my ($file, $line) = $seq->file_line ();
	    $parser->error_msg ("formatting code `$command' nested within",
				"`" . $parent->cmd_name () . "'",
				"at line $line in file $file");
	    return $seq->raw_text ();
	}
    }

    $argument = '' unless defined $argument;

    # the substring "\37632\377" is a space character protected
    # against translation in S<>; other characters are protected at
    # the end of this function, and all protected characters are
    # de-protected in _fix_chars ()

    if ($command eq 'I') {
	$string = qq!<emphasis\37632\377role="italic">$argument</emphasis>!;
    }

    elsif ($command eq 'B') {
	$string = qq!<emphasis\37632\377role="bold">$argument</emphasis>!;
    }

    elsif ($command eq 'C') {
	$string = qq!<literal\37632\377role="code">! .
	  "<![CDATA[$argument]]></literal>";
    }

    elsif ($command eq 'L') {
	$string = $parser->_handle_L ($argument, $seq);
    }

    elsif ($command eq 'E') {
	$string = $parser->_handle_E ($argument, $seq);
    }

    elsif ($command eq 'F') {
	$string = "<filename>$argument</filename>";
    }

    elsif ($command eq 'S') {
	$argument =~ s/\s(?![^<]*>)/&nbsp;/g;
	$string = $argument;
    }

    elsif ($command eq 'X') {
	$string = "<indexterm><primary>$argument</primary></indexterm>";
    }

    elsif ($command eq 'Z') {
	$string = '';
    }

    else {
	my ($file, $line) = $seq->file_line ();
	$parser->error_msg ("unknown formatting code `$command' at line",
			    "in file $file");
	$string = $seq->raw_text ();
    }

    # protect &, <, and > characters from later processing
    # I got this from the first edition Camel Book
    unless ($seq->nested ()) {
	# just do this once, at the top of a subtree so we can
	# report more meaningful errors along the way
	foreach my $char ('&', '<', '>') {
	    $string =~ s/$char/"\376" . ord ($char) . "\377"/eg;
	}
    }

    return $string;
}

#----------------------------------------------------------------------
# other public methods
#----------------------------------------------------------------------

sub error_msg
{
    my $parser = shift;

    push (@{$parser->{'Pod::2::DocBook::errors'}}, join (' ', @_));
}

#----------------------------------------------------------------------
# private methods and helper functions
#----------------------------------------------------------------------

sub _indent
{
    my ($parser) = @_;
    return (' ' x ($parser->{spaces} * $parser->{indentlevel}++));
}

sub _outdent
{
    my ($parser) = @_;
    return (' ' x (--$parser->{indentlevel} * $parser->{spaces}));
}

sub _current_indent
{
    my $parser = shift;

    return ' ' x ($parser->{spaces} * $parser->{indentlevel});
}

sub _make_id
{
    my $parser = shift;
    my $string = join ('-', $parser->{doctype}, $parser->{title}, $_[0]);

    $string =~ s/<!\[CDATA\[(.+?)\]\]>/$1/g;
    $string =~ s/<.+?>//g;

    return 'ID-' . md5_hex ($string);
}

sub _handle_L
{
    my ($parser, $argument, $seq) = @_;
    my $node = $seq;

    # look all the way up the subtree to see if any ancestor is an 'L'
    while ($node = $node->nested ()) {
	if ($node->cmd_name () eq 'L') {
	    my ($file, $line) = $seq->file_line ();
	    $parser->error_msg ("formatting code `L' nested within `L' at",
				"line $line in file $file");
	    return $seq->raw_text ();
	}
    }

    # the substring "\37632\377" is a space character protected
    # against translation in S<>; other characters are protected at
    # the end of interior_sequence (), and all protected characters
    # are de-protected in _fix_chars ()

    my ($text, $inferred, $name, $section, $type) = parselink ($argument);

    if ($type eq 'url') {
	return qq!<ulink\37632\377url="$inferred">$inferred</ulink>!;
    }

    else {
	# types 'man' and 'pod' are handled the same way
	if (defined $section && ! defined $name) {
	    my $id = $parser->_make_id ($section);

	    $section = $text if defined $text;
	    return (qq!<link\37632\377linkend="$id"><quote>$section! .
		    "</quote></link>");
	}

	elsif (defined $text) {
	    return $text;
	}

	elsif (defined $name) {
	    my $string;
	    if ($name =~ /(.+?)\((.+)\)/) {
		$string = $parser->_manpage ($1, $2);
	    }

	    else {
		$string = $parser->_manpage ($name);
	    }

	    if (defined $section) {
		return "<quote>$section</quote> in $string";
	    }

	    else {
		return $string;
	    }
	}

	else {
	    my ($file, $line) = $seq->file_line ();
	    $parser->error_msg ("empty L&lt;&gt; at line",
				"$line in file $file\n");
	    return $seq->raw_text ();
	}
    }
}

sub _handle_E
{
    my ($parser, $argument, $seq) = @_;

    if ($argument !~ /\A\w+\z/) {
	my ($file, $line) = $seq->file_line ();
	$parser->error_msg ("invalid escape `$argument'",
			    "at line $line in file $file\n");
	return $seq->raw_text ();
    }

    elsif ($argument eq 'verbar') {
	return '|';
    }

    elsif ($argument eq 'sol') {
	return '/';
    }

    elsif ($argument eq 'lchevron' || $argument eq 'laquo') {
	return '&#171;';
    }

    elsif ($argument eq 'rchevron' || $argument eq 'raquo') {
	return '&#187;';
    }

    elsif ($argument =~ /^0x/) {
	return ('&#' . hex ($argument) . ';');
    }

    elsif ($argument =~ /^0/) {
	return ('&#' . oct ($argument) . ';');
    }

    elsif ($argument =~ /^\d+$/) {
	return "&#$argument;";
    }

    else {
	return "&$argument;";
    }
}

sub _handle_head
{
    my ($parser, $command, $paragraph, $line_num) = @_;
    my $out_fh        = $parser->output_handle ();

    if ($parser->{doctype} eq 'refentry' &&
	$command eq 'head1' && $paragraph eq 'NAME') {
	push @{$parser->{'Pod::2::DocBook::state'}}, 'name';
    }

    elsif ($parser->{doctype} eq 'refentry' &&
	   $command eq 'head1' && $paragraph eq 'SYNOPSIS') {
	push @{$parser->{'Pod::2::DocBook::state'}}, 'synopsis+';
    }

    else {
	push @{$parser->{'Pod::2::DocBook::state'}}, "$command+";
	my $id = $parser->_make_id ($paragraph);

	if ($parser->{doctype} eq 'refentry') {
	    print $out_fh $parser->_indent (),
	      qq!<refsection id="$id"><title>$paragraph</title>\n!;
	}

	else {
	    print $out_fh $parser->_indent (),
	      qq!<section id="$id"><title>$paragraph</title>\n!;
	}
    }
}

sub _handle_item
{
    my ($parser, $paragraph, $line_num) = @_;
    my $out_fh = $parser->output_handle ();
    my $state  = pop @{$parser->{'Pod::2::DocBook::state'}};

    $state = '' unless defined $state;

    if ($state eq 'verbatim') {
	print $out_fh "]]></screen>\n";
	$state = pop @{$parser->{'Pod::2::DocBook::state'}};
	$state = '' unless defined $state;
    }

    if ($state =~ /list\+$/) {
	print $out_fh $parser->_current_indent (), "<para></para>\n";
    }

    if ($state eq 'over') {
	# first item
	if (!defined ($paragraph) ||
	    $paragraph =~ /^\s*$/  ||
	    $paragraph eq '*') {
	    print $out_fh join ('',
				$parser->_indent (),
				"<para>\n",
				$parser->_indent (),
				"<itemizedlist>\n",
				$parser->_indent (),
				"<listitem>\n");
	    $state = 'list+';
	}

	elsif ($paragraph =~ /^([1aAiI])\.?$/) {
	    my $numeration = { 1 => 'arabic',
			       a => 'loweralpha',
			       A => 'upperalpha',
			       i => 'lowerroman',
			       I => 'upperroman' }->{$1};

	    print $out_fh join ('',
				$parser->_indent (),
				"<para>\n",
				$parser->_indent (),
				qq!<orderedlist numeration="$numeration">\n!,
				$parser->_indent (),
				"<listitem>\n");
	    $state = 'olist+';
	}

	else {
	    my $id = $parser->_make_id ($paragraph);
	    print $out_fh join ('',
				$parser->_indent (),
				"<para>\n",
				$parser->_indent (),
				"<variablelist>\n",
				$parser->_indent (),
				"<varlistentry>\n",
				$parser->_current_indent (),
				qq!<term><anchor id="$id">$paragraph</term>\n!,
				$parser->_indent (),
				qq!<listitem>\n!);
	    $state = 'vlist+';
	}
    }

    elsif ($state =~ /^o?list/) {
	print $out_fh join ('',
			    $parser->_outdent (),
			    "</listitem>\n",
			    $parser->_indent (),
			    "<listitem>\n");
	$state = "$state+" unless $state =~ /\+$/;
    }

    elsif ($state =~ /^vlist/) {
	my $id = $parser->_make_id ($paragraph);
	print $out_fh join ('',
			    $parser->_outdent (),
			    "</listitem>\n",
			    $parser->_outdent (),
			    "</varlistentry>\n",
			    $parser->_indent (),
			    "<varlistentry>\n",
			    $parser->_current_indent (),
			    qq!<term><anchor id="$id">$paragraph</term>\n!,
			    $parser->_indent (),
			    "<listitem>\n");
	$state = 'vlist+';
    }

    else {
	$parser->error_msg ('=item must be inside an',
			    '=over ... =back region',
			    "at line $line_num in file",
			    $parser->input_file ());
    }

    push @{$parser->{'Pod::2::DocBook::state'}}, $state;
}

sub _transition
{
    my ($parser, $what) = @_;
    my $out_fh = $parser->output_handle ();
    my ($level);

    # $level helps us determine what to do when we see =head
    # 1-4 are the valid numbers after '=head', so 0 and 5
    # are safe to use to mark out-of-bounds on either side
    if ($what eq 'THE END') {
	$level = 0;
    }

    elsif ($what =~ /^head(\d)/) {
	$level = $1;
    }

    else {
	$level = 5;
    }

    while (my $state = pop @{$parser->{'Pod::2::DocBook::state'}}) {
	if (($what eq 'item' || $what eq 'over') &&
	    ($state eq 'over' || $state =~ /^(o|v)?list/)) {
	    # these are treated specially in _handle_item ()
	    push @{$parser->{'Pod::2::DocBook::state'}}, $state;
	    last;
	}

	if ($state =~ /list\+$/) {
	    print $out_fh $parser->_current_indent (), "<para></para>\n";
	    $state =~ s/\+$//;
	}

	if ($state =~ /^head(\d)/) {
	    my $prev_level = $1;

	    if ($level > $prev_level) {
		# embed in a previously opened section (i.e. restore
		# state and continue processing the document)

		# the enclosing section is no longer empty
		$state =~ s/\+$//;
		push @{$parser->{'Pod::2::DocBook::state'}}, $state;
		last;
	    }

	    else {
		if ($state =~ /\+$/) {
		    # prevent empty sections
		    print $out_fh $parser->_current_indent (),
		      "<para></para>\n";
		}

		# close the previous section and continue with the stack
		if ($parser->{doctype} eq 'refentry') {
		    print $out_fh $parser->_outdent (), "</refsection>\n";
		}

		else {
		    print $out_fh $parser->_outdent (), "</section>\n";
		}
	    }
	}

	elsif ($state eq 'indent') {
	    print $out_fh $parser->_outdent (), "</blockquote>\n";

	    push @{$parser->{'Pod::2::DocBook::state'}}, 'over'
	      if ($what eq 'item');

	    last if $what eq 'back';
	}

	elsif ($state eq 'list') {
	    print $out_fh join ('',
				$parser->_outdent (),
				"</listitem>\n",
				$parser->_outdent (),
				"</itemizedlist>\n",
				$parser->_outdent (),
				"</para>\n");

	    last if $what eq 'back';
	}

	elsif ($state eq 'olist') {
	    print $out_fh join ('',
				$parser->_outdent (),
				"</listitem>\n",
				$parser->_outdent (),
				"</orderedlist>\n",
				$parser->_outdent (),
				"</para>\n");

	    last if $what eq 'back';
	}

	elsif ($state eq 'vlist') {
	    print $out_fh join ('',
				$parser->_outdent (),
				"</listitem>\n",
				$parser->_outdent (),
				"</varlistentry>\n",
				$parser->_outdent (),
				"</variablelist>\n",
				$parser->_outdent (),
				"</para>\n");

	    last if $what eq 'back';
	}

	elsif ($state =~ /^synopsis/) {
	    print $out_fh join ('',
				$parser->_indent (),
				"<refsynopsisdiv>\n",
				$parser->_current_indent (),
				"<synopsis></synopsis>\n")
	      if $state eq 'synopsis+';

	    print $out_fh $parser->_outdent (), "</refsynopsisdiv>\n";
	}

	elsif ($state eq 'name') {
	    print $out_fh join ('',
				$parser->_indent (),
				"<refnamediv>\n",
				$parser->_indent (),
				"<refname></refname>\n",
				$parser->_current_indent (),
				"<refpurpose></refpurpose>\n",
				$parser->_outdent (),
				"</refnamediv>\n");
	}

	elsif ($state eq 'verbatim') {
	    print $out_fh "]]></screen>\n";
	}

	elsif ($state =~ /^begin (.+)/) {
	    my $begin_format = $1;
	    if ($what =~ /^end (.+)/) {
		my $end_format = $1;

		if ($end_format eq $begin_format) {
		    if ($end_format eq 'docbook') {
			my $paragraph =
			  join ('',
				@{$parser->{'Pod::2::DocBook::dbpara'}});
			$paragraph =~ s/\s+$//;
			print $out_fh $paragraph, "\n";
			$parser->{'Pod::2::DocBook::dbpara'} = [];
		    }

		    last;
		}

		else {
		    # this is bad POD, but we do what we can
		    # (maybe we'll find the begin we're looking for
		    #  deeper in the stack)
		    $parser->error_msg ("`=end $end_format' found",
					'but current region opened with',
					"`=begin $begin_format'");
		}
	    }

	    elsif ($what eq 'THE END') {
		# this is bad POD, but we do what we can
		$parser->error_msg ("no matching `=end' for",
				    "`=begin $begin_format'");

		# we've got the data stored; might as well use it
		if ($begin_format eq 'docbook') {
		    my $paragraph =
		      join ('',
			    @{$parser->{'Pod::2::DocBook::dbpara'}});
		    $paragraph =~ s/\s+$//;
		    print $out_fh $paragraph, "\n";
		    $parser->{'Pod::2::DocBook::dbpara'} = [];
		}
	    }

	    else {
		push @{$parser->{'Pod::2::DocBook::state'}}, $state;
		last;
	    }
	}

	elsif ($state eq 'over') {
	    next;
	}

	else {
	    $parser->error_msg ("encountered unknown state `$state'",
				'(this should never happen)');
	}

    }
}

sub _handle_table
{
    my ($parser, $paragraph, $line_num) = @_;
    my $out_fh = $parser->output_handle ();
    my (@rows, $columns, $title);

    foreach my $row (split (/\n/, $paragraph)) {
	my @fields = quotewords (',', 0, $row);

	$columns = @fields
	  if (!defined $columns || @fields > $columns);
	push @rows, [@fields];
    }

    # the first row specifies the title
    $title = $rows[0]->[0];

    print $out_fh join ('',
			$parser->_indent (),
			"<table>\n",
			$parser->_current_indent (),
			"<title>$title</title>\n",
			$parser->_indent (),
			qq!<tgroup cols="$columns">\n!);

    # the second row specifies column alignments
    foreach my $spec (@{$rows[1]}) {
	print $out_fh $parser->_current_indent (), '<colspec ';

	if (grep { $_ eq $spec } qw(left right center justify)) {
	    print $out_fh qq!align="$spec">\n!;
	}

	else {
	    print $out_fh qq!align="left">\n!;
	    $parser->error_msg ("unknown colspec `$spec' in table",
				$title, "at line $line_num in file",
				$parser->input_file ());
	}
    }

    # the third row (first row of data) is the table header
    print $out_fh join ('',
			$parser->_indent (),
			"<thead>\n",
			$parser->_indent (),
			"<row>\n");

    foreach my $field (@{$rows[2]}) {
	print $out_fh $parser->_current_indent (),
	  "<entry>$field</entry>\n";
    }

    print $out_fh join ('',
			$parser->_outdent (),
			"</row>\n",
			$parser->_outdent (),
			"</thead>\n");

    # the remaining rows are the table body
    print $out_fh $parser->_indent (), "<tbody>\n";

    foreach my $row (@rows[3..$#rows]) {
	print $out_fh $parser->_indent (), "<row>\n";

	foreach my $field (@$row) {
	    print $out_fh $parser->_current_indent (),
	      "<entry>$field</entry>\n";
	}

	print $out_fh $parser->_outdent (), "</row>\n";
    }

    print $out_fh join ('',
			$parser->_outdent (),
			"</tbody>\n",
			$parser->_outdent (),
			"</tgroup>\n",
			$parser->_outdent (),
			"</table>\n");
}

sub _manpage
{
    my ($parser, $title, $volnum) = @_;

    # the substring "\37632\377" is a space character protected
    # against translation in S<>; other characters are protected at
    # the end of interior_sequence (), and all protected characters
    # are de-protected in _fix_chars ()

    if (defined $volnum) {
	return join ("\n",
		     '<citerefentry>',
		     "\37632\377" x $parser->{spaces} .
		     "<refentrytitle>$title</refentrytitle>",
		     "\37632\377" x $parser->{spaces} .
		     "<manvolnum>$volnum</manvolnum>",
		     '</citerefentry>');
    }

    else {
	return join ("\n",
		     '<citerefentry>',
		     "\37632\377" x $parser->{spaces} .
		     "<refentrytitle>$title</refentrytitle>",
		     '</citerefentry>');
    }
}

#----------------------------------------------------------------------
# helper functions
#----------------------------------------------------------------------

sub _fix_chars
{
    my ($paragraph) = @_;

    # fix characters that might annoy an SGML parser
    $paragraph =~ s/&/&amp;/g;
    $paragraph =~ s/</&lt;/g;
    $paragraph =~ s/>/&gt;/g;

    # finally, de-protect any characters that were protected
    # from the previous step
    $paragraph =~ s!\376(\d+)\377!pack ('C', $1)!eg;

    return $paragraph;
}

1;

__END__

=head1 NAME

Pod::2::DocBook - Convert Pod data to DocBook SGML

=head1 SYNOPSIS

  use Pod::2::DocBook;
  my $parser = Pod::2::DocBook->new (title             => 'My Article',
                                     doctype           => 'article',
				  fix_double_quotes => 1,
				  spaces            => 3);

  $parser->parse_from_file ('my_article.pod', 'my_article.sgml');

=head1 DESCRIPTION

Pod::2::DocBook is a module for translating Pod-formatted documents to
DocBook 4.2 SGML (see L<http://www.docbook.org/>).  It is primarily a
back end for B<pod2docbook>, but, as a Pod::Parser subclass, it can be
used on its own.  The only public extensions to the Pod::Parser
interface are options available to C<new()>:

=over

=item doctype

This option sets the output document's doctype.  The currently
supported types are B<article>, B<chapter>, B<refentry> and
B<section>.  Special processing is performed when the doctype is set
to B<refentry> (see L</Document Types>).  You I<must> set this option
in order to get valid DocBook output.

=item fix_double_quotes

If this option is set to a true value, pairs of double quote
characters ('"') in ordinary paragraphs will be replaced with
B<E<lt>quoteE<gt>> and B<E<lt>/quoteE<gt>>.  See L</Ordinary
Paragraphs> for details.

=item header

If this option is set to a true value, Pod::2::DocBook will emit a
DOCTYPE as the first line of output.

=item spaces

Pod::2::DocBook produces pretty-printed output.  This option sets the
number of spaces per level of indentation in the output.

=item title

This option sets the output document's title.

=back

The rest of this document only describes issues specific to
Pod::2::DocBook; for details on invoking the parser, specifically the
C<new()>, C<parse_from_file()> and C<parse_from_filehandle()> methods,
see L<Pod::Parser>.

=head1 POD TO DOCBOOK TRANSLATION

Pod is a deceptively simple format; it is easy to learn and very
straightforward to use, but it is suprisingly expressive.
Nevertheless, it is not nearly as expressive or complex as DocBook.
In most cases, given some Pod, the analogous DocBook markup is
obvious, but not always.  This section describes how Pod::2::DocBook
treats Pod input so that Pod authors may make informed choices.  In
every case, Pod::2::DocBook strives to make easy things easy and hard
things possible.

The primary motivation behind Pod::2::DocBook is to facilitate
single-source publishing.  That is, you should be able to generate man
pages, web pages, PDF and PostScript documents, or any other format
your SGML and/or Pod tools can produce, from the same Pod source,
without the need for hand-editing any intermediate files.  This may
not always be possible, or you may simply choose to render Pod to
DocBook and use that as your single source.  To satisfy the first
requirement, Pod::2::DocBook always processes the entire Pod source and
tries very hard to produce valid DocBook markup, even in the presence
of malformed Pod (see L</DIAGNOSTICS>).  To satisfy the second
requirement (and to be a little nifty), Pod::2::DocBook pretty-prints its
output.  If you're curious about what specific output to expect, read
on.

=head2 Document Types

DocBook's structure is very modular; many of its document types can be
embedded directly into other documents.  Accordingly, Pod::2::DocBook
will generate four different document types: B<article>, B<chapter>,
B<refentry>, and B<section>.  This makes it easy, for instance, to
write all the chapters of a book in separate Pod documents, translate
them into DocBook markup and later glue them together before
processing the entire book.  You could do the same with each section
in an article, or you could write the entire article in a single Pod
document.  Other document types, such as B<book> and B<set>, do not
map easily from Pod, because they require structure for which there is
no Pod equivalent.  But given sections and chapters, making larger
documents becomes much simpler.

The B<refentry> document type is a little different from the others.
Sections, articles, and chapters are essentially composed of nested
sections.  But a refentry has specialized elements for the I<NAME> and
I<SYNOPSIS> sections.  To accommodate this, Pod::2::DocBook performs
extra processing on the Pod source when the B<doctype> is set to
B<refentry>.  You probably don't have to do anything to your document
to assist the processing; typical man page conventions cover the
requirements.  Just make sure that the I<NAME> and I<SYNOPSIS> headers
are both B<=head1>s, that "NAME" and "SYNOPSIS" are both uppercase,
and that B<=head1 NAME> is the first line of Pod source.

=head2 Ordinary Paragraphs

Ordinary paragraphs in a Pod document translate naturally to DocBook
paragraphs.  Specifically, after any formatting codes are processed,
the characters C<E<lt>>, C<E<gt>> and C<E<amp>> are translated to
their respective SGML character entities, and the paragraph is wrapped
in B<E<lt>paraE<gt>> and B<E<lt>/paraE<gt>>.

For example, given this Pod paragraph:

  Here is some text with I<italics> & an ampersand.

Pod::2::DocBook would produce DocBook markup similar to this:

  <para>
    Here is some text with <emphasis role="italic">italics</emphasis>
    &amp; an ampersand.
  </para>

Depending on your final output format, you may sometimes want double
quotes in ordinary paragraphs to show up ultimately as "smart quotes"
(little 66s and 99s).  Pod::2::DocBook offers a convenient mechanism for
handling double quotes in ordinary paragraphs and letting your SGML
toolchain manage their presentation: the B<fix_double_quotes> option
to C<new()>.  If this option is set to a true value, Pod::2::DocBook will
replace pairs of double quotes in ordinary paragraphs (and I<only> in
ordinary paragraphs) with B<E<lt>quoteE<gt>> and B<E<lt>/quoteE<gt>>.

For example, given this Pod paragraph:

  Here is some text with I<italics> & an "ampersand".

Pod::2::DocBook, with B<fix_double_quotes> set, would produce DocBook
markup similar to this:

  <para>
    Here is some text with <emphasis role="italic">italics</emphasis>
    &amp; an <quote>ampersand</quote>.
  </para>

If you have a paragraph with an odd number of double quotes, the last
one will be left untouched, which may or may not be what you want.  If
you have such a document, replace the unpaired double quote character
with B<< EE<lt>quotE<gt> >>, and Pod::2::DocBook should be able to give
you the output you expect.  Also, if you have any
S<< B<=begin docbook> >> ... S<< B<=end docbook> >> regions (see
L</Embedded DocBook Markup>) in your Pod, you are responsible for
managing your own quotes in those regions.

=head2 Verbatim Paragraphs

Verbatim paragraphs translate even more naturally; L<perlpodspec>
mandates that absolutely no processing should be performed on them.
So Pod::2::DocBook simply marks them as CDATA and wraps them in
B<E<lt>screenE<gt>> and B<E<lt>/screenE<gt>>.  They are not indented
the way ordinary paragraphs are, because they treat whitespace as
significant.

For example, given this verbatim paragraph (imagine there's leading
whitespace in the source):

  my $i = 10;
  while (<> && $i--) {
      print "$i: $_";
  }

Pod::2::DocBook would produce DocBook markup similar to this:

  <screen><![CDATA[my $i = 10;
  while (<> && $i--) {
      print "$i: $_";
  }]] ></screen>

Multiple contiguous verbatim paragraphs are treated as a single
I<screen> element, with blank lines separating the paragraphs, as
dictated by L<perlpodspec>.

=head2 Command Paragraphs

=over

=item C<=head1 Heading Text>

=item C<=head2 Heading Text>

=item C<=head3 Heading Text>

=item C<=head4 Heading Text>

All of the Pod heading commands produce DocBook I<section> elements,
with the heading text as titles.  Pod::2::DocBook (L<perlpod>) only
allows for 4 heading levels, but DocBook allows arbitrary nesting; see
L</Embedded DocBook Markup> if you need more than 4 levels.
Pod::2::DocBook only looks at relative heading levels to determine
nesting.  For example, this bit of Pod:

  =head1 1

  Contents of section 1

  =head2 1.1

  Contents of section 1.1

and this bit of Pod:

  =head1 1

  Contents of section 1

  =head3 1.1

  Contents of section 1.1

both produce the same DocBook markup, which will look something like
this:

  <section id="article-My-Article-1"><title>1</title>
    <para>
      Contents of section 1
    </para>
    <section id="article-My-Article-1-1"><title>1.1</title>
      <para>
        Contents of section 1.1
      </para>
    </section>
  </section>

Note that Pod::2::DocBook automatically generates section identifiers
from your doctype, document title and section title.  It does the same
when you make internal links (see L</Formatting Codes>, ensuring that
if you supply the same link text as you did for the section title, the
resulting identifiers will be the same.

=item C<=over indentlevel>

=item C<=item stuff...>

=item C<=back>

C<=over> ... C<=back> regions are somewhat complex, in that they can
lead to a variety of DocBook constructs.  In every case,
I<indentlevel> is ignored by Pod::2::DocBook, since that's best left to
your stylesheets.

An C<=over> ... C<=back> region with no C<=item>s represents indented
text and maps directly to a DocBook I<blockquote> element.  Given this
source:

  =over 4

  This text should be indented.

  =back

Pod::2::DocBook will produce DocBook markup similar to this:

  <blockquote>
    <para>
      This text should be indented.
    </para>
  </blockquote>

Inside an C<=over> ... C<=back> region, C<=item> commands generate
lists.  The text that follows the first C<=item> determines the type
of list that will be output:

=over

=item *

"*" (an asterisk) produces B<E<lt>itemizedlistE<gt>>

=item *

"1" or "1." produces S<< B<E<lt>orderedlist numeration="arabic"E<gt>> >>

=item *

"a" or "a." produces S<< B<E<lt>orderedlist numeration="loweralpha"E<gt>> >>

=item *

"A" or "A." produces S<< B<E<lt>orderedlist numeration="upperalpha"E<gt>> >>

=item *

"i" or "i." produces S<< B<E<lt>orderedlist numeration="lowerroman"E<gt>> >>

=item *

"I" or "I." produces S<< B<E<lt>orderedlist numeration="upperroman"E<gt>> >>

=item *

anything else produces B<E<lt>variablelistE<gt>>

=back

Since the output from each of these is relatively verbose, the best
way to see examples is to actually render some Pod into DocBook.

=item C<=pod>

=item C<=cut>

L<Pod::Parser> recognizes these commands, and, therefore, so does
Pod::2::DocBook, but they don't produce any output.

=item C<=begin formatname>

=item C<=end formatname>

=item C<=for formatname text...>

Pod::2::DocBook supports two formats: B<docbook>, explained in
L</Embedded DocBook Markup>, and B<table>, explained in L</Simple
Tables>.

=item C<=encoding encodingname>

This command is currently not supported.  If Pod::2::DocBook encounters a
document that contains C<=encoding>, it will ignore the command and
report an error (L</unknown command `%s' at line %d in file %s>).

=back

=head3 Embedded DocBook Markup

There are a wide range of DocBook structures for which there is no Pod
equivalent.  For these, you will have to provide your own markup using
B<=begin docbook> ... B<=end docbook> or B<=for docbook ...>.
Pod::2::DocBook will directly output whatever text you provide,
unprocessed, so it's up to you to ensure that it's valid DocBook.

Images, footnotes and many inline elements are obvious candidates for
embedded markup.  Another possible use is nesting sections more than
four-deep.  For example, given this source:

  =head1  1

  This is Section 1

  =head2 1.1

  This is Section 1.1

  =head3 1.1.1

  This is Section 1.1.1

  =head4 1.1.1.1

  This is Section 1.1.1.1

  =begin docbook

  <section>
  <title>1.1.1.1.1</title>
  <para>This is Section 1.1.1.1.1</para>
  </section>

  =end docbook

Pod::2::DocBook will generate DocBook markup similar to this:


    <section id="article-My-Article-1"><title>1</title>
      <para>
        This is Section 1
      </para>
      <section id="article-My-Article-1-1"><title>1.1</title>
        <para>
	  This is Section 1.1
        </para>
        <section id="article-My-Article-1-1-1"><title>1.1.1</title>
          <para>
	    This is Section 1.1.1
          </para>
          <section id="article-My-Article-1-1-1-1"><title>1.1.1.1</title>
            <para>
	      This is Section 1.1.1.1
            </para>
  <section>
  <title>1.1.1.1.1</title>
  <para>This is Section 1.1.1.1.1</para>
  </section>
          </section>
        </section>
      </section>
    </section>

=head3 Simple Tables

Pod::2::DocBook also provides a mechanism for generating basic tables
with S<< B<=begin table> >> and S<< B<=end docbook> >>.  If you have
simple tabular data or a CSV file exported from some application,
Pod::2::DocBook makes it easy to generate a table from your data.  The
syntax is intended to be simple, so DocBook's entire table feature set
is not represented, but even if you do need more complex table markup
than Pod::2::DocBook produces, you can rapidly produce some markup which
you can hand-edit and then embed directly in your Pod with
S<< B<=begin docbook> >> ... S<< B<=end docbook> >>.  Each table
definition spans multiple lines, so there is no equivalent
S<< B<=for table> >> command.

The first line of a table definition gives the table's title.  The
second line gives a list of comma-separated column specifications
(really just column alignments), each of which can be B<left>,
B<center> or B<right>.  The third line is a list of comma-separated
column headings, and every subsequent line consists of comma-separated
row data.  If any of your data actually contain commas, you can
enclose them in double quotes; if they also contain double quotes, you
must escape the inner quotes with backslashes (typical CSV stuff).

Here's an example:

  =begin table

  Sample Table
  left,center,right
  Powers of Ten,Planets,Dollars
  10,Earth,$1
  100,Mercury,$5
  1000,Mars,$10
  10000,Venus,$20
  100000,"Jupiter, Saturn",$50

  =end table

And here's what Pod::2::DocBook would do with it:

  <table>
    <title>Sample Table</title>
    <tgroup cols="3">
      <colspec align="left">
      <colspec align="center">
      <colspec align="right">
      <thead>
        <row>
          <entry>Powers of Ten</entry>
          <entry>Planets</entry>
          <entry>Dollars</entry>
        </row>
      </thead>
      <tbody>
        <row>
          <entry>10</entry>
          <entry>Earth</entry>
          <entry>$1</entry>
        </row>
        <row>
          <entry>100</entry>
          <entry>Mercury</entry>
          <entry>$5</entry>
        </row>
        <row>
          <entry>1000</entry>
          <entry>Mars</entry>
          <entry>$10</entry>
        </row>
        <row>
          <entry>10000</entry>
          <entry>Venus</entry>
          <entry>$20</entry>
        </row>
        <row>
          <entry>100000</entry>
          <entry>Jupiter, Saturn</entry>
          <entry>$50</entry>
        </row>
      </tbody>
    </tgroup>
  </table>

=head2 Formatting Codes

Pod formatting codes render directly into DocBook as inline elements:

=over

=item *

C<< IZ<><text> >>

  <emphasis role="italic">text</emphasis>

=item *

C<< BZ<><text> >>

  <emphasis role="bold">text</emphasis>

=item *

C<< CZ<><code> >>

  <literal role="code"><![CDATA[code]] ></literal>

=item *

C<< LZ<><name> >>

  <citerefentry><refentrytitle>name</refentrytitle></citerefentry>

=item *

C<< LZ<><name(n)> >>

  <citerefentry><refentrytitle>name</refentrytitle>
  <manvolnum>n</manvolnum></citerefentry>


=item *

C<< LZ<><name/"sec"> >> or C<< LZ<><name/sec> >>

  <quote>sec</quote> in <citerefentry>
  <refentrytitle>name</refentrytitle></citerefentry>

=item *

C<< LZ<><name(n)/"sec"> >> or C<< LZ<><name(n)/sec> >>

  <quote>sec</quote> in <citerefentry>
  <refentrytitle>name</refentrytitle><manvolnum>n</manvolnum>
  </citerefentry>

=item *

C<< LZ<></"sec"> >> or C<< LZ<></sec> >> or  C<< LZ<><"sec"> >>

  <link linkend="article-My-Article-sec"><quote>sec</quote></link>

=item *

C<< LZ<><text|name> >>

  text

=item *

C<< LZ<><text|name/"sec"> >> or C<< LZ<><text|name/sec> >>

  text

=item *

C<< LZ<><text|/"sec"> >> or C<< LZ<><text|/sec> >> or C<< LZ<><text|"sec"> >>

  <link linkend="article-My-Article-sec"><quote>text</quote></link>

=item *

C<< LZ<><scheme:...> >>

  <ulink url="scheme:...">scheme:...</ulink>

=item *

C<< EZ<><verbar> >>

  |

=item *

C<< EZ<><sol> >>

  /

=item *

C<< EZ<><number> >>

  &#number;

=item *

any other C<< EZ<><escape> >>

  &escape;

=item *

C<< FZ<><filename> >>

<filename>filename</filename>

=item *

C<< SZ<><text with spaces> >>

 text&nbsp;with&nbsp;spaces

=item *

C<< XZ<><topic name> >>

<indexterm><primary>topic name</primary></indexterm>

=back

=head1 DIAGNOSTICS

Pod::2::DocBook makes every possible effort to produce valid DocBook
markup, even with malformed POD source.  Any processing errors will be
noted in comments at the end of the output document.  Even when errors
occur, Pod::2::DocBook always reads the entire input document and never
exits with a non-zero status.


=over

=item unknown command `%s' at line %d in file %s

See L<perlpod/Command Paragraph> for a list of valid commands.  The
command referenced in the error message was ignored.

=item formatting code `%s' nested within `%s' at line %d in file %s

See L<perlpod/Formatting Codes> for details on which formatting codes
can be nested.  The offending code was translated into the output
document as the raw text inside its angle brackets.

=item unknown formatting code `%s' at line in file %s

The input contained a formatting code not listed in L<perlpod>; it was
translated into the output document as the raw text inside the angle
brackets.

=item empty LZ<><> at line %d in file %s

Self-explanatory.

=item invalid escape `%s' at line %d in file %s

Self-explanatory; it was translated into the output document as the
raw text inside the angle brackets.

=item =item must be inside an =over ... =back section at line %d in file %s

Self-explanatory.  The `=item' referenced in the error was ignored.

=item `=end %s' found but current region opened with `=begin %s'

The closest `=end' command to the referenced `=begin' didn't match;
processing continued as if the mismatched `=end' wasn't there.

=item no matching `=end' for `=begin %s'

Pod::2::DocBook reached the end of its input without finding an `=end'
command to match the `=begin' referenced in the error; end-of-file
processing continued.

=item unknown colspec `%s' in table at line %d in file %s

See L</Simple Tables> for a list of supported column specifications.

=item encountered unknown state `%s' (this should never happen)

The state referred to is an internal variable used to properly manage
nested DocBook structures.  You should indeed never see this message,
but if you do, you should contact the module's author.

=back

=head1 SEE ALSO

L<pod2docbook>, L<perlpod>, L<http://www.docbook.org/>, L<Pod::DocBook>

=head1 AUTHOR

Alligator Descartes <descarte@symbolstone.org> wrote a module called
Pod::2::DocBook, which was later maintained by Jan Iven
<jan.iven@cern.ch>.  That module was based on the original L<pod2html>
by Tom Christiansen <tchrist@mox.perl.com>.

Nandu Shah <nandu@zvolve.com> wrote Pod::DocBook, which is
unrelated to the previous module (even though they both perform the
same function).

Jozef Kutej <jkutej@cpan.org> renamed the module to Pod::2::DocBook
because Nandus version was buried in the CPAN archive as an
"UNAUTHORIZED RELEASE". Still hoping to get in contact with Nandu...

=head1 COPYRIGHT

Copyright 2004, Nandu Shah <nandu@zvolve.com>

This library is free software; you may redistribute it and/or modify
it under the same terms as Perl itself

=cut
