#!/usr/bin/perl
$DEFAULT_UTF8TRANS_PROGRAM = q</usr/bin/utf8trans>;
$DEFAULT_UTF8TRANS_ROFFMAP = q</usr/share/docbook2X/charmaps/roff.charmap>;
$DEFAULT_UTF8TRANS_TEXIMAP = q</usr/share/docbook2X/charmaps/texi.charmap>;
$DEFAULT_ICONV_PROGRAM = q</usr/bin/iconv>;
$DOCBOOK2X_VERSION = q<0.8.3>;
$DEFAULT_DB2X_XSLTPROC_PROGRAM = q</usr/bin/db2x_xsltproc>;
$DEFAULT_DB2X_MANXML_PROGRAM = q</usr/bin/db2x_manxml>;
$DEFAULT_DB2X_TEXIXML_PROGRAM = q</usr/bin/db2x_texixml>;

#!/usr/bin/perl
# vim:sw=4 et sta showmatch

# db2x_texixml - convert Texi-XML to Texinfo
#                (See DocBook documentation for details)
#
# Copyright (C) 2000-2002 Steve Cheng <stevecheng@users.sourceforge.net>
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be included
# in allcopies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALNGS IN THE SOFTWARE.
#

use strict;




##################################################
#
#
# Option parsing
#
##################################################

# Configuration variables from config.pl
use vars qw($DEFAULT_UTF8TRANS_PROGRAM
            $DEFAULT_UTF8TRANS_TEXIMAP
            $DEFAULT_ICONV_PROGRAM
            $DOCBOOK2X_VERSION

            $DEFAULT_DB2X_XSLTPROC_PROGRAM
            $DEFAULT_DB2X_MANXML_PROGRAM
            $DEFAULT_DB2X_TEXIXML_PROGRAM
            $DEFAULT_UTF8TRANS_ROFFMAP);
# The last variables are to silence variable-not-used warnings.

$DEFAULT_UTF8TRANS_PROGRAM = 'utf8trans'
    if !defined $DEFAULT_UTF8TRANS_PROGRAM;
$DEFAULT_UTF8TRANS_TEXIMAP = 'texi.charmap' 
    if !defined $DEFAULT_UTF8TRANS_TEXIMAP;
$DEFAULT_ICONV_PROGRAM = 'iconv' 
    if !defined $DEFAULT_ICONV_PROGRAM;

use Getopt::Long;
Getopt::Long::Configure('bundling');
my $cmdoptions = {
    'encoding' => 'us-ascii',
    'list-files' => 0,
    'to-stdout' => 0,
    'info' => 0,
    'utf8trans-program' => $DEFAULT_UTF8TRANS_PROGRAM,
    'utf8trans-map' => $DEFAULT_UTF8TRANS_TEXIMAP,
    'iconv-program' => $DEFAULT_ICONV_PROGRAM,
};

sub options_help {
    print "Usage: $0 [OPTION]... [FILE]...\n";
    print <<'end';
Make Texinfo documents from Texi-XML

  --encoding=ENCODING   Character encoding for Texinfo files
                        Default is US-ASCII
  --list-files          Write list of output files to stdout
  --to-stdout           Write output to stdout instead of to files
  --info                Pipe output to makeinfo, creating Info files directly
  
  These options set the location of auxiliary programs:
  --utf8trans-program=PATH, --utf8trans-map=PATH, --iconv-program=PATH

  --help                Show this help and exit
  --version             Show version and exit
end
    exit 0;
}

sub options_version
{
    print "db2x_texixml (part of docbook2X ${DOCBOOK2X_VERSION})\n";
    print <<'end';
$Revision: 1.19 $ $Date: 2004/07/15 22:03:26 $
<URL:http://docbook2x.sourceforge.net/>

Copyright (C) 2000-2002 Steve Cheng
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
end
    exit 0;
}

$SIG{__WARN__} = sub { print STDERR "$0: " . $_[0]; };
if(!GetOptions($cmdoptions,
    'encoding=s',
    'list-files',
    'to-stdout',
    'info',
    'utf8trans-program=s',
    'utf8trans-map=s',
    'iconv-program=s',
    'help', \&options_help,
    'version', \&options_version))
{
    print STDERR "Try \"$0 --help\" for more information.\n";
    exit 1;
}
$SIG{__WARN__} = undef;

use XML::Handler::SGMLSpl;
my $texixmldata = { 'options' => $cmdoptions };
$texixml::templates = XML::Handler::SGMLSpl->new($texixmldata);
$texixml::templates->push_mode('file-unselected');



package texixml;
use vars qw($templates);

##################################################
#
# Output routines
#
##################################################

sub shell_quote
{
    join(' ', map { my $u = $_;
                    $u =~ s#([\$`"\\\n])#\\$1#g; 
                    '"' . $u . '"' } @_);
}

sub output {
    my $text = shift;

    if($text =~ s/^\n//) {
        # Force newline if needed
        print OUT "\n" unless $texixml::newline_last++;
    }
    return if $text eq '';

    print OUT $text;

    $texixml::newline_last = ($text =~ /\n$/);
}

sub texi_arg_escape
{
    my $s = shift;
    $s =~ tr/,/./;
    return $s;
}

# Escape Texinfo syntax chars
#
sub texi_escape
{
    my $s = shift;
    $s =~ s/([\@\{\}])/\@$1/g;
    return $s;
}

# Allows output to be saved when $texixml::output_save is set,
# otherwise same as output.
#
# Savable output does not stack, unlike SGMLS::Output.
#
# When saving output, this function does not have any line-breaking 
# semantics of regular output() and will NOT work with block-level
# elements. (Saving of arbitrary output has many other complications
# which we will not attempt solve here to solve in the interests of 
# minimizing code bloat.)
#
sub savable_output
{
    if(defined $texixml::output_save) {
        $texixml::output_save .= shift;
    } else {
        &output;
    }
}



    
##################################################
#
# A clean solution to the extra-newlines problem
#
##################################################

# In essence, texi_xml usually puts newlines only where you 
# expect a human editing a Texinfo file directly would.
# The heuristic works by checking if we are at the beginning
# of a line or not in our output ($texixml::newline_last),
# and if so, refrain from putting too many newlines
# (which would actually produce 2 or more blank lines).
#
# texi_xml also keeps track of what type of element (block, inline 
# or neither) it just processed, then makes line breaks only
# if it is required to separate them.
#
# This is not complete "whitespace collapsing", but since Texinfo
# is reasonably tolerant in its whitespace handling we don't need
# to have a model that collapses whitespace perfectly in every case.
# 
# 
sub block_start
{
    my ($self, $elem) = @_;

    if(defined $texixml::output_save) {
        die "$0: block_start called while saving output (BUG)";
    }

    output "\n\n"
        unless ($elem->in('listitem') and 
                $elem->parent->ext->{lastchild} eq '')
            or ($elem->in('entry') and
                $elem->parent->ext->{lastchild} eq '');
            # Don't put blank before the first block in 
            # varlistentries and entries of a multitable.

    $elem->parent->ext->{lastchild} = 'block';
}


sub mixed_inline_start
{
    my ($self, $node) = @_;
    
    if(defined $texixml::output_save) {
        return;
    }
    
    # Example:
    # <para>Warning<itemize>...</itemize>Do not indent this text
    # since it's part of the same paragraph</para>

    output "\n\n\@noindent\n" 
        if $node->parent->ext->{lastchild} eq 'block';

    $node->parent->ext->{lastchild} = 'inline';
}




##################################################
#
# Texinfo preamble and eof
#
##################################################

$templates->add_rule('texinfoset<', 'file-unselected', sub {});
$templates->add_rule('texinfoset>', 'file-unselected', sub {});

$templates->add_rule('texinfo<', 'file-unselected', sub {
    my ($self, $elem, $templates) = @_;

    my $basename;
    if($elem->attr('file') ne '') {
        $basename = $elem->attr('file');
    } elsif($self->{inputfile} ne '-') {
        $basename = $self->{inputfile};
        $basename =~ s/\.txml$//;
    } else {
        $basename = 'untitled';
    }
    
    my $filename = "${basename}.texi";
    my $encoding = $self->{options}->{encoding};

    my $openstr = '';
    
    if($encoding !~ /^utf|ucs/i
        and $self->{options}->{'utf8trans-program'} ne '')
    {
        $openstr .= '| ' .
            shell_quote($self->{options}->{'utf8trans-program'},
                        $self->{options}->{'utf8trans-map'})
            . ' ';
    }
    
    if($encoding !~ /^utf-?8$/i
        and $self->{options}->{'iconv-program'} ne '')
    {
        $openstr .= '| ' .
            shell_quote($self->{options}->{'iconv-program'},
                        '-f', 'utf-8',
                        '-t', $encoding)
            . ' ';
    }

    if($self->{options}->{'info'}) {
        $openstr .= '| makeinfo';

        if($self->{options}->{'to-stdout'}) {
            $openstr .= ' -o -';
        } else {
            print "${basename}.info\n"
                if $self->{options}->{'list-files'};
        }
    }

    if($openstr eq '') {
        if(!$self->{options}->{'to-stdout'}) {
            $openstr = $filename;
            # Trick from Perl FAQ to open file with arbitrary characters
            $openstr =~ s#^(\s)#./$1#;
            $openstr = ">${openstr}\0";
            print "$filename\n"
                if $self->{options}->{'list-files'};
        } else {
            $openstr = '>-';
        }
    }
    elsif(!$self->{options}->{'info'}) {
        if(!$self->{options}->{'to-stdout'}) {
            $openstr .= '> ' . shell_quote($filename);
            print "$filename\n"
                if $self->{options}->{'list-files'};
        }
    }

    open(OUT, $openstr) 
        or die "Failed to open: $openstr: $!\n";
    
    # Set output encoding to UTF-8 on Perl >=5.8.0
    # so it doesn't complain
    binmode(OUT, ":utf8") unless $] < 5.008;
    
    $texixml::newline_last = 1;

    $self->{basename} = $basename;
    
    output("\\input texinfo\n");
    output("\n\@setfilename ${basename}.info\n");

    $encoding =~ s#//TRANSLIT$##;
    output("\@documentencoding $encoding\n");

    $templates->pop_mode();
});

$templates->add_rule('texinfo>', 'file-unselected', sub {
    output("\n\n\@bye\n");
    close(OUT);
    
    $templates->push_mode('file-unselected');
});





##################################################
#
# Simple title pages
#
##################################################

$templates->add_rule('metatitlepage<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@titlepage\n";
});
$templates->add_rule('metatitlepage>', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@end titlepage\n";
});
$templates->add_rule('title<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@title ";
    $templates->push_mode('single-line-mode');
});
$templates->add_rule('title>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n";
});
$templates->add_rule('subtitle<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@subtitle ";
    $templates->push_mode('single-line-mode');
});
$templates->add_rule('subtitle>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n";
});
$templates->add_rule('author<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@author ";
    $templates->push_mode('single-line-mode');
});
$templates->add_rule('author>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n";
});




##################################################
#
# Menus, nodes
#
##################################################

# Do escaping for nodenames:
# NOTE: stylesheets should do this if possible
# since there can be rare name clashes.
sub node_escape
{
    my $name = shift;
    for ($name) {
        tr/().,:'/[]_;;_/;
        tr/ \t\n/ /s;
        s/^ +//mg;
        s/ +$//mg;
    }
    return $name;
}


$templates->add_rule('node<', sub {
    my ($self, $elem, $templates) = @_;
    my $node = node_escape(texi_escape($elem->attr('name')));

    if(defined $elem->attr('next') or
       defined $elem->attr('previous') or
       defined $elem->attr('up'))
    {
        my $next = node_escape(texi_escape($elem->attr('next')));
        my $previous = node_escape(texi_escape($elem->attr('previous')));
        my $up = node_escape(texi_escape($elem->attr('up')));

        if($node =~ /^[Tt]op$/ and !defined $elem->attr('up')) {
            $up = '(dir)';
        }

        output "\n\n\@node ${node}, ${next}, ${previous}, ${up}\n";
    } else {
        output "\n\n\@node $node\n";
    }
});

$templates->add_rule('menu<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    output "\@menu\n";
    $templates->push_mode('menu-mode');
});
$templates->add_rule('menu>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n\@end menu\n";
});
$templates->add_rule('detailmenu<', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    output "\@detailmenu\n";
});
$templates->add_rule('detailmenu>', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@end detailmenu\n";
});
$templates->add_rule('menuline<', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    $templates->push_mode('saved-text-mode');
    $texixml::output_save = '';
});
$templates->add_rule('menuline>', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();

    my $s = $texixml::output_save;
    $texixml::output_save = undef;

    output $s . "\n";
    output "\n\n" if($s eq '');
});

$templates->add_rule('menuentry<', 'menu-mode', sub {});
$templates->add_rule('menuentry>', 'menu-mode', sub {
    output "\n";
});

$templates->add_rule('menuentrytitle<', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    $templates->push_mode('saved-text-ignore-markup-mode');
    $texixml::output_save = '';
});

$templates->add_rule('menuentrytitle>', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();

    my $entry = $texixml::output_save;
    $texixml::output_save = undef;
    
    # Since the contents of @menu is supposed to be "pre-formatted",
    # Texinfo will be picky about extra spaces.
    # Eliminate them here.
    $entry =~ tr/ / /s;
    $entry =~ s/^ //;


    # Although the menu entry is not constrained to the set
    # of characters allowed for node names, the use of ':'
    # to separate the parts of menu entry implies that it
    # is not an allowed character.
    $entry = node_escape($entry);
    
    my $node = node_escape(texi_escape(
        $elem->parent->attr('node')));

    # The eventual output
    my $s;
    
    output "\n";
    
    if($elem->parent->attr('file') ne '') {
        # This element is overloaded for direntry, and there
        # we need to specify the file.
        my $file = texi_escape($elem->parent->attr('file'));
        
        $s = "* ${entry}: (${file})${node}.";
    
    } else {
        if($entry eq $node) {
            $s = "* ${entry}::";
        } else {
            $s = "* ${entry}: ${node}.";
        }
    }

    output $s;

    $elem->parent->ext->{'entry_length'} = length($s);
});

use Text::Wrap ();
$templates->add_rule('menuentrydescrip<', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    $templates->push_mode('saved-text-mode');
    $texixml::output_save = '';
});
$templates->add_rule('menuentrydescrip>', 'menu-mode', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();

    my $text = $texixml::output_save;
    $texixml::output_save = undef;
    
    # Since the contents of @menu is supposed to be "pre-formatted",
    # Texinfo will be picky about extra spaces.
    # Eliminate them here.
    $text =~ tr/ / /s;
    $text =~ s/^ //;
        
    my $entry_length = $elem->parent->ext->{'entry_length'};
    
    my $first_line_padding = 
        $entry_length<32 ? 32-$entry_length : 3;
    my $first_line_overflow = 0;

    my $start_column = 
        $entry_length + $first_line_padding + 2;
        
    if($start_column > 50) {
        $first_line_overflow = 1;
        $start_column = 50;
    }


    $Text::Wrap::columns = 78 - $start_column;
    
    my @lines = split(/(\n)/, Text::Wrap::wrap("", "", $text));

    if(!$first_line_overflow) {
        my $first_line = shift @lines;
        if($first_line) {
            output((' ' x $first_line_padding) . $first_line);
        }
    } else {
        output "\n";
    }

    foreach my $line (@lines) {
        if($line eq "\n") {
            output "\n";
        } else {
            output((' ' x $start_column) . $line);
        }
    }
});
    



##################################################
#
# Info directory
#
##################################################

$templates->add_rule('directory<', sub {
    my ($self, $elem, $templates) = @_;

    if(defined $elem->attr('category')) {
        output "\n\@dircategory " . 
            texi_escape($elem->attr('category')) . "\n";
    }
    
    output "\n\@direntry\n";
    $templates->push_mode('menu-mode');
});
$templates->add_rule('directory>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n\@end direntry\n";
});



 
##################################################
#
# Internationalization
#
##################################################

# Allowing a common lang attribute on all elements 
# would really help XML applications...

$templates->add_rule('documentlanguage<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@documentlanguage " . $elem->attr('lang') . "\n";
});




##################################################
#
# Inline elements
#
##################################################

sub inline_start_handler {
    my ($self, $elem, $templates) = @_;
    mixed_inline_start($self, $elem);
    savable_output '@'. $elem->name . '{';
}
sub inline_end_handler {
    my ($self, $elem, $templates) = @_;
    savable_output "}";
}

foreach my $gi
    (qw(code samp cite email dfn file sc acronym emph strong key kbd var 
        env command option
        i b r t 
        footnote)) 
{
    $templates->add_rule("${gi}<", \&inline_start_handler);
    $templates->add_rule("${gi}>", \&inline_end_handler);

    $templates->add_rule("${gi}<", 'single-line-mode', \&inline_start_handler);
    $templates->add_rule("${gi}>", 'single-line-mode', \&inline_end_handler);

    $templates->add_rule("${gi}<", 'saved-text-mode', \&inline_start_handler);
    $templates->add_rule("${gi}>", 'saved-text-mode', \&inline_end_handler);

    $templates->add_rule("${gi}<", 'saved-text-ignore-markup-mode', sub {});
    $templates->add_rule("${gi}>", 'saved-text-ignore-markup-mode', sub {});
}

sub anchor_start {
    my ($self, $elem, $templates) = @_;
    mixed_inline_start($self, $elem);
    savable_output '@anchor{' 
        . node_escape(texi_escape($elem->attr('node'))) 
        . '}';
}

$templates->add_rule('anchor<', \&anchor_start);
$templates->add_rule('anchor<', 'single-line-mode', \&anchor_start);
$templates->add_rule('anchor<', 'saved-text-mode', \&anchor_start);
$templates->add_rule('anchor<', 'saved-text-ignore-markup-mode', \&anchor_start);



    
##################################################
#
# Cross references, links
#
##################################################

sub crossref_start_handler {
    my ($self, $elem, $templates) = @_;
    mixed_inline_start($self, $elem);
    $texixml::output_save = '';
    $templates->push_mode('saved-text-mode');
}

sub crossref_end_handler {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();

    # Syntax:
    # @ref{$node,$infoname,$printname,$file,$printmanual}
    # node - required
    # infoname, printname - optionally specified by inline content 
    #     (I don't think there is much utility in having these separate)
    # infofile, printfile - optional
    
    my $node = node_escape(texi_escape($elem->attr('node')));
    
    my $printname = texi_arg_escape($texixml::output_save);
    $texixml::output_save = undef;
    my $infoname;

    # If the node and cross reference name turn out to be
    # the same, make the latter empty so info won't display it
    # twice.
    $infoname = ($node eq $printname) ? '' : $printname;
    
    my $file = texi_escape($elem->attr('file'));
    my $printmanual = texi_escape($elem->attr('printmanual'));

    # Required part
    output '@' . $elem->name . '{' . $node;

    # Reference to another file
    if($file ne '' and $file ne $self->{basename}) {
        output ",$infoname,$printname,$file,$printmanual}";
    }
    else {
        # No inline content either, so use one-argument @ref
        if($printname eq '') { output "}"; }
        else { output ",$infoname,$printname}"; }
    }
    
    # Texinfo's ugly hack
    output "." unless $elem->name eq 'pxref';
}

foreach my $gi (qw(xref ref pxref)) {
    $templates->add_rule("${gi}<", \&crossref_start_handler);
    $templates->add_rule("${gi}>", \&crossref_end_handler);
}




##################################################
#
# URI references
#
##################################################

$templates->add_rule('uref<', sub {
    my ($self, $elem, $templates) = @_;
    mixed_inline_start($self, $elem);
    $texixml::output_save = '';
    $templates->push_mode('saved-text-mode');
});

$templates->add_rule('uref>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    
    my $url = texi_escape($elem->attr('url'));
    my $text = texi_arg_escape($texixml::output_save);
    $texixml::output_save = undef;

    if($text eq '') {
        output "\@uref{$url}";
    } else {
        output "\@uref{$url,$text}";
    }
});




##################################################
#
# Sectioning elements
#
##################################################

sub section_start_handler {
    my ($self, $elem, $templates) = @_;
    $elem->parent->ext->{'lastchild'} = 'block';
    output "\n\@" . $elem->name . ' ';
    $templates->push_mode('single-line-mode');
}

sub section_end_handler {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n";
}

foreach my $gi
    (qw(chapter section subsection subsubsection
       majorheading chapheading heading subheading subsubheading
       top unnumbered unnumberedsec unnumberedsubsec unnumberedsubsubsec
       appendix appendixsec appendixsubsec appendixsubsubsec)) 
{
    $templates->add_rule("${gi}<", \&section_start_handler);
    $templates->add_rule("${gi}>", \&section_end_handler);
}




##################################################
#
# Paragraph
#
##################################################

$templates->add_rule('para<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
});
$templates->add_rule('para>', sub {
    my ($self, $elem, $templates) = @_;
    output "\n";
});
    



##################################################
#
# Verbatim displays, quotations
#
##################################################

sub verbatim_block_start_handler 
{
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    output '@' . $elem->name . "\n";
    $templates->push_mode('verbatim-mode');
}
sub verbatim_block_end_handler
{
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n\@end " . $elem->name . "\n";
}

foreach my $gi
    (qw(example display format)) {
    $templates->add_rule("${gi}<", \&verbatim_block_start_handler);
    $templates->add_rule("${gi}>", \&verbatim_block_end_handler);
}

$templates->add_rule('quotation<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    output "\@quotation\n";
});

$templates->add_rule('quotation>', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@end quotation\n";
});




##################################################
#
# Lists
#
##################################################

$templates->add_rule('enumerate<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    output "\@enumerate " . $elem->attr('begin') . "\n";
});

$templates->add_rule('enumerate>', sub {
    output "\n\@end enumerate\n";
});

$templates->add_rule('itemize<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    
    if($elem->attr('markchar') ne '') {
        output "\@itemize " 
            . texi_escape($elem->attr('markchar')) 
            . "\n";
    } else {
        output "\@itemize \@w\n";
    }
});

$templates->add_rule('itemize>', sub {
    output "\n\@end itemize\n";
});

$templates->add_rule('varlist<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    output "\@table \@asis\n";
});
$templates->add_rule('varlist>', sub {
    output "\n\@end table\n";
});

$templates->add_rule('varlistentry<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
});
$templates->add_rule('term<', sub {
    my ($self, $elem, $templates) = @_;
    if($elem->parent->ext->{numterms}++) {
        output "\@itemx ";
    } else {
        output "\@item ";
    }
    $templates->push_mode('single-line-mode');
});
$templates->add_rule('term>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode;
    output "\n";
});

$templates->add_rule('listitem<', sub {
    my ($self, $elem, $templates) = @_;

    # listitem is used in both varlistentry and plain lists,
    # but the @item markup is supplied by <term> in the former 
    # case already.
    if($elem->parent->name ne 'varlistentry') {
        block_start($self, $elem);
        output "\@item\n";
    }
});




##################################################
#
# Tables
#
#################################################

$templates->add_rule('multitable<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);
    $elem->ext->{total_cols} = $elem->attr('cols');
    $elem->ext->{column_data} = [];
    $elem->ext->{colspec_current_colnum} = 0;
    $elem->ext->{colnames} = {};
    $elem->ext->{spannames} = {};
});

$templates->add_rule('colspec<', sub {
    my ($self, $elem, $templates) = @_;

    my $col;
    if($elem->attr('col')) {
        $col = $elem->attr('col');
    } else {
        $col = $elem->parent->ext->{colspec_current_colnum} + 1;
    }
    $elem->parent->ext->{colspec_current_colnum} = $col;

    if($elem->attr('colname') ne '') {
        $elem->parent->ext->{colnames}->{$elem->attr('colname')} = $col;
    }
    
    $elem->parent->ext->{column_data}->[$col-1] = 
        '' . $elem->attr('colwidth');
});

$templates->add_rule('spanspec<', sub {
    my ($self, $elem, $templates) = @_;

    $elem->parent->ext->{spannames}->{$elem->attr('spanname')}
        = [ $elem->attr('namest'), $elem->attr('nameend') ];
});

$templates->add_rule('tbody<', sub {
    my ($self, $elem, $templates) = @_;
    
    my $column_data = $elem->parent->ext->{column_data};
    my $totalcols = $elem->parent->ext->{total_cols};

    my @vspans = ();
    for(my $i = 0; $i < $totalcols; $i++) {
        push(@vspans, 0);
    }
    $elem->ext->{current_vspans} = \@vspans;
    
    my $proportsum = 0;
    for(my $i = 0; $i < $totalcols; $i++) {
        my $colwidth = $column_data->[$i];
        if($colwidth eq '') {
            $colwidth = $column_data->[$i] = '1*';
        }
        
        # Later we may support other types of width measure,
        # so proportional measures should be written
        # as "r*".
        $colwidth =~ s/\*\s*$//;
        $proportsum += $colwidth;
    }
   
    my $columnfractions = '';
    for(my $i = 0; $i < $totalcols; $i++) {
        my $colwidth = $column_data->[$i];
        $colwidth =~ s/\*\s*$//;
        $columnfractions .= $colwidth/$proportsum . ' ';
    }
    $columnfractions =~ s/ $//;
    
    output "\n\@multitable \@columnfractions $columnfractions\n";
});

$templates->add_rule('tbody>', sub {
    output "\n\@end multitable\n";
});

$templates->add_rule('row<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@item\n";

    $elem->ext->{current_colnum} = 0;
    tbl_advance_column($elem, 0, 1);
});
$templates->add_rule('row>', sub {
    my ($self, $elem, $templates) = @_;
    output "\n";
    my $vspans = $elem->parent->ext->{current_vspans};
    for(my $i = 0; $i < @$vspans; $i++) {
        $vspans->[$i]-- if $vspans->[$i] > 0;
    }
});

$templates->add_rule('entry<', sub {
    my ($self, $elem, $templates) = @_;

    my $tableext = $elem->parent->parent->parent->ext;
    my $namest; my $nameend;
    if($elem->attr('spanname')) {
        $namest = $tableext->{spannames}->{$elem->attr('spanname')}->[0];
        $nameend = $tableext->{spannames}->{$elem->attr('spanname')}->[1];
    } elsif($elem->attr('namest')) {
        $namest = $elem->attr('namest');
        $nameend = $elem->attr('nameend');
    }

    my $relative_advance = 1;
    my $colnum;
    if(defined $namest) {
        my $col_st = $colnum = $tableext->{colnames}->{$namest};
        my $col_end = $tableext->{colnames}->{$nameend};
        
        $relative_advance = $col_end - $col_st + 1;
    } 
    elsif($elem->attr('colname')) {
        $colnum = $tableext->{colnames}->{$elem->attr('colname')};
    }

    if(defined $colnum) {
        tbl_advance_column($elem->parent, $colnum);
    }
 
    $elem->ext->{relative_advance} = $relative_advance;

    if($elem->attr('morerows')) {
        if($elem->attr('morerows') !~ /^\d+$/) {
            warn_location($elem, "invalid morerows value --- ignoring\n");
        } else {
            for(my $i = 0; $i < $relative_advance; $i++) {
                $elem->parent->parent->ext->{current_vspans}->[
                    $elem->parent->ext->{current_colnum} - 1 + $i]
                        = $elem->attr('morerows') + 1;
            }
        }
    }
    
});

$templates->add_rule('entry>', sub {
    my ($self, $elem, $templates) = @_;
    tbl_advance_column($elem->parent, 0, $elem->ext->{relative_advance});
});

sub tbl_advance_column
{
    my ($row, $new_colnum, $relative_advance) = @_;

    my $old_colnum = $row->ext->{current_colnum};
    my $total_cols = $row->parent->parent->ext->{total_cols};

    if($relative_advance) {
        my $vspans = $row->parent->ext->{current_vspans};
        for($new_colnum = $old_colnum + $relative_advance;
            $new_colnum <= $total_cols && ($vspans->[$new_colnum - 1] > 0);
            $new_colnum++)
        {}
    }
    elsif($new_colnum == -1) {
        $new_colnum = $total_cols + 1;
    }

    $row->ext->{current_colnum} = $new_colnum;

    $new_colnum = $total_cols if $new_colnum > $total_cols;
    $old_colnum = 1           if $old_colnum == 0;

    output('@tab ' x ($new_colnum - $old_colnum));
}

##################################################
#
# Graphics
#
##################################################

$templates->add_rule('image<', sub {
    my ($self, $elem, $templates) = @_;
    mixed_inline_start($self, $elem);
    
    # FIXME Should we resolve URIs?
    my $filename = texi_escape($elem->attr('filename'));
    
    if(defined $elem->attr('width') or
       defined $elem->attr('height'))
    {
        savable_output '@image{' . $filename . ',' . 
            texi_escape($elem->attr('width')) .
            ',' . 
            texi_escape($elem->attr('height')) .
            '}';
    } 
    else {
        savable_output '@image{' . $filename . '}';
    }
});




##################################################
#
# Vertical spacing
#
##################################################

$templates->add_rule('sp<', sub {
    my ($self, $elem, $templates) = @_;
    output "\n\@sp " . $elem->attr('n') . "\n";
});
$templates->add_rule('page<', sub {
    output "\n\@page\n";
});




##################################################
#
# Indices
#
##################################################

$templates->add_rule('indexterm<', sub {
    my ($self, $elem, $templates) = @_;

    # We allow indexterm at block level just like
    # DocBook.  When that happens, don't treat
    # it as an inline (hence no @noindent mantra).
    if($elem->parent->ext->{lastchild} ne 'block') {
        mixed_inline_start($self, $elem);
    }
    
    my $class = $elem->attr('class');
    $class = 'c' if $class eq 'cp';
    $class = 'f' if $class eq 'fn';
    $class = 'v' if $class eq 'vr';
    $class = 'k' if $class eq 'ky';
    $class = 'p' if $class eq 'pg';
    $class = 'd' if $class eq 'tp';
    
    # @cindex has to start on a new line.
    # I don't know if we are in a middle of an inline
    # command (eg @{code}) that @cindex would work
    # and not disrupt the inline.  I'm just hoping it works.
    # If it doesn't, then it is a dumb limitation!
    
    output "\n\@" . $class . 'index ';

    # Are @-commands allowed for indexed terms?
    $templates->push_mode('plain-text-mode');

});
$templates->add_rule('indexterm>', sub {
    my ($self, $elem, $templates) = @_;
    $templates->pop_mode();
    output "\n";
});

$templates->add_rule('printindex<', sub {
    my ($self, $elem, $templates) = @_;
    block_start($self, $elem);

    my $class = $elem->attr('class');
    $class = 'cp' if $class eq 'c';
    $class = 'fn' if $class eq 'f';
    $class = 'vr' if $class eq 'v';
    $class = 'ky' if $class eq 'k';
    $class = 'pg' if $class eq 'p';
    $class = 'tp' if $class eq 't';
    
    output "\@printindex " . $class . "\n";
});




##################################################
#
# Character data
#
##################################################

$templates->add_rule('text()', 'single-line-mode', sub {
    my ($self, $node, $templates) = @_;
    my $s = texi_escape($node->{Data});
    
    # Collapse spaces, no newlines.
    $s =~ tr/ \t\n/ /s;
        
    output $s;
});

$templates->add_rule('text()', 'plain-text-mode', sub {
    my ($self, $node, $templates) = @_;
    my $s = texi_escape($node->{Data});
    
    # Collapse spaces, no newlines.
    $s =~ tr/ \t\n/ /s;
        
    output $s;
});

sub saved_text_mode_handler
{
    my ($self, $node, $templates) = @_;
    my $s = texi_escape($node->{Data});

    # Newlines, die!
    $s =~ tr/ \t\n/ /s;

    $texixml::output_save .= $s;
}
$templates->add_rule('text()', 'saved-text-mode', 
    \&saved_text_mode_handler);
$templates->add_rule('text()', 'saved-text-ignore-markup-mode', 
    \&saved_text_mode_handler);

$templates->add_rule('text()', 'menu-mode', sub {});

$templates->add_rule('text()', 'verbatim-mode', sub {
    my ($self, $node, $templates) = @_;
    my $s = texi_escape($node->{Data});

    if($texixml::newline_last and $s =~ /^\n/) {
        # Make another line anyway
        output "\n\n";
    }

    output $s;
});

$templates->add_rule('text()', sub {
    my ($self, $node, $templates) = @_;
    
    my $s = texi_escape($node->{Data});
 
    # Newlines are also whitespace but we want to keep
    # them whenever possible so that the output looks nicer.
    $s =~ tr/\n/\n/s;
 
    # Collapse whitespace
    $s =~ tr/ \t/ /s;
    
    mixed_inline_start($self, $node)
        unless $s =~ /^\s+$/;
            # Whitespace used to separate element
            # in a non-mixed content model should
            # not cause any spurious breaks.

    $s =~ s/^ // if $texixml::newline_last;

    output $s;
});




##################################################
#
# Comments
#
##################################################

$templates->add_rule('comment<', sub {
    my ($self, $elem, $templates) = @_;
    output '@c ';
    $templates->push_mode('comment-mode');
});

$templates->add_rule('comment>', sub {
    $templates->pop_mode('comment-mode');
    output "\n";
});

$templates->add_rule('text()', 'comment-mode', sub {
    my ($self, $node, $templates) = @_;
    my $s = $node->{Data};
    $s =~ tr/\n/ /;
    output $s;
});




##################################################
#
# Processing instructions
#
##################################################

$templates->add_rule('processing-instruction()', sub {
    my ($self, $node, $templates) = @_;

    if($node->{Target} eq 'texinfo') {
        my $data = $node->{Data};
        $data =~ s/\&#xA;/\n/g;
        $data =~ s/\&#10;/\n/g;
        output $data;
    }
});




##################################################
#
# Catch unknown elements
#
##################################################

$templates->add_rule('*<', \&unexpected_element);
sub unexpected_element {
    my ($self, $node, $templates) = @_;
    $templates->warn_location($node, "unexpected element\n");
};




##################################################
#
# Main
#
##################################################

package main;

use XML::SAX::ParserFactory;

unshift(@ARGV, '-') unless @ARGV;
my $parser = XML::SAX::ParserFactory->parser(
        DocumentHandler => $texixml::templates);

foreach my $file (@ARGV)
{
    $texixmldata->{inputfile} = $file;
    if($file eq '-') {
        $parser->parse_file(\*STDIN);
    } else {
        $parser->parse_uri($file);
    }
}

