Blame perl/db2x_texixml.pl

Packit e4b6da
# vim:sw=4 et sta showmatch
Packit e4b6da
Packit e4b6da
# db2x_texixml - convert Texi-XML to Texinfo
Packit e4b6da
#                (See docbook2X documentation for details)
Packit e4b6da
#
Packit e4b6da
# (C) 2000-2004 Steve Cheng <stevecheng@users.sourceforge.net>
Packit e4b6da
#
Packit e4b6da
# See the COPYING file in the docbook2X distribution 
Packit e4b6da
# for the copyright status of this software.
Packit e4b6da
#      
Packit e4b6da
# Note: db2x_texixml.pl does not run by itself!
Packit e4b6da
#       It must be configured by including a config.pl file
Packit e4b6da
#       which is done when building docbook2X.
Packit e4b6da
#       In addition, the non-standard module 
Packit e4b6da
#       XML::Handler::SGMLSpl must be explicitly loaded
Packit e4b6da
#       when docbook2X is not installed.
Packit e4b6da
Packit e4b6da
package main;
Packit e4b6da
use strict;
Packit e4b6da
Packit e4b6da
Packit e4b6da
#############################################################################
Packit e4b6da
#
Packit e4b6da
# Option parsing
Packit e4b6da
#
Packit e4b6da
#############################################################################
Packit e4b6da
Packit e4b6da
use Getopt::Long;
Packit e4b6da
Getopt::Long::Configure('bundling');
Packit e4b6da
my $cmdoptions = {
Packit e4b6da
    'encoding' => 'us-ascii',
Packit e4b6da
    'list-files' => 0,
Packit e4b6da
    'to-stdout' => 0,
Packit e4b6da
    'output-dir' => '',
Packit e4b6da
    'info' => 0,
Packit e4b6da
    'plaintext' => 0,
Packit e4b6da
    'utf8trans-program' => $db2x_config{'utf8trans-program'},
Packit e4b6da
    'utf8trans-map' => $db2x_config{'utf8trans-map-texi'},
Packit e4b6da
    'iconv-program' => $db2x_config{'iconv-program'},
Packit e4b6da
    'makeinfo-program' => $db2x_config{'makeinfo-program'},
Packit e4b6da
};
Packit e4b6da
Packit e4b6da
sub options_help {
Packit e4b6da
    print "Usage: $0 [OPTION]... [FILE]...\n";
Packit e4b6da
    print <<'end';
Packit e4b6da
Make Texinfo documents from Texi-XML
Packit e4b6da
Packit e4b6da
  --encoding=ENCODING   Character encoding for Texinfo files
Packit e4b6da
                        Default is US-ASCII
Packit e4b6da
  --list-files          Write list of output files to stdout
Packit e4b6da
  --to-stdout           Write output to stdout instead of to files
Packit e4b6da
  --output-dir          Directory to write the output files
Packit e4b6da
                        Default is current working directory
Packit e4b6da
  --info                Pipe output to makeinfo, creating Info files directly
Packit e4b6da
  --plaintext           Pipe output to makeinfo, creating plain text files
Packit e4b6da
  
Packit e4b6da
  These options set the location of auxiliary programs:
Packit e4b6da
  --utf8trans-program=PATH, --utf8trans-map=PATH, 
Packit e4b6da
  --iconv-program=PATH, --makeinfo-program=PATH
Packit e4b6da
Packit e4b6da
  --help                Show this help and exit
Packit e4b6da
  --version             Show version and exit
Packit e4b6da
Packit e4b6da
See the db2x_texixml(1) manual page and the docbook2X documentation for
Packit e4b6da
more details.
Packit e4b6da
end
Packit e4b6da
    exit 0;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub options_version
Packit e4b6da
{
Packit e4b6da
    print "db2x_texixml (part of docbook2X " . 
Packit e4b6da
            $db2x_config{'docbook2X-version'} . ")\n";
Packit e4b6da
    print <<'end';
Packit e4b6da
$Revision: 1.49 $ $Date: 2006/04/20 03:02:31 $
Packit e4b6da
<URL:http://docbook2x.sourceforge.net/>
Packit e4b6da
Packit e4b6da
Copyright (C) 2000-2004 Steve Cheng
Packit e4b6da
This is free software; see the source for copying conditions.  There is NO
Packit e4b6da
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Packit e4b6da
end
Packit e4b6da
    exit 0;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$SIG{__WARN__} = sub { print STDERR "$0: " . $_[0]; };
Packit e4b6da
if(!GetOptions($cmdoptions,
Packit e4b6da
    'encoding=s',
Packit e4b6da
    'list-files',
Packit e4b6da
    'to-stdout',
Packit e4b6da
    'output-dir=s',
Packit e4b6da
    'info',
Packit e4b6da
    'plaintext',
Packit e4b6da
    'utf8trans-program=s',
Packit e4b6da
    'utf8trans-map=s',
Packit e4b6da
    'iconv-program=s',
Packit e4b6da
    'makeinfo-program=s',
Packit e4b6da
    'help', \&options_help,
Packit e4b6da
    'version', \&options_version))
Packit e4b6da
{
Packit e4b6da
    print STDERR "Try \"$0 --help\" for more information.\n";
Packit e4b6da
    exit 1;
Packit e4b6da
}
Packit e4b6da
$SIG{__WARN__} = undef;
Packit e4b6da
Packit e4b6da
#use XML::Handler::SGMLSpl;     # we link to this explicitly during building
Packit e4b6da
my $texixmldata = { 'options' => $cmdoptions };
Packit e4b6da
$texixml::templates = XML::Handler::SGMLSpl->new($texixmldata);
Packit e4b6da
$texixml::templates->push_mode('file-unselected');
Packit e4b6da
$texixml::templates->{namespaces}->{''}="http://docbook2x.sourceforge.net/xmlns/Texi-XML";
Packit e4b6da
               
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
#############################################################################
Packit e4b6da
#
Packit e4b6da
# TexiWriter: output routines
Packit e4b6da
#
Packit e4b6da
#############################################################################
Packit e4b6da
Packit e4b6da
package TexiWriter;
Packit e4b6da
require Exporter;
Packit e4b6da
@TexiWriter::ISA = qw(Exporter);
Packit e4b6da
@TexiWriter::EXPORT_OK = qw(texi_escape texi_arg_escape);
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Use TexiWriter on specified file
Packit e4b6da
# Params: fh - an IO::Handle to send the output
Packit e4b6da
#
Packit e4b6da
sub new {
Packit e4b6da
    my ($class, $fh) = @_;
Packit e4b6da
    my $self = { fh => $fh, line_start => 1, output_buffers => [] };
Packit e4b6da
    return bless($self, $class);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Print text with whitespace folding
Packit e4b6da
# (actually only newline folding)
Packit e4b6da
# Usually need to escape text first
Packit e4b6da
# Params: text - string to print
Packit e4b6da
#
Packit e4b6da
sub print_ws {
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
    
Packit e4b6da
    foreach my $line (split(/(\n)/, $text)) {
Packit e4b6da
        if($line eq "\n") {
Packit e4b6da
            # Don't leave a blank line
Packit e4b6da
            $self->{fh}->print("\n")
Packit e4b6da
                unless $self->{line_start}++;
Packit e4b6da
        } else {
Packit e4b6da
            # Don't put any spaces at the beginning
Packit e4b6da
            # of a line.  These cause makeinfo
Packit e4b6da
            # to erroneously indent the text.
Packit e4b6da
            $line =~ s/^[ \t]+// if $self->{line_start};
Packit e4b6da
Packit e4b6da
            # Collapse whitespace
Packit e4b6da
            $line =~ tr/ \t/ /;
Packit e4b6da
Packit e4b6da
            if($line ne '') {
Packit e4b6da
                $self->{fh}->print($line);
Packit e4b6da
                $self->{line_start} = 0;
Packit e4b6da
            }
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# This is basically just a print, but
Packit e4b6da
# with the special instruction that,
Packit e4b6da
# if a '\n' is present at the beginning
Packit e4b6da
# of the string, this means to start a new line,
Packit e4b6da
# 
Packit e4b6da
# i.e. if we are already at the beginning of the line
Packit e4b6da
# we do not emit another '\n' and therefore create
Packit e4b6da
# a blank line.
Packit e4b6da
#
Packit e4b6da
# This routine is useful for emitting "block" Texinfo
Packit e4b6da
# commands that have to start at the beginning of the
Packit e4b6da
# line. (Note: unlike RoffWriter's request method, 
Packit e4b6da
# TexiWriter does not provide an explicit "do command" 
Packit e4b6da
# method --- they are cumbersome and not necessary
Packit e4b6da
# for the Texinfo format.)
Packit e4b6da
#
Packit e4b6da
# No escaping of the text is done.
Packit e4b6da
# 
Packit e4b6da
# Params: text - string to print
Packit e4b6da
# 
Packit e4b6da
sub output {
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
Packit e4b6da
    if($text =~ s/^\n//) {
Packit e4b6da
        $self->{fh}->print("\n") unless $self->{line_start}++;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    return if $text eq '';
Packit e4b6da
Packit e4b6da
    $self->{fh}->print($text);
Packit e4b6da
    $self->{line_start} = ($text =~ /\n$/);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# The following functions implement a simple stack
Packit e4b6da
# of output buffers.  These are used to handle the arguments
Packit e4b6da
# of a Texinfo @-command, when the content of the arguments
Packit e4b6da
# is not immediately accessible in stream XML processing.
Packit e4b6da
#
Packit e4b6da
# The line-breaking semantics of regular output() are not implemented,
Packit e4b6da
# because they do not make sense on strings.
Packit e4b6da
#
Packit e4b6da
Packit e4b6da
sub output_buffer_push
Packit e4b6da
{
Packit e4b6da
    my ($self) = @_;
Packit e4b6da
    unshift(@{$self->{output_buffers}}, "");
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub output_buffer_pop
Packit e4b6da
{
Packit e4b6da
    my ($self) = @_;
Packit e4b6da
    return shift(@{$self->{output_buffers}});
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub savable_output {
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
    if(scalar(@{$self->{output_buffers}}) == 0) {
Packit e4b6da
        return output($self, $text);
Packit e4b6da
    } else {
Packit e4b6da
        $self->{output_buffers}->[0] .= $text;
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
        
Packit e4b6da
    
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Print text without folding whitespace
Packit e4b6da
# Usually need to escape text first
Packit e4b6da
# Params: text - string to print
Packit e4b6da
#
Packit e4b6da
sub print {
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
    $self->{fh}->print($text);
Packit e4b6da
    $self->{line_start} = ($text =~ /\n$/);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
# Escape Texinfo syntax chars
Packit e4b6da
#
Packit e4b6da
sub texi_escape
Packit e4b6da
{
Packit e4b6da
    my $s = shift;
Packit e4b6da
    $s =~ s/([\@\{\}])/\@$1/g;
Packit e4b6da
    return $s;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub texi_arg_escape 
Packit e4b6da
{
Packit e4b6da
    my $s = shift;
Packit e4b6da
    $s =~ s/,/\@comma\{\}/g;
Packit e4b6da
    return $s;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
# Escape the ',' when output buffering is activated,
Packit e4b6da
# because output buffering is typically used to handle
Packit e4b6da
# arguments, and a literal ',' would be misinterpreted
Packit e4b6da
# as an argument delimeter.
Packit e4b6da
Packit e4b6da
sub texi_arg_escape_conditional
Packit e4b6da
{
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
Packit e4b6da
    if(scalar(@{$self->{output_buffers}}) > 0) {
Packit e4b6da
        $text =~ s/,/\@comma\{\}/g;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    return $text;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
#############################################################################
Packit e4b6da
#
Packit e4b6da
# Template rules
Packit e4b6da
#
Packit e4b6da
#############################################################################
Packit e4b6da
Packit e4b6da
Packit e4b6da
package texixml;
Packit e4b6da
import TexiWriter qw(texi_escape texi_arg_escape);
Packit e4b6da
Packit e4b6da
use IO::File;
Packit e4b6da
use vars qw($templates);
Packit e4b6da
Packit e4b6da
Packit e4b6da
    
Packit e4b6da
    
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# A clean solution to the extra-newlines problem
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
# In essence, texi_xml usually puts newlines only where you 
Packit e4b6da
# expect a human editing a Texinfo file directly would.
Packit e4b6da
# The heuristic works by checking if we are at the beginning
Packit e4b6da
# of a line or not in our output ($texixml::newline_last),
Packit e4b6da
# and if so, refrain from putting too many newlines
Packit e4b6da
# (which would actually produce 2 or more blank lines).
Packit e4b6da
#
Packit e4b6da
# texi_xml also keeps track of what type of element (block, inline 
Packit e4b6da
# or neither) it just processed, then makes line breaks only
Packit e4b6da
# if it is required to separate them.
Packit e4b6da
#
Packit e4b6da
# This is not complete "whitespace collapsing", but since Texinfo
Packit e4b6da
# is reasonably tolerant in its whitespace handling we don't need
Packit e4b6da
# to have a model that collapses whitespace perfectly in every case.
Packit e4b6da
# 
Packit e4b6da
# 
Packit e4b6da
sub block_start
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem) = @_;
Packit e4b6da
Packit e4b6da
    if(scalar(@{$self->{tw}->{output_buffers}}) > 0) {
Packit e4b6da
        die "$0: block_start called while saving output (this is a bug)";
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->{tw}->output("\n\n")
Packit e4b6da
        unless ($elem->in('listitem') and 
Packit e4b6da
                $elem->parent->ext->{lastchild} eq '')
Packit e4b6da
            or ($elem->in('entry') and
Packit e4b6da
                $elem->parent->ext->{lastchild} eq '');
Packit e4b6da
            # Don't put blank before the first block in 
Packit e4b6da
            # varlistentries and entries of a multitable.
Packit e4b6da
Packit e4b6da
    $elem->parent->ext->{lastchild} = 'block';
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
sub mixed_inline_start
Packit e4b6da
{
Packit e4b6da
    my ($self, $node) = @_;
Packit e4b6da
    
Packit e4b6da
    if(scalar(@{$self->{tw}->{output_buffers}}) > 0) {
Packit e4b6da
        return;
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    # Example:
Packit e4b6da
    # <para>Warning<itemize>...</itemize>Do not indent this text
Packit e4b6da
    # since it's part of the same paragraph</para>
Packit e4b6da
Packit e4b6da
    $self->{tw}->output("\n\n\@noindent\n") 
Packit e4b6da
        if $node->parent->ext->{lastchild} eq 'block';
Packit e4b6da
Packit e4b6da
    $node->parent->ext->{lastchild} = 'inline';
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Texinfo preamble and eof
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
sub shell_quote
Packit e4b6da
{
Packit e4b6da
    join(' ', map { my $u = $_;
Packit e4b6da
                    $u =~ s#([\$`"\\\n])#\\$1#g; 
Packit e4b6da
                    '"' . $u . '"' } @_);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub texi_openfile {
Packit e4b6da
    my ($self, $basename) = @_;
Packit e4b6da
Packit e4b6da
    my $dir = $self->{options}->{'output-dir'};
Packit e4b6da
    $dir =~ s/([^\/])$/$1\//;     # terminate with slash
Packit e4b6da
Packit e4b6da
    my $encoding = $self->{options}->{encoding};
Packit e4b6da
    
Packit e4b6da
    my $openstr = '';
Packit e4b6da
        
Packit e4b6da
    if(($encoding !~ /^utf|ucs/i or $encoding =~ s/\/\/TRANSLIT$//i)
Packit e4b6da
        and $self->{options}->{'utf8trans-program'} ne '')
Packit e4b6da
    {
Packit e4b6da
        $openstr .= '| ' .
Packit e4b6da
            shell_quote($self->{options}->{'utf8trans-program'}) . ' -- ' .
Packit e4b6da
            shell_quote($self->{options}->{'utf8trans-map'}) . ' ';
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    if($encoding !~ /^utf-?8$/i
Packit e4b6da
        and $self->{options}->{'iconv-program'} ne '')
Packit e4b6da
    {
Packit e4b6da
        $openstr .= '| ' .
Packit e4b6da
            shell_quote($self->{options}->{'iconv-program'},
Packit e4b6da
                        '-f', 'utf-8',
Packit e4b6da
                        '-t', $encoding)
Packit e4b6da
            . ' ';
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if($self->{options}->{'plaintext'}) {
Packit e4b6da
        my $filename = "${dir}${basename}.txt";
Packit e4b6da
        $openstr .= '| ' . 
Packit e4b6da
            shell_quote($self->{options}->{'makeinfo-program'}, 
Packit e4b6da
                        '--no-headers');
Packit e4b6da
        if(not $self->{options}->{'to-stdout'}) {
Packit e4b6da
            print "$filename\n"
Packit e4b6da
                if $self->{options}->{'list-files'};
Packit e4b6da
            $openstr .= ' > ' . shell_quote($filename);
Packit e4b6da
        }
Packit e4b6da
    } elsif($self->{options}->{'info'}) {
Packit e4b6da
        if(not $self->{options}->{'to-stdout'}) {
Packit e4b6da
            $openstr .= '| ( cd ' 
Packit e4b6da
                        . shell_quote($dir) . ' && exec ' 
Packit e4b6da
                        . shell_quote($self->{options}->{'makeinfo-program'})
Packit e4b6da
                        . ' )';
Packit e4b6da
            print "${dir}${basename}.info\n"
Packit e4b6da
                if $self->{options}->{'list-files'};
Packit e4b6da
        } else {
Packit e4b6da
            $openstr .= '| ' . shell_quote(
Packit e4b6da
                                    $self->{options}->{'makeinfo-program'})
Packit e4b6da
                             . ' -o -';
Packit e4b6da
        }
Packit e4b6da
    } else {
Packit e4b6da
        my $filename = "${dir}${basename}.texi";
Packit e4b6da
        if($openstr eq '') {
Packit e4b6da
            if(not $self->{options}->{'to-stdout'}) {
Packit e4b6da
                $openstr = $filename;
Packit e4b6da
                # Trick from Perl FAQ to open file with arbitrary characters
Packit e4b6da
                $openstr =~ s#^(\s)#./$1#;
Packit e4b6da
                $openstr = ">${openstr}\0";
Packit e4b6da
                print "$filename\n"
Packit e4b6da
                    if $self->{options}->{'list-files'};
Packit e4b6da
            } else {
Packit e4b6da
                $openstr = '>-';
Packit e4b6da
            }
Packit e4b6da
        } else {
Packit e4b6da
            $openstr .= '> ' . shell_quote($filename);
Packit e4b6da
            print "$filename\n"
Packit e4b6da
                if $self->{options}->{'list-files'};
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    my $iof = new IO::File($openstr)
Packit e4b6da
        or die "$0: error opening $openstr: $!\n";
Packit e4b6da
Packit e4b6da
    # Set output encoding to UTF-8 on Perl >=5.8.0
Packit e4b6da
    # so it doesn't complain
Packit e4b6da
    binmode($iof, ":utf8") unless $] < 5.008;
Packit e4b6da
Packit e4b6da
    return $iof;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('texinfoset<', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{node2id_map} = {};
Packit e4b6da
    $self->{id2node_map} = {};
Packit e4b6da
    $self->{id2file_map} = {};
Packit e4b6da
    $self->{id_counter} = 1;
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('texinfoset>', 'file-unselected', sub {});
Packit e4b6da
Packit e4b6da
$templates->add_rule('texinfo<', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    my $basename;
Packit e4b6da
    if($elem->attr('file') ne '') {
Packit e4b6da
        $basename = filename_escape($elem->attr('file'));
Packit e4b6da
    } elsif($self->{inputfile} ne '-') {
Packit e4b6da
        $basename = $self->{inputfile};
Packit e4b6da
        # strip the path component, and extension
Packit e4b6da
        $basename = $1 if $basename =~ /([^\/]+)$/;
Packit e4b6da
        $basename =~ s/\.txml$//;
Packit e4b6da
    } else {
Packit e4b6da
        $basename = 'untitled';
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->{fh} = texi_openfile($self, $basename);
Packit e4b6da
    $self->{tw} = new TexiWriter($self->{fh});
Packit e4b6da
Packit e4b6da
    $self->{basename} = $basename;
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\\input texinfo\n");
Packit e4b6da
    $self->{tw}->output("\n\@setfilename ${basename}.info\n");
Packit e4b6da
Packit e4b6da
    my $encoding = $self->{options}->{encoding};
Packit e4b6da
    $encoding =~ s#//TRANSLIT$##i;
Packit e4b6da
    $self->{tw}->output("\@documentencoding $encoding\n");
Packit e4b6da
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('texinfo>', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\n\n\@bye\n");
Packit e4b6da
Packit e4b6da
    $self->{fh}->close
Packit e4b6da
        or die $! ? "$0: error closing file/pipe: $!\n"
Packit e4b6da
                  : "$0: program in pipeline exited with an error\n";
Packit e4b6da
Packit e4b6da
    $self->{fh} = undef;
Packit e4b6da
    $self->{tw} = undef;
Packit e4b6da
    
Packit e4b6da
    $templates->push_mode('file-unselected');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'file-unselected', \&illegal_text_handler);
Packit e4b6da
$templates->add_rule('*<', 'file-unselected', \&illegal_element_handler);
Packit e4b6da
Packit e4b6da
sub illegal_text_handler {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($node->{Data} =~ /[^ \t\r\n]/) {
Packit e4b6da
        $templates->warn_location($node, "character data is not allowed here");
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
                    
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Node name maps
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('nodenamemap<', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->push_mode('nodenamemap-mode');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('nodenamemap>', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('nodenamemapentry<', 'nodenamemap-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->ext->{nodenames} = [];
Packit e4b6da
});
Packit e4b6da
    
Packit e4b6da
$templates->add_rule('nodenamemapentry>', 'nodenamemap-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    my $id = $elem->attr('id');
Packit e4b6da
    my $f = filename_escape($elem->attr('file'));
Packit e4b6da
    my $nodename;
Packit e4b6da
Packit e4b6da
    foreach my $s (@{$elem->ext->{nodenames}}) {
Packit e4b6da
        if(not exists $self->{node2id_map}->{"${f}/$s"}) {
Packit e4b6da
            $nodename = $s;
Packit e4b6da
            last;
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if(not defined $nodename) {
Packit e4b6da
        if(scalar(@{$elem->ext->{nodenames}}) > 0) {
Packit e4b6da
            for(my $i = 1; ; $i++) {
Packit e4b6da
                $nodename = $elem->ext->{nodenames}->[0] . 
Packit e4b6da
                                ' <' . $i . '>';
Packit e4b6da
                last if not 
Packit e4b6da
                    exists $self->{node2id_map}->{"${f}/$nodename"};
Packit e4b6da
            }
Packit e4b6da
        } elsif(not exists $self->{node2id_map}->{
Packit e4b6da
                    $f . '/' . ($nodename = nodename_escape($id))}) {
Packit e4b6da
        } else {
Packit e4b6da
            $nodename = 'untitled node <' . $self->{id_counter}++ . '>';
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->{node2id_map}->{"${f}/$nodename"} = $id;
Packit e4b6da
    $self->{id2node_map}->{$id} = $nodename;
Packit e4b6da
    $self->{id2file_map}->{$id} = $f;
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('nodename<', 'nodenamemap-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->ext->{nodename} = '';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('nodename>', 'nodenamemap-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $nodenames = $elem->parent->ext->{nodenames};
Packit e4b6da
    my $s = nodename_escape($elem->ext->{nodename});
Packit e4b6da
    push(@$nodenames, $s) unless $s eq '';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'nodenamemap-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    if($node->in('nodename')) {
Packit e4b6da
        $node->parent->ext->{nodename} .= $node->{Data};
Packit e4b6da
    } else {
Packit e4b6da
        &illegal_text_handler;
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', 'nodenamemap-mode', \&illegal_element_handler);;
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
sub get_nodename
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem, $nodename_attr, $id_attr, $optional) = @_;
Packit e4b6da
    if($nodename_attr ne '') {
Packit e4b6da
        return nodename_escape($nodename_attr);
Packit e4b6da
    } else {
Packit e4b6da
        my $f = $self->{id2file_map}->{$id_attr};
Packit e4b6da
        if($f ne '' and $f ne $self->{basename}) {
Packit e4b6da
            # Error, we expect this node to be in the same
Packit e4b6da
            # file.
Packit e4b6da
            $templates->warn_location($elem, "fatal error: node belongs to a different file");
Packit e4b6da
            die;
Packit e4b6da
        }
Packit e4b6da
    
Packit e4b6da
        return undef if($optional and $id_attr eq '');
Packit e4b6da
        
Packit e4b6da
        if($id_attr eq '') {
Packit e4b6da
            $templates->warn_location($elem, "fatal error: neither a node name nor an ID was specified");
Packit e4b6da
            die;
Packit e4b6da
        }
Packit e4b6da
        if(not exists($self->{id2node_map}->{$id_attr})) {
Packit e4b6da
            $templates->warn_location($elem, "ID \"${id_attr}\" does not exist");
Packit e4b6da
            return nodename_escape($id_attr);
Packit e4b6da
        }
Packit e4b6da
        
Packit e4b6da
        return $self->{id2node_map}->{$id_attr};
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub get_nodename_filename
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem, $nodename_attr, $file_attr, $id_attr, $optional) = @_;
Packit e4b6da
    if($nodename_attr ne '' or $file_attr ne '') {
Packit e4b6da
        return (nodename_escape($nodename_attr), filename_escape($file_attr));
Packit e4b6da
    } else {
Packit e4b6da
        return undef if $optional and $id_attr eq '';
Packit e4b6da
        
Packit e4b6da
        if($id_attr eq '') {
Packit e4b6da
            $templates->warn_location($elem, "fatal error: neither a node name nor an ID was specified");
Packit e4b6da
            die;
Packit e4b6da
        }
Packit e4b6da
        if(not exists($self->{id2node_map}->{$id_attr})) {
Packit e4b6da
            $templates->warn_location($elem, "fatal error: ID \"${id_attr}\" does not exist");
Packit e4b6da
            return (nodename_escape($id_attr), '')
Packit e4b6da
        }
Packit e4b6da
        
Packit e4b6da
        return ($self->{id2node_map}->{$id_attr},
Packit e4b6da
                $self->{id2file_map}->{$id_attr});
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
            
Packit e4b6da
Packit e4b6da
    
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Simple title pages
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('settitle<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@settitle ");
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('settitle>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('titlepage<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@titlepage\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('titlepage>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end titlepage\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('title<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@title ");
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('title>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('subtitle<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@subtitle ");
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('subtitle>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('author<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@author ");
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('author>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Menus, nodes
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
# Do escaping for nodenames:
Packit e4b6da
# NOTE: stylesheets should do this if possible
Packit e4b6da
# since there can be rare name clashes.
Packit e4b6da
sub nodename_escape
Packit e4b6da
{
Packit e4b6da
    my $name = shift;
Packit e4b6da
    for ($name) {
Packit e4b6da
        tr/().,:/[]_;;/;
Packit e4b6da
        tr/ \t\n/ /s;
Packit e4b6da
        s/^ +//g;
Packit e4b6da
        s/ +$//g;
Packit e4b6da
    }
Packit e4b6da
    return $name;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub filename_escape
Packit e4b6da
{
Packit e4b6da
    my $s = shift;
Packit e4b6da
    $s =~ tr/\//_/;
Packit e4b6da
    return $s;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
$templates->add_rule('node<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $node = texi_escape(
Packit e4b6da
                get_nodename($self, $elem, 
Packit e4b6da
                    $elem->attr('name'), $elem->attr('id')));
Packit e4b6da
Packit e4b6da
    my $next = texi_escape(get_nodename($self, $elem,
Packit e4b6da
            $elem->attr('next'), $elem->attr('nextid'), 'optional'));
Packit e4b6da
    my $previous = texi_escape(get_nodename($self, $elem,
Packit e4b6da
            $elem->attr('previous'), $elem->attr('previousid'), 'optional'));
Packit e4b6da
    my $up = texi_escape(get_nodename($self, $elem,
Packit e4b6da
            $elem->attr('up'), $elem->attr('upid'), 'optional'));
Packit e4b6da
Packit e4b6da
    if(defined($next) or defined($previous) or defined($up)) {
Packit e4b6da
        if($node =~ /^[Tt]op$/ and $elem->attr('up') eq '') {
Packit e4b6da
            $up = '(dir)';
Packit e4b6da
        }
Packit e4b6da
Packit e4b6da
        $self->{tw}->output(
Packit e4b6da
            "\n\n\@node ${node}, ${next}, ${previous}, ${up}\n");
Packit e4b6da
    } else {
Packit e4b6da
        $self->{tw}->output("\n\n\@node $node\n");
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('menu<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $self->{tw}->output("\@menu\n");
Packit e4b6da
    $templates->push_mode('menu-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('menu>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n\@end menu\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('detailmenu<', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $self->{tw}->output("\@detailmenu\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('detailmenu>', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end detailmenu\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('menuline<', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->push_mode('menu-saved-text-mode');
Packit e4b6da
    $self->{tw}->output_buffer_push();
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('menuline>', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
Packit e4b6da
    my $s = $self->{tw}->output_buffer_pop();
Packit e4b6da
Packit e4b6da
    $self->{tw}->output($s . "\n");
Packit e4b6da
    $self->{tw}->output("\n\n") if($s eq '');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('menuentry<', 'menu-mode', sub {});
Packit e4b6da
$templates->add_rule('menuentry>', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('menuentrytitle<', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->push_mode('menu-saved-text-mode');
Packit e4b6da
    $self->{tw}->output_buffer_push();
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('menuentrytitle>', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
Packit e4b6da
    my $entry = $self->{tw}->output_buffer_pop();
Packit e4b6da
    
Packit e4b6da
    # Since the contents of @menu is supposed to be "pre-formatted",
Packit e4b6da
    # Texinfo will be picky about extra spaces.
Packit e4b6da
    # Eliminate them here.
Packit e4b6da
    $entry =~ tr/ / /s;
Packit e4b6da
    $entry =~ s/^ //;
Packit e4b6da
Packit e4b6da
Packit e4b6da
    # Although the menu entry is not constrained to the set
Packit e4b6da
    # of characters allowed for node names, the use of ':'
Packit e4b6da
    # to separate the parts of menu entry implies that it
Packit e4b6da
    # is not an allowed character.
Packit e4b6da
    $entry =~ tr/:/;/;
Packit e4b6da
Packit e4b6da
    my ($node,$file) = get_nodename_filename($self, $elem->parent,
Packit e4b6da
                            $elem->parent->attr('node'),
Packit e4b6da
                            $elem->parent->attr('file'),
Packit e4b6da
                            $elem->parent->attr('idref'),
Packit e4b6da
                            $elem->parent->in('directory'));
Packit e4b6da
    $node = texi_escape($node);
Packit e4b6da
    $file = texi_escape($file);
Packit e4b6da
Packit e4b6da
    # The eventual output
Packit e4b6da
    my $s;
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
    
Packit e4b6da
    if($file ne '' and ($node eq '' or $file ne $self->{basename})) {
Packit e4b6da
        $s = "* ${entry}: (${file})${node}.";
Packit e4b6da
    } else {
Packit e4b6da
        if($entry eq $node) {
Packit e4b6da
            $s = "* ${entry}::";
Packit e4b6da
        } else {
Packit e4b6da
            $s = "* ${entry}: ${node}.";
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->{tw}->output($s);
Packit e4b6da
Packit e4b6da
    $elem->parent->ext->{'entry_length'} = length($s);
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
use Text::Wrap ();
Packit e4b6da
$templates->add_rule('menuentrydescrip<', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->push_mode('menu-saved-text-mode');
Packit e4b6da
    $self->{tw}->output_buffer_push();
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('menuentrydescrip>', 'menu-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
Packit e4b6da
    my $text = $self->{tw}->output_buffer_pop();
Packit e4b6da
    
Packit e4b6da
    # Since the contents of @menu is supposed to be "pre-formatted",
Packit e4b6da
    # Texinfo will be picky about extra spaces.
Packit e4b6da
    # Eliminate them here.
Packit e4b6da
    $text =~ tr/ / /s;
Packit e4b6da
    $text =~ s/^ //;
Packit e4b6da
        
Packit e4b6da
    my $entry_length = $elem->parent->ext->{'entry_length'};
Packit e4b6da
    
Packit e4b6da
    my $first_line_padding = 
Packit e4b6da
        $entry_length<32 ? 32-$entry_length : 3;
Packit e4b6da
    my $first_line_overflow = 0;
Packit e4b6da
Packit e4b6da
    my $start_column = 
Packit e4b6da
        $entry_length + $first_line_padding + 2;
Packit e4b6da
        
Packit e4b6da
    if($start_column > 50) {
Packit e4b6da
        $first_line_overflow = 1;
Packit e4b6da
        $start_column = 50;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
Packit e4b6da
    $Text::Wrap::columns = 78 - $start_column;
Packit e4b6da
    
Packit e4b6da
    my @lines = split(/(\n)/, Text::Wrap::wrap("", "", $text));
Packit e4b6da
Packit e4b6da
    if(!$first_line_overflow) {
Packit e4b6da
        my $first_line = shift @lines;
Packit e4b6da
        if($first_line) {
Packit e4b6da
            $self->{tw}->output((' ' x $first_line_padding) . $first_line);
Packit e4b6da
        }
Packit e4b6da
    } else {
Packit e4b6da
        $self->{tw}->output("\n");
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    foreach my $line (@lines) {
Packit e4b6da
        if($line eq "\n") {
Packit e4b6da
            $self->{tw}->output("\n");
Packit e4b6da
        } else {
Packit e4b6da
            $self->{tw}->output((' ' x $start_column) . $line);
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
    
Packit e4b6da
$templates->add_rule('text()', 'menu-mode', \&illegal_text_handler);
Packit e4b6da
$templates->add_rule('*<', 'menu-mode', \&illegal_element_handler);
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Info directory
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('directory<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    # If creating plain text files, suppress the directory.
Packit e4b6da
    # Really, makeinfo ought to do this, but it doesn't.
Packit e4b6da
    if($self->{options}->{'plaintext'}) {
Packit e4b6da
        $templates->push_mode('directory-suppress');
Packit e4b6da
        return;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if(defined $elem->attr('category')) {
Packit e4b6da
        $self->{tw}->output("\n\@dircategory " . 
Packit e4b6da
            texi_escape($elem->attr('category')) . "\n");
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\n\@direntry\n");
Packit e4b6da
    $templates->push_mode('menu-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('directory>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
Packit e4b6da
    return if $self->{options}->{'plaintext'};
Packit e4b6da
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n\@end direntry\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'directory-suppress', sub {});
Packit e4b6da
$templates->add_rule('*<', 'directory-suppress', sub {});
Packit e4b6da
Packit e4b6da
 
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Internationalization
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('documentlanguage<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $lstack = $self->{'language-stack'};
Packit e4b6da
Packit e4b6da
    if(defined $elem->attr('lang')) {
Packit e4b6da
        $self->{tw}->output("\n\@documentlanguage " . $elem->attr('lang') . "\n");
Packit e4b6da
        push(@$lstack, $elem->attr('lang'));
Packit e4b6da
    } else {
Packit e4b6da
        pop(@$lstack);
Packit e4b6da
        $self->{tw}->output("\n\@documentlanguage " . $lstack->[-1] . "\n")
Packit e4b6da
            if scalar(@$lstack);
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Inline elements
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
sub inline_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
    $self->{tw}->savable_output('@'. $elem->name . '{');
Packit e4b6da
}
Packit e4b6da
sub inline_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->savable_output('}');
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
foreach my $gi
Packit e4b6da
    (qw(code samp cite email dfn file sc acronym emph strong key kbd var 
Packit e4b6da
        env command option
Packit e4b6da
        i b r t 
Packit e4b6da
        footnote)) 
Packit e4b6da
{
Packit e4b6da
    $templates->add_rule("${gi}<", \&inline_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", \&inline_end_handler);
Packit e4b6da
Packit e4b6da
    $templates->add_rule("${gi}<", 'single-line-mode', \&inline_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", 'single-line-mode', \&inline_end_handler);
Packit e4b6da
Packit e4b6da
    $templates->add_rule("${gi}<", 'saved-text-mode', \&inline_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", 'saved-text-mode', \&inline_end_handler);
Packit e4b6da
    
Packit e4b6da
    $templates->add_rule("${gi}<", 'verbatim-mode', sub {});
Packit e4b6da
    $templates->add_rule("${gi}>", 'verbatim-mode', sub {});
Packit e4b6da
    $templates->add_rule("${gi}<", 'menu-saved-text-mode', sub {});
Packit e4b6da
    $templates->add_rule("${gi}>", 'menu-saved-text-mode', sub {});
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub anchor_start {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
    $self->{tw}->savable_output('@anchor{' 
Packit e4b6da
        . texi_escape(get_nodename($self, $elem,
Packit e4b6da
                $elem->attr('node'), $elem->attr('id')))
Packit e4b6da
        . '}');
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('anchor<', \&anchor_start);
Packit e4b6da
$templates->add_rule('anchor<', 'single-line-mode', \&anchor_start);
Packit e4b6da
$templates->add_rule('anchor<', 'saved-text-mode', \&anchor_start);
Packit e4b6da
$templates->add_rule('anchor<', 'menu-saved-text-mode', \&anchor_start);
Packit e4b6da
$templates->add_rule('anchor<', 'verbatim-mode', \&anchor_start);
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', 'single-line-mode', \&illegal_element_handler);
Packit e4b6da
$templates->add_rule('*<', 'saved-text-mode', \&illegal_element_handler);
Packit e4b6da
$templates->add_rule('*<', 'menu-saved-text-mode', \&illegal_element_handler);
Packit e4b6da
Packit e4b6da
Packit e4b6da
    
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Cross references, links
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
sub crossref_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
    $self->{tw}->output_buffer_push();
Packit e4b6da
    $templates->push_mode('saved-text-mode');
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub crossref_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
Packit e4b6da
    # Syntax:
Packit e4b6da
    # @ref{$node,$infolabel,$label,$file,$printmanual}
Packit e4b6da
    # node - required
Packit e4b6da
    # infolabel, label - optional
Packit e4b6da
    # label is actually the inline content of this element
Packit e4b6da
    # infofile, printmanual - optional
Packit e4b6da
Packit e4b6da
    my ($node, $file) = get_nodename_filename($self, $elem,
Packit e4b6da
                            $elem->attr('node'),
Packit e4b6da
                            $elem->attr('file'),
Packit e4b6da
                            $elem->attr('idref'));
Packit e4b6da
    
Packit e4b6da
    my $infolabel = $elem->attr('infolabel');
Packit e4b6da
    my $label = $self->{tw}->output_buffer_pop();
Packit e4b6da
Packit e4b6da
    # If the node and cross reference label turn out to be
Packit e4b6da
    # the same, make the latter empty so info won't display it
Packit e4b6da
    # twice.
Packit e4b6da
    $label = '' if $node eq $label;
Packit e4b6da
    $infolabel = '' if $node eq $infolabel;
Packit e4b6da
Packit e4b6da
    # Note: 
Packit e4b6da
    # 1. Node names cannot contain commas anyway, so no 
Packit e4b6da
    #    texi_arg_escape needed.
Packit e4b6da
    # 2. label is not escaped here, because it already IS escaped.
Packit e4b6da
    $node = texi_escape($node);
Packit e4b6da
    $infolabel = texi_arg_escape(texi_escape($infolabel));
Packit e4b6da
    $file = texi_arg_escape(texi_escape($file));
Packit e4b6da
    
Packit e4b6da
    my $printmanual = texi_arg_escape(
Packit e4b6da
                        texi_escape($elem->attr('printmanual')));
Packit e4b6da
Packit e4b6da
    $self->{tw}->savable_output('@' . $elem->name . '{' . $node);
Packit e4b6da
Packit e4b6da
    if($file ne '' and $file ne $self->{basename}) {
Packit e4b6da
        # Reference to another file
Packit e4b6da
        $self->{tw}->savable_output(",$infolabel,$label,$file,$printmanual}");
Packit e4b6da
    }
Packit e4b6da
    else {
Packit e4b6da
        # Same file
Packit e4b6da
        if($label eq '' and $infolabel eq '') { 
Packit e4b6da
            $self->{tw}->savable_output("}"); 
Packit e4b6da
            return;
Packit e4b6da
        } elsif($label eq '') { 
Packit e4b6da
            $self->{tw}->savable_output(",$infolabel}"); 
Packit e4b6da
        } else { 
Packit e4b6da
            $self->{tw}->savable_output(",$infolabel,$label}"); 
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
foreach my $gi (qw(xref ref pxref)) {
Packit e4b6da
    $templates->add_rule("${gi}<", \&crossref_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", \&crossref_end_handler);
Packit e4b6da
Packit e4b6da
    $templates->add_rule("${gi}<", 'single-line-mode', \&crossref_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", 'single-line-mode', \&crossref_end_handler);
Packit e4b6da
    $templates->add_rule("${gi}<", 'saved-text-mode', \&crossref_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", 'saved-text-mode', \&crossref_end_handler);
Packit e4b6da
Packit e4b6da
    $templates->add_rule("${gi}<", 'verbatim-mode', sub {});
Packit e4b6da
    $templates->add_rule("${gi}>", 'verbatim-mode', sub {});
Packit e4b6da
    $templates->add_rule("${gi}<", 'menu-saved-text-mode', sub {});
Packit e4b6da
    $templates->add_rule("${gi}>", 'menu-saved-text-mode', sub {});
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# URI references
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('uref<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
    $self->{tw}->output_buffer_push();
Packit e4b6da
    $templates->push_mode('saved-text-mode');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('uref>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    
Packit e4b6da
    my $url = texi_escape($elem->attr('url'));
Packit e4b6da
    my $text = $self->{tw}->output_buffer_pop();
Packit e4b6da
Packit e4b6da
    if($text eq '') {
Packit e4b6da
        $self->{tw}->savable_output("\@uref{$url}");
Packit e4b6da
    } else {
Packit e4b6da
        $self->{tw}->savable_output("\@uref{$url,$text}");
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
# FIXME
Packit e4b6da
$templates->add_rule("uref<", 'single-line-mode', sub {});
Packit e4b6da
$templates->add_rule("uref>", 'single-line-mode', sub {});
Packit e4b6da
$templates->add_rule("uref<", 'saved-text-mode', sub {});
Packit e4b6da
$templates->add_rule("uref>", 'saved-text-mode', sub {});
Packit e4b6da
Packit e4b6da
$templates->add_rule("uref<", 'verbatim-mode', sub {});
Packit e4b6da
$templates->add_rule("uref>", 'verbatim-mode', sub {});
Packit e4b6da
$templates->add_rule("uref<", 'menu-saved-text-mode', sub {});
Packit e4b6da
$templates->add_rule("uref>", 'menu-saved-text-mode', sub {});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Sectioning elements
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
sub section_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->parent->ext->{'lastchild'} = 'block';
Packit e4b6da
    $self->{tw}->output("\n\@" . $elem->name . ' ');
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub section_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
foreach my $gi
Packit e4b6da
    (qw(chapter section subsection subsubsection
Packit e4b6da
       majorheading chapheading heading subheading subsubheading
Packit e4b6da
       top unnumbered unnumberedsec unnumberedsubsec unnumberedsubsubsec
Packit e4b6da
       appendix appendixsec appendixsubsec appendixsubsubsec)) 
Packit e4b6da
{
Packit e4b6da
    $templates->add_rule("${gi}<", \&section_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", \&section_end_handler);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Paragraph
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('para<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('para>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
    
Packit e4b6da
        
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Verbatim displays
Packit e4b6da
#
Packit e4b6da
################################################
Packit e4b6da
Packit e4b6da
sub verbatim_block_start_handler 
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $self->{tw}->output('@' . $elem->name . "\n");
Packit e4b6da
    $templates->push_mode('verbatim-mode');
Packit e4b6da
}
Packit e4b6da
sub verbatim_block_end_handler
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n\@end " . $elem->name . "\n");
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
foreach my $gi
Packit e4b6da
    (qw(example display format)) {
Packit e4b6da
    $templates->add_rule("${gi}<", \&verbatim_block_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", \&verbatim_block_end_handler);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', 'verbatim-mode', \&illegal_element_handler);
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Quotation blocks
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
sub quotation_block_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $self->{tw}->output('@' . $elem->name . "\n");
Packit e4b6da
}
Packit e4b6da
sub quotation_block_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end " . $elem->name . "\n");
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
foreach my $gi
Packit e4b6da
    (qw(quotation cartouche flushleft flushright)) {
Packit e4b6da
    $templates->add_rule("${gi}<", \&quotation_block_start_handler);
Packit e4b6da
    $templates->add_rule("${gi}>", \&quotation_block_end_handler);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Lists
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('enumerate<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $self->{tw}->output("\@enumerate " . $elem->attr('begin') . "\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('enumerate>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end enumerate\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('itemize<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    
Packit e4b6da
    if($elem->attr('markchar') ne '') {
Packit e4b6da
        $self->{tw}->output("\@itemize ") 
Packit e4b6da
            . texi_escape($elem->attr('markchar')) 
Packit e4b6da
            . "\n";
Packit e4b6da
    } else {
Packit e4b6da
        $self->{tw}->output("\@itemize \@w\n");
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('itemize>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end itemize\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('varlist<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $self->{tw}->output("\@table \@asis\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('varlist>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end table\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('varlistentry<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('term<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    if($elem->parent->ext->{numterms}++) {
Packit e4b6da
        $self->{tw}->output("\@itemx ");
Packit e4b6da
    } else {
Packit e4b6da
        $self->{tw}->output("\@item ");
Packit e4b6da
    }
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('term>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('listitem<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    # listitem is used in both varlistentry and plain lists,
Packit e4b6da
    # but the @item markup is supplied by <term> in the former 
Packit e4b6da
    # case already.
Packit e4b6da
    if($elem->parent->name ne 'varlistentry') {
Packit e4b6da
        block_start($self, $elem);
Packit e4b6da
        $self->{tw}->output("\@item\n");
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Tables
Packit e4b6da
#
Packit e4b6da
#################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('multitable<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $elem->ext->{total_cols} = $elem->attr('cols');
Packit e4b6da
    $elem->ext->{column_data} = [];
Packit e4b6da
    $elem->ext->{colspec_current_colnum} = 0;
Packit e4b6da
    $elem->ext->{colnames} = {};
Packit e4b6da
    $elem->ext->{spannames} = {};
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('colspec<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    my $col;
Packit e4b6da
    if($elem->attr('colnum')) {
Packit e4b6da
        $col = $elem->attr('colnum');
Packit e4b6da
    } else {
Packit e4b6da
        $col = $elem->parent->ext->{colspec_current_colnum} + 1;
Packit e4b6da
    }
Packit e4b6da
    $elem->parent->ext->{colspec_current_colnum} = $col;
Packit e4b6da
Packit e4b6da
    if($elem->attr('colname') ne '') {
Packit e4b6da
        $elem->parent->ext->{colnames}->{$elem->attr('colname')} = $col;
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    $elem->parent->ext->{column_data}->[$col-1] = 
Packit e4b6da
        '' . $elem->attr('colwidth');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('spanspec<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    $elem->parent->ext->{spannames}->{$elem->attr('spanname')}
Packit e4b6da
        = [ $elem->attr('namest'), $elem->attr('nameend') ];
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('tbody<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    my $column_data = $elem->parent->ext->{column_data};
Packit e4b6da
    my $totalcols = $elem->parent->ext->{total_cols};
Packit e4b6da
Packit e4b6da
    my @vspans = ();
Packit e4b6da
    for(my $i = 0; $i < $totalcols; $i++) {
Packit e4b6da
        push(@vspans, 0);
Packit e4b6da
    }
Packit e4b6da
    $elem->ext->{current_vspans} = \@vspans;
Packit e4b6da
    
Packit e4b6da
    my $proportsum = 0;
Packit e4b6da
    for(my $i = 0; $i < $totalcols; $i++) {
Packit e4b6da
        my $colwidth = $column_data->[$i];
Packit e4b6da
        if($colwidth eq '') {
Packit e4b6da
            $colwidth = $column_data->[$i] = '1*';
Packit e4b6da
        }
Packit e4b6da
        
Packit e4b6da
        # Later we may support other types of width measure,
Packit e4b6da
        # so proportional measures should be written
Packit e4b6da
        # as "r*".
Packit e4b6da
        $colwidth =~ s/\*\s*$//;
Packit e4b6da
        $proportsum += $colwidth;
Packit e4b6da
    }
Packit e4b6da
   
Packit e4b6da
    my $columnfractions = '';
Packit e4b6da
    for(my $i = 0; $i < $totalcols; $i++) {
Packit e4b6da
        my $colwidth = $column_data->[$i];
Packit e4b6da
        $colwidth =~ s/\*\s*$//;
Packit e4b6da
        $columnfractions .= $colwidth/$proportsum . ' ';
Packit e4b6da
    }
Packit e4b6da
    $columnfractions =~ s/ $//;
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\n\@multitable \@columnfractions $columnfractions\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('tbody>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@end multitable\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('row<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@item\n");
Packit e4b6da
Packit e4b6da
    $elem->ext->{current_colnum} = 0;
Packit e4b6da
    tbl_advance_column($elem, $self->{tw}, 0, 1);
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('row>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
    my $vspans = $elem->parent->ext->{current_vspans};
Packit e4b6da
    for(my $i = 0; $i < @$vspans; $i++) {
Packit e4b6da
        $vspans->[$i]-- if $vspans->[$i] > 0;
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('entry<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    my $tableext = $elem->parent->parent->parent->ext;
Packit e4b6da
    my $namest; my $nameend;
Packit e4b6da
    if($elem->attr('spanname')) {
Packit e4b6da
        $namest = $tableext->{spannames}->{$elem->attr('spanname')}->[0];
Packit e4b6da
        $nameend = $tableext->{spannames}->{$elem->attr('spanname')}->[1];
Packit e4b6da
    } elsif($elem->attr('namest')) {
Packit e4b6da
        $namest = $elem->attr('namest');
Packit e4b6da
        $nameend = $elem->attr('nameend');
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    my $relative_advance = 1;
Packit e4b6da
    my $colnum;
Packit e4b6da
    if(defined $namest) {
Packit e4b6da
        my $col_st = $colnum = $tableext->{colnames}->{$namest};
Packit e4b6da
        my $col_end = $tableext->{colnames}->{$nameend};
Packit e4b6da
        
Packit e4b6da
        $relative_advance = $col_end - $col_st + 1;
Packit e4b6da
    } 
Packit e4b6da
    elsif($elem->attr('colname')) {
Packit e4b6da
        $colnum = $tableext->{colnames}->{$elem->attr('colname')};
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if(defined $colnum) {
Packit e4b6da
        tbl_advance_column($elem->parent, $self->{tw}, $colnum);
Packit e4b6da
    }
Packit e4b6da
 
Packit e4b6da
    $elem->ext->{relative_advance} = $relative_advance;
Packit e4b6da
Packit e4b6da
    if($elem->attr('morerows')) {
Packit e4b6da
        if($elem->attr('morerows') !~ /^\d+$/) {
Packit e4b6da
            warn_location($elem, "invalid morerows value --- ignoring\n");
Packit e4b6da
        } else {
Packit e4b6da
            for(my $i = 0; $i < $relative_advance; $i++) {
Packit e4b6da
                $elem->parent->parent->ext->{current_vspans}->[
Packit e4b6da
                    $elem->parent->ext->{current_colnum} - 1 + $i]
Packit e4b6da
                        = $elem->attr('morerows') + 1;
Packit e4b6da
            }
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('entry>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    tbl_advance_column($elem->parent, $self->{tw}, 
Packit e4b6da
                       0, $elem->ext->{relative_advance});
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
sub tbl_advance_column
Packit e4b6da
{
Packit e4b6da
    my ($row, $tw, $new_colnum, $relative_advance) = @_;
Packit e4b6da
Packit e4b6da
    my $old_colnum = $row->ext->{current_colnum};
Packit e4b6da
    my $total_cols = $row->parent->parent->ext->{total_cols};
Packit e4b6da
Packit e4b6da
    if($relative_advance) {
Packit e4b6da
        my $vspans = $row->parent->ext->{current_vspans};
Packit e4b6da
        for($new_colnum = $old_colnum + $relative_advance;
Packit e4b6da
            $new_colnum <= $total_cols && ($vspans->[$new_colnum - 1] > 0);
Packit e4b6da
            $new_colnum++)
Packit e4b6da
        {}
Packit e4b6da
    }
Packit e4b6da
    elsif($new_colnum == -1) {
Packit e4b6da
        $new_colnum = $total_cols + 1;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $row->ext->{current_colnum} = $new_colnum;
Packit e4b6da
Packit e4b6da
    $new_colnum = $total_cols if $new_colnum > $total_cols;
Packit e4b6da
    $old_colnum = 1           if $old_colnum == 0;
Packit e4b6da
Packit e4b6da
    $tw->output('@tab ' x ($new_colnum - $old_colnum));
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Graphics
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
sub image_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
    
Packit e4b6da
    my $filename = texi_escape($elem->attr('filename'));
Packit e4b6da
Packit e4b6da
    # The @image command has to have the basename and extension
Packit e4b6da
    # separated, so do that.
Packit e4b6da
    my $basename; my $extension;
Packit e4b6da
Packit e4b6da
    if($filename =~ /^(.+)(\.[^\.]+)$/) {
Packit e4b6da
        $basename = $1;
Packit e4b6da
        $extension = $2;
Packit e4b6da
    } else {
Packit e4b6da
        $basename = $filename;
Packit e4b6da
        $extension = '';
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if(defined $elem->attr('width') or
Packit e4b6da
       defined $elem->attr('height'))
Packit e4b6da
    {
Packit e4b6da
        $self->{tw}->savable_output('@image{' . $basename . ',' . 
Packit e4b6da
            texi_escape($elem->attr('width')) .
Packit e4b6da
            ',' . 
Packit e4b6da
            texi_escape($elem->attr('height')) .
Packit e4b6da
            ',,' .
Packit e4b6da
            $extension .
Packit e4b6da
            '}');
Packit e4b6da
    } 
Packit e4b6da
    else {
Packit e4b6da
        $self->{tw}->savable_output(
Packit e4b6da
            '@image{' . $basename . ',,,,' . $extension . '}');
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('image<', \&image_handler);
Packit e4b6da
$templates->add_rule('image<', 'single-line-mode', \&image_handler);
Packit e4b6da
$templates->add_rule('image<', 'saved-text-mode', \&image_handler);
Packit e4b6da
$templates->add_rule('image<', 'verbatim-mode', \&image_handler);
Packit e4b6da
$templates->add_rule('image<', 'menu-saved-text-mode', sub {});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Vertical spacing
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('sp<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@sp " . $elem->attr('lines') . "\n");
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('page<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n\@page\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Indices
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('indexterm<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    # We allow indexterm at block level just like
Packit e4b6da
    # DocBook.  When that happens, don't treat
Packit e4b6da
    # it as an inline (hence no @noindent mantra).
Packit e4b6da
    if($elem->parent->ext->{lastchild} ne 'block') {
Packit e4b6da
        mixed_inline_start($self, $elem);
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    my $class = $elem->attr('class');
Packit e4b6da
    $class = 'c' if $class eq 'cp';
Packit e4b6da
    $class = 'f' if $class eq 'fn';
Packit e4b6da
    $class = 'v' if $class eq 'vr';
Packit e4b6da
    $class = 'k' if $class eq 'ky';
Packit e4b6da
    $class = 'p' if $class eq 'pg';
Packit e4b6da
    $class = 't' if $class eq 'tp';
Packit e4b6da
    
Packit e4b6da
    # @cindex has to start on a new line.
Packit e4b6da
    # I don't know if we are in a middle of an inline
Packit e4b6da
    # command (eg @{code}) that @cindex would work
Packit e4b6da
    # and not disrupt the inline.  I'm just hoping it works.
Packit e4b6da
    # If it doesn't, then it is a dumb limitation!
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\n\@" . $class . 'index ');
Packit e4b6da
Packit e4b6da
    # Are @-commands allowed for indexed terms?
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('indexterm>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('printindex<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
Packit e4b6da
    my $class = $elem->attr('class');
Packit e4b6da
    $class = 'cp' if $class eq 'c';
Packit e4b6da
    $class = 'fn' if $class eq 'f';
Packit e4b6da
    $class = 'vr' if $class eq 'v';
Packit e4b6da
    $class = 'ky' if $class eq 'k';
Packit e4b6da
    $class = 'pg' if $class eq 'p';
Packit e4b6da
    $class = 'tp' if $class eq 't';
Packit e4b6da
    
Packit e4b6da
    $self->{tw}->output("\n\@printindex " . $class . "\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Character data
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'single-line-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    my $s = texi_escape($node->{Data});
Packit e4b6da
    
Packit e4b6da
    # Collapse spaces, no newlines.
Packit e4b6da
    $s =~ tr/ \t\n/ /s;
Packit e4b6da
    $s = $self->{tw}->texi_arg_escape_conditional($s);
Packit e4b6da
Packit e4b6da
    $self->{tw}->savable_output($s);
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
sub saved_text_mode_handler
Packit e4b6da
{
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    my $s = texi_escape($node->{Data});
Packit e4b6da
Packit e4b6da
    # Collapse spaces, no newlines.
Packit e4b6da
    $s =~ tr/ \t\n/ /s;
Packit e4b6da
    $s = $self->{tw}->texi_arg_escape_conditional($s);
Packit e4b6da
Packit e4b6da
    $self->{tw}->savable_output($s);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub menu_saved_text_mode_handler
Packit e4b6da
{
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    my $s = texi_escape($node->{Data});
Packit e4b6da
Packit e4b6da
    # Collapse spaces, no newlines.
Packit e4b6da
    $s =~ tr/ \t\n/ /s;
Packit e4b6da
Packit e4b6da
    $self->{tw}->savable_output($s);
Packit e4b6da
}
Packit e4b6da
$templates->add_rule('text()', 'saved-text-mode', 
Packit e4b6da
    \&saved_text_mode_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'menu-saved-text-mode', 
Packit e4b6da
    \&menu_saved_text_mode_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'menu-mode', sub {});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'verbatim-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    my $s = texi_escape($node->{Data});
Packit e4b6da
Packit e4b6da
    $self->{tw}->print($s);
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    my $s = texi_escape($node->{Data});
Packit e4b6da
    
Packit e4b6da
    mixed_inline_start($self, $node)
Packit e4b6da
        unless $s =~ /^[ \t\r\n]+$/;
Packit e4b6da
            # Whitespace used to separate element
Packit e4b6da
            # in a non-mixed content model should
Packit e4b6da
            # not cause any spurious breaks.
Packit e4b6da
Packit e4b6da
    $self->{tw}->print_ws($s);
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Comments
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('comment<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{tw}->output("\n");
Packit e4b6da
    $self->{tw}->output_buffer_push();
Packit e4b6da
    $templates->push_mode('comment-mode');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('comment>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode('comment-mode');
Packit e4b6da
    
Packit e4b6da
    foreach my $line (split(/\n/, $self->{tw}->output_buffer_pop())) {
Packit e4b6da
        $self->{tw}->output("\@c $line\n");
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'comment-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    my $s = $node->{Data};
Packit e4b6da
    $self->{tw}->savable_output($s);
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Processing instructions
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('processing-instruction()', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($node->{Target} eq 'texinfo') {
Packit e4b6da
        my $data = $node->{Data};
Packit e4b6da
        $data =~ s/\
/\n/g;
Packit e4b6da
        $data =~ s/\
/\n/g;
Packit e4b6da
        $self->{tw}->output($data);
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Catch unknown elements
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', \&illegal_element_handler);
Packit e4b6da
sub illegal_element_handler {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    $templates->warn_location($node, "element not allowed here");
Packit e4b6da
};
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
#############################################################################
Packit e4b6da
#
Packit e4b6da
# Main 
Packit e4b6da
#
Packit e4b6da
#############################################################################
Packit e4b6da
Packit e4b6da
package main;
Packit e4b6da
Packit e4b6da
use XML::SAX::ParserFactory;
Packit e4b6da
Packit e4b6da
unshift(@ARGV, '-') unless @ARGV;
Packit e4b6da
my $parser = XML::SAX::ParserFactory->parser(
Packit e4b6da
        DocumentHandler => $texixml::templates);
Packit e4b6da
Packit e4b6da
foreach my $file (@ARGV)
Packit e4b6da
{
Packit e4b6da
    $texixmldata->{inputfile} = $file;
Packit e4b6da
    if($file eq '-') {
Packit e4b6da
        $parser->parse_file(\*STDIN);
Packit e4b6da
    } else {
Packit e4b6da
        $parser->parse_uri($file);
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da