Blame perl/db2x_manxml.pl

Packit e4b6da
# vim:sw=4 sta et showmatch
Packit e4b6da
Packit e4b6da
# db2x_manxml - convert Man-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_manxml.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('gnu_getopt');
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
    'symlinks' => 0,
Packit e4b6da
    'solinks' => 0,
Packit e4b6da
    'no-links' => 0,
Packit e4b6da
    'utf8trans-program' => $db2x_config{'utf8trans-program'},
Packit e4b6da
    'utf8trans-map' => $db2x_config{'utf8trans-map-roff'},
Packit e4b6da
    'iconv-program' => $db2x_config{'iconv-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 man pages from Man-XML
Packit e4b6da
Packit e4b6da
  --encoding=ENCODING   Character encoding for man pages
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
  
Packit e4b6da
  Some man pages are made available under multiple names. Use one of 
Packit e4b6da
  the following to select how should these names be made available:
Packit e4b6da
  --symlinks            Symbolic links to principal man page
Packit e4b6da
  --solinks             Stub pages with .so requests to principal man page
Packit e4b6da
  --no-links            Make man page available only under principal name
Packit e4b6da
Packit e4b6da
Packit e4b6da
  These options set the location of auxiliary programs:
Packit e4b6da
  --utf8trans-program=PATH, --utf8trans-map=PATH, --iconv-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_manxml(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_manxml (part of docbook2X " . 
Packit e4b6da
            $db2x_config{'docbook2X-version'} . ")\n";
Packit e4b6da
    print <<'end';
Packit e4b6da
$Revision: 1.62 $ $Date: 2006/04/22 15:21:32 $
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
    'symlinks|sym-links',
Packit e4b6da
    'solinks|so-links',
Packit e4b6da
    'no-links|nolinks',
Packit e4b6da
    'utf8trans-program=s',
Packit e4b6da
    'utf8trans-map=s',
Packit e4b6da
    'iconv-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
if($cmdoptions->{'symlinks'} +
Packit e4b6da
   $cmdoptions->{'solinks'} +
Packit e4b6da
   $cmdoptions->{'no-links'} > 1)
Packit e4b6da
{
Packit e4b6da
    print STDERR "$0: Only one of --symlinks, --solinks or --no-links options is allowed!\n";
Packit e4b6da
    exit 1;
Packit e4b6da
} elsif($cmdoptions->{'symlinks'} +
Packit e4b6da
   $cmdoptions->{'solinks'} +
Packit e4b6da
   $cmdoptions->{'no-links'} == 0)
Packit e4b6da
{
Packit e4b6da
   $cmdoptions->{'no-links'} = 1;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#use XML::Handler::SGMLSpl;     # we link to this explicitly during building
Packit e4b6da
$manxml::templates = XML::Handler::SGMLSpl->new(
Packit e4b6da
                        { 'options' => $cmdoptions });
Packit e4b6da
$manxml::templates->push_mode('file-unselected');
Packit e4b6da
$manxml::templates->{namespaces}->{''} = "http://docbook2x.sourceforge.net/xmlns/Man-XML";
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# RoffWriter: Sanitized output routines
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
package RoffWriter;
Packit e4b6da
require Exporter;
Packit e4b6da
@RoffWriter::ISA = qw(Exporter);
Packit e4b6da
@RoffWriter::EXPORT_OK = qw(man_escape man_normalize_ws);
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Use RoffWriter on specified file
Packit e4b6da
# Params: fh - an IO::Handle to send the output
Packit e4b6da
#
Packit e4b6da
sub new
Packit e4b6da
{
Packit e4b6da
    my ($class, $fh) = @_;
Packit e4b6da
    my $self = { fh => $fh, line_start => 1, whitespace_last => 0 };
Packit e4b6da
    return bless($self, $class);
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Write output to filehandle
Packit e4b6da
# or the string buffer (see buffer_{on,off}).
Packit e4b6da
#
Packit e4b6da
sub write
Packit e4b6da
{
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
    if(defined $self->{stringbuffer}) {
Packit e4b6da
        $self->{stringbuffer} .= $text;
Packit e4b6da
    } else {
Packit e4b6da
        $self->{fh}->print($text);
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# buffer_on, buffer_off
Packit e4b6da
#
Packit e4b6da
# Hack to allow the user to buffer output temporarily
Packit e4b6da
# to a string which can be back pasted in later with write().
Packit e4b6da
#
Packit e4b6da
# This is used, in particular, for tbl output:
Packit e4b6da
# The column and span width information must precede
Packit e4b6da
# the cell data for each row, but the cell data may be 
Packit e4b6da
# arbitrarily complex text mixed with any number of 
Packit e4b6da
# roff requests.
Packit e4b6da
#
Packit e4b6da
# Don't take this facility lightly :)
Packit e4b6da
# It is low-level: it intentionally does not save or restore
Packit e4b6da
# the line_start internal state --- you must do that yourself,
Packit e4b6da
# or use this facility only at well-defined points
Packit e4b6da
# (e.g. only at line_start = 1).
Packit e4b6da
#
Packit e4b6da
sub buffer_on
Packit e4b6da
{
Packit e4b6da
    my ($self) = @_;
Packit e4b6da
    $self->{stringbuffer} = '';
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub buffer_off
Packit e4b6da
{
Packit e4b6da
    my ($self) = @_;
Packit e4b6da
    my $stringbuffer = $self->{stringbuffer};
Packit e4b6da
    $self->{stringbuffer} = undef;
Packit e4b6da
    return $stringbuffer;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Print text with whitespace 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
{
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
Packit e4b6da
    my @lines = split(/(\n)/, $text);
Packit e4b6da
    
Packit e4b6da
    foreach my $line (@lines) {
Packit e4b6da
        # Go to beginning of next line
Packit e4b6da
        # unless already at beginning of a line
Packit e4b6da
        if($line eq "\n") {
Packit e4b6da
            $self->write("\n")
Packit e4b6da
                unless $self->{line_start}++;
Packit e4b6da
        } else {
Packit e4b6da
            # No spaces at the beginning of a line.
Packit e4b6da
            # 
Packit e4b6da
            # The second if condition ensures that whenever the 
Packit e4b6da
            # 'logical' input contains a string of whitespace, but
Packit e4b6da
            # the 'logical' input is split into two separate print_ws
Packit e4b6da
            # in the middle of the string of whitespace, like this:
Packit e4b6da
            #
Packit e4b6da
            # print_ws('text      ') ; print_ws('     more text')
Packit e4b6da
            #
Packit e4b6da
            # Then the output should be 'text more text'
Packit e4b6da
            # with one space in between the (first two) words.
Packit e4b6da
            #
Packit e4b6da
            # NB: The use of 'whitespace' here excludes the newline.
Packit e4b6da
            # NB: whitespace_last is only meaningful when
Packit e4b6da
            #     line_start == 0.  This interpretation
Packit e4b6da
            #     allows us to not have to update 
Packit e4b6da
            #     the whitespace_last status as much.
Packit e4b6da
            #
Packit e4b6da
            $line =~ s/^[ \t]+// if $self->{line_start}
Packit e4b6da
                or $self->{whitespace_last};
Packit e4b6da
Packit e4b6da
            # "." and "'" get misinterpreted as a request
Packit e4b6da
            # at beginning of lines, so use a no-width space
Packit e4b6da
            # to prevent that.
Packit e4b6da
            $line =~ s/^([.'])/\\\&$1/ if $self->{line_start};
Packit e4b6da
Packit e4b6da
            # Fold whitespaces in the middle of the line
Packit e4b6da
            $line =~ tr/ \t/ /s;
Packit e4b6da
Packit e4b6da
            if($line ne '') {
Packit e4b6da
                $self->write($line);
Packit e4b6da
                $self->{line_start} = 0;
Packit e4b6da
                $self->{whitespace_last} = ($line =~ /[ \t]$/);
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
{
Packit e4b6da
    my ($self, $text) = @_;
Packit e4b6da
            
Packit e4b6da
    my @lines = split(/(\n)/, $text);
Packit e4b6da
    
Packit e4b6da
    foreach my $line (@lines) {
Packit e4b6da
        if($line eq "\n") {
Packit e4b6da
            $self->write("\n");
Packit e4b6da
            $self->{line_start} = 1;
Packit e4b6da
        } else {
Packit e4b6da
            # "." and "'" get misinterpreted as a request
Packit e4b6da
            # at beginning of lines, so use a no-width space
Packit e4b6da
            # to prevent that.
Packit e4b6da
            $line =~ s/^([.'])/\\\&$1/ if $self->{line_start};
Packit e4b6da
Packit e4b6da
            $self->{line_start} = ($line eq '');
Packit e4b6da
            $self->{whitespace_last} = ($line =~ /[ \t]$/);
Packit e4b6da
            $self->write($line);
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub skip_line
Packit e4b6da
{
Packit e4b6da
    my ($self) = @_;
Packit e4b6da
    $self->write(($self->{line_start}++? "\n" : "\n\n"));
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Make a roff "request"
Packit e4b6da
# Params: name - request identifier.  The beginning '.' is optional.
Packit e4b6da
#         args - request arguments, in raw form.
Packit e4b6da
#                If there are characters to be escaped,
Packit e4b6da
#                wrap the string in a call to string_escape.
Packit e4b6da
# Does not support using a different request start character.
Packit e4b6da
#
Packit e4b6da
sub request
Packit e4b6da
{
Packit e4b6da
    my ($self, $name, @args) = @_;
Packit e4b6da
Packit e4b6da
    $name = ".$name" if $name !~ /^[\.']/;
Packit e4b6da
Packit e4b6da
    $self->write 
Packit e4b6da
        ($self->{line_start} ? "$name" : "\n$name");
Packit e4b6da
        
Packit e4b6da
    foreach my $arg (@args) {
Packit e4b6da
        $arg =~ tr/\n/ /;
Packit e4b6da
Packit e4b6da
        # Suggestion from groff Info manual.
Packit e4b6da
        # Escape double quotes using \(dq
Packit e4b6da
        $arg =~ s/"/\\\(dq/g;
Packit e4b6da
Packit e4b6da
        # Quote arguments if either
Packit e4b6da
        # 1. the argument contains spaces
Packit e4b6da
        # 2. the argument is empty, and it is not the sole
Packit e4b6da
        #    argument to the request
Packit e4b6da
        $arg = '"' . $arg . '"' if ($arg =~ / /
Packit e4b6da
            or (@args>1 and $arg eq ''));
Packit e4b6da
        $self->write(' ' . $arg);
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->write("\n");
Packit e4b6da
    $self->{line_start} = 1;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Print a comment in the output.
Packit e4b6da
# Params: comment - the comment text.  
Packit e4b6da
#                   May use any characters; they need not be escaped.
Packit e4b6da
#
Packit e4b6da
sub comment
Packit e4b6da
{
Packit e4b6da
    my ($self, $comment) = @_;
Packit e4b6da
    $self->write("\n") unless $self->{line_start};
Packit e4b6da
Packit e4b6da
    foreach my $line (split(/\n/, $comment)) {
Packit e4b6da
        $self->write('.\" ');
Packit e4b6da
        $self->write($line);
Packit e4b6da
        $self->write("\n");
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->{line_start} = 1;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Use a roff "escape" i.e. commands embedded in text starting with \
Packit e4b6da
# Params: escape - the escape sequence, excluding \
Packit e4b6da
# Does not support using a different escape character.
Packit e4b6da
#
Packit e4b6da
sub escape
Packit e4b6da
{
Packit e4b6da
    my ($self, $escape) = @_;
Packit e4b6da
    $self->write("\\$escape");
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Escape characters special to roff so they are displayed literally
Packit e4b6da
# Params: s - the string to escape
Packit e4b6da
# Returns: new string, with offending characters escaped
Packit e4b6da
#
Packit e4b6da
sub man_escape
Packit e4b6da
{
Packit e4b6da
    my $s = shift;
Packit e4b6da
    $s =~ s/\\/\\e/g;
Packit e4b6da
    return $s;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub man_normalize_ws
Packit e4b6da
{
Packit e4b6da
    my $s = shift;
Packit e4b6da
    $s =~ tr/[ \t\n]/ /s;
Packit e4b6da
    $s =~ s/(^ )|( $)//;
Packit e4b6da
    return $s;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
package manxml;
Packit e4b6da
import RoffWriter qw(man_escape man_normalize_ws);
Packit e4b6da
Packit e4b6da
use IO::File;
Packit e4b6da
use vars qw($templates);
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Man page management
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
# Remove leading and trailing blanks.
Packit e4b6da
sub strip_string
Packit e4b6da
{
Packit e4b6da
    my $str = shift;
Packit e4b6da
Packit e4b6da
    $str = $1 if ($str =~ m#^\s*(\S.*)#);
Packit e4b6da
    $str = $1 if ($str =~ m#^(.*\S)\s*$#);
Packit e4b6da
Packit e4b6da
    return $str;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Generate a good file name for a man page, given a title and section
Packit e4b6da
# 
Packit e4b6da
# Params: title - man page title
Packit e4b6da
#         section - 1, 2, 3, 4, etc.
Packit e4b6da
#
Packit e4b6da
sub man_filename
Packit e4b6da
{
Packit e4b6da
    my $title = strip_string(shift);
Packit e4b6da
    my $sect = strip_string(shift);
Packit e4b6da
Packit e4b6da
    # Escape the path separator '/' which is not allowed
Packit e4b6da
    # in Unix file names. (NUL is not allowed either, but 
Packit e4b6da
    # it can't occur in XML data anyway.)
Packit e4b6da
    # Also escape lone . or .., or blank file name,
Packit e4b6da
    # which are also not allowed.
Packit e4b6da
    # 
Packit e4b6da
    # We deliberate do NOT escape any other characters
Packit e4b6da
    # (e.g. space) because the user who happens to want
Packit e4b6da
    # to use a space in his file names, for whatever reason,
Packit e4b6da
    # would be justifiably annoyed at us if we had hard-coded
Packit e4b6da
    # any unnecessary escaping here. 
Packit e4b6da
    #
Packit e4b6da
    # On the other hand, for people who want escaping, they 
Packit e4b6da
    # can either fix their XML sources or stylesheets, very easily.
Packit e4b6da
Packit e4b6da
    $title =~ tr/\//_/;
Packit e4b6da
    $sect =~ tr/\//_/;
Packit e4b6da
Packit e4b6da
    $title =~ s/^(\.{0,2})$/_$1/;
Packit e4b6da
Packit e4b6da
    return "$title.$sect";
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 man_openfile
Packit e4b6da
{
Packit e4b6da
    my ($self, $filename) = @_;
Packit e4b6da
    my $encoding = $self->{options}->{'encoding'};
Packit e4b6da
    my $dir = $self->{options}->{'output-dir'};
Packit e4b6da
    $dir =~ s/([^\/])$/$1\//;     # terminate with slash
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($openstr eq '') {
Packit e4b6da
        if(!$self->{options}->{'to-stdout'}) {
Packit e4b6da
            $openstr = "${dir}$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 "${dir}$filename\n"
Packit e4b6da
                if $self->{options}->{'list-files'};
Packit e4b6da
        } else {
Packit e4b6da
            $openstr = '>-';
Packit e4b6da
        }
Packit e4b6da
    } else {
Packit e4b6da
        if(!$self->{options}->{'to-stdout'}) {
Packit e4b6da
            $openstr .= '> ' . shell_quote("${dir}$filename");
Packit e4b6da
            print "${dir}$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('manpage<', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    my $filename = man_filename(
Packit e4b6da
                    $elem->attr('title'),
Packit e4b6da
                    $elem->attr('sect'));
Packit e4b6da
  
Packit e4b6da
    $self->{fh} = man_openfile($self, $filename);
Packit e4b6da
    $self->{rw} = new RoffWriter($self->{fh});
Packit e4b6da
Packit e4b6da
    $self->{'adjust-stack'} = [ 'b' ];
Packit e4b6da
Packit e4b6da
    $self->{rw}->comment($elem->attr('preprocessors'))
Packit e4b6da
        if($elem->attr('preprocessors') ne '');
Packit e4b6da
Packit e4b6da
    # I've dug through the Internet to see if there was any
Packit e4b6da
    # standard way to specify encoding with man pages.
Packit e4b6da
    # The following seems to be a reasonable proposal:
Packit e4b6da
    # <URL:http://mail.nl.linux.org/linux-utf8/2001-04/msg00168.html>
Packit e4b6da
    my $encoding = $self->{options}->{'encoding'};
Packit e4b6da
    $encoding =~ s#//TRANSLIT$##i;
Packit e4b6da
    $self->{rw}->comment("-*- coding: $encoding -*-");
Packit e4b6da
    
Packit e4b6da
    # Define escapes for switching to and from monospace fonts (groff only)
Packit e4b6da
    $self->{rw}->request(qw{ .if \n(.g .ds T< \\\\FC});
Packit e4b6da
    $self->{rw}->request(qw{ .if \n(.g .ds T> \\\\F[\n[.fam]]});
Packit e4b6da
Packit e4b6da
    # Provide the URL macro
Packit e4b6da
    $self->{rw}->request(qw{ .de URL});
Packit e4b6da
    $self->{rw}->print('\\\\$2 \(la\\\\$1\(ra\\\\$3');
Packit e4b6da
    $self->{rw}->request('..');
Packit e4b6da
    $self->{rw}->request(qw{ .if \n(.g .mso www.tmac});
Packit e4b6da
Packit e4b6da
    $self->{rw}->request('TH',
Packit e4b6da
        # Nothing in the man macros say this has to be the same as
Packit e4b6da
        # the $file and $sect.  While it is best to follow convention,
Packit e4b6da
        # some stylesheets may want to uppercase/lowercase the name, 
Packit e4b6da
        # so it is best leave this to them.
Packit e4b6da
        man_normalize_ws(man_escape($elem->attr('h1'))),
Packit e4b6da
        man_normalize_ws(man_escape($elem->attr('h2'))),
Packit e4b6da
        man_normalize_ws(man_escape($elem->attr('h3'))),
Packit e4b6da
        man_normalize_ws(man_escape($elem->attr('h4'))),
Packit e4b6da
        man_normalize_ws(man_escape($elem->attr('h5'))));
Packit e4b6da
    
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('manpage>', 'file-unselected', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    $self->{rw}->print_ws("\n");        # ensure file ends in eol
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
    $self->{fh} = undef;
Packit e4b6da
    $self->{rw} = undef;
Packit e4b6da
Packit e4b6da
    my $mainfilename = man_filename($elem->attr('title'),
Packit e4b6da
                                    $elem->attr('sect'));
Packit e4b6da
Packit e4b6da
    # Make the files for the alternate names for the man page
Packit e4b6da
    foreach my $refname (@{$elem->ext->{'refnames'}})
Packit e4b6da
    {
Packit e4b6da
        my $filename = man_filename($refname, $elem->attr('sect'));
Packit e4b6da
Packit e4b6da
        if($filename eq $mainfilename || $self->{options}->{'no-links'}
Packit e4b6da
            || $self->{options}->{'to-stdout'})
Packit e4b6da
        {
Packit e4b6da
            # Same as main man page, don't make link.
Packit e4b6da
        }
Packit e4b6da
        else {
Packit e4b6da
            print STDOUT "$filename\n"
Packit e4b6da
                if $self->{options}->{'list-files'};
Packit e4b6da
            
Packit e4b6da
            if($self->{options}->{'symlinks'}) {
Packit e4b6da
                symlink($mainfilename, $filename);
Packit e4b6da
        
Packit e4b6da
            } else {
Packit e4b6da
                my $sectnum = $1 if $mainfilename =~ /^.+\.(\d)/;
Packit e4b6da
Packit e4b6da
                my $fh = new IO::File $filename, "w";
Packit e4b6da
                my $rw = new RoffWriter($fh);
Packit e4b6da
Packit e4b6da
                $rw->request('so', 
Packit e4b6da
                    man_escape("man${sectnum}/$mainfilename"));
Packit e4b6da
Packit e4b6da
                $fh->close;
Packit e4b6da
            }
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $templates->push_mode('file-unselected');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('manpageset<', 'file-unselected', sub {});
Packit e4b6da
$templates->add_rule('manpageset>', 'file-unselected', sub {});
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
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# A clean solution to the extra-newlines problem
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
# Call before almost block-level element; needed to ensure
Packit e4b6da
# hanging indent and line-breaking before and after
Packit e4b6da
# is handled correctly.
Packit e4b6da
sub block_start
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem) = @_;
Packit e4b6da
    my $lastchild = $elem->parent->ext->{lastchild};
Packit e4b6da
Packit e4b6da
    
Packit e4b6da
    # There's no macro to stop the hang indent without
Packit e4b6da
    # starting a new paragraph (.PP), so it has to be
Packit e4b6da
    # done at the beginning of the next block.
Packit e4b6da
    if($lastchild eq 'hanging') {
Packit e4b6da
        $self->{rw}->request('PP');
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    # Conversely, here we are supposed to be hanging, 
Packit e4b6da
    # so do not use .PP, which would nullify it
Packit e4b6da
    elsif($elem->within('TPitem')) {
Packit e4b6da
        $self->{rw}->skip_line()
Packit e4b6da
            unless $lastchild eq '';    # Don't put blank before first block
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    # Start a new 'paragraph'
Packit e4b6da
    elsif($lastchild eq 'block') {
Packit e4b6da
        # .PP macro messes up the indentation when
Packit e4b6da
        # used inside a table entry.
Packit e4b6da
        if($elem->parent->name eq 'entry') {
Packit e4b6da
            $self->{rw}->skip_line();
Packit e4b6da
        } else {
Packit e4b6da
            $self->{rw}->request('PP');
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    # Could also be done with .PP.
Packit e4b6da
    elsif($lastchild eq 'inline') {
Packit e4b6da
        $self->{rw}->skip_line();
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $elem->parent->ext->{lastchild} = 'block';
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub mixed_inline_start
Packit e4b6da
{
Packit e4b6da
    my ($self, $node) = @_;
Packit e4b6da
Packit e4b6da
    # If text is just whitespace, then breaking should not be done
Packit e4b6da
    # at this point. Otherwise the following input
Packit e4b6da
    #
Packit e4b6da
    # <para>xxx<TP>...</TP>
Packit e4b6da
    # </para><para>yyy</para>
Packit e4b6da
    #
Packit e4b6da
    # would lead to extra .PP requests:
Packit e4b6da
    #
Packit e4b6da
    # xxx
Packit e4b6da
    # .PP
Packit e4b6da
    # .PP
Packit e4b6da
    # yyy
Packit e4b6da
    #
Packit e4b6da
    # the first .PP being caused by the newline 
Packit e4b6da
    # at the end of the </TP> tag that is otherwise meaningless.
Packit e4b6da
    #
Packit e4b6da
    # So quit now if the text node is whitespace.
Packit e4b6da
    # Note this means we also do not change the lastchild
Packit e4b6da
    # status; this is correct behavior, because we still
Packit e4b6da
    # want breaking to be done later if necessary.
Packit e4b6da
    return if $node->{Data} !~ /[^ \t\r\n]/;
Packit e4b6da
Packit e4b6da
    if($node->parent->ext->{lastchild} eq 'block') {
Packit e4b6da
        $self->{rw}->skip_line();
Packit e4b6da
    }
Packit e4b6da
    elsif($node->parent->ext->{lastchild} eq 'hanging') {
Packit e4b6da
        # Skipping a line after a hanging paragraph
Packit e4b6da
        # doesn't work, because we want to stop
Packit e4b6da
        # the indent
Packit e4b6da
        $self->{rw}->request('PP');
Packit e4b6da
    }
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
# Changing fonts
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('b<', \&bold_start_handler);
Packit e4b6da
$templates->add_rule('b>', \&bold_end_handler);
Packit e4b6da
$templates->add_rule('i<', \&italic_start_handler);
Packit e4b6da
$templates->add_rule('i>', \&italic_end_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('b<', 'single-line-mode', \&bold_start_handler);
Packit e4b6da
$templates->add_rule('b>', 'single-line-mode', \&bold_end_handler);
Packit e4b6da
$templates->add_rule('i<', 'single-line-mode', \&italic_start_handler);
Packit e4b6da
$templates->add_rule('i>', 'single-line-mode', \&italic_end_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('b<', 'table-mode', \&bold_start_handler);
Packit e4b6da
$templates->add_rule('b>', 'table-mode', \&bold_end_handler);
Packit e4b6da
$templates->add_rule('i<', 'table-mode', \&italic_start_handler);
Packit e4b6da
$templates->add_rule('i>', 'table-mode', \&italic_end_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('b<', 'verbatim-mode', \&bold_start_handler);
Packit e4b6da
$templates->add_rule('b>', 'verbatim-mode', \&bold_end_handler);
Packit e4b6da
$templates->add_rule('i<', 'verbatim-mode', \&italic_start_handler);
Packit e4b6da
$templates->add_rule('i>', 'verbatim-mode', \&italic_end_handler);
Packit e4b6da
Packit e4b6da
Packit e4b6da
sub bold_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
Packit e4b6da
    # If the last font is also bold, don't change anything.
Packit e4b6da
    # Basically this is to just get more readable man output.
Packit e4b6da
    $self->{rw}->escape('fB')
Packit e4b6da
        unless $elem->in('b');
Packit e4b6da
}
Packit e4b6da
sub bold_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($elem->in('i')) { 
Packit e4b6da
        $self->{rw}->escape('fI');
Packit e4b6da
    }
Packit e4b6da
    elsif($elem->in('b')) { }
Packit e4b6da
    else {
Packit e4b6da
        $self->{rw}->escape('fR');
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
    
Packit e4b6da
sub italic_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    mixed_inline_start($self, $elem);
Packit e4b6da
Packit e4b6da
    # If the last font is also bold, don't change anything.
Packit e4b6da
    # Basically this is to just get more readable man output.
Packit e4b6da
    $self->{rw}->escape('fI')
Packit e4b6da
        unless $elem->in('i');
Packit e4b6da
}
Packit e4b6da
sub italic_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($elem->in('b')) { 
Packit e4b6da
        $self->{rw}->escape('fB');
Packit e4b6da
    }
Packit e4b6da
    elsif($elem->in('i')) { }
Packit e4b6da
    else {
Packit e4b6da
        $self->{rw}->escape('fR');
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
# Fixed-width fonts
Packit e4b6da
# 
Packit e4b6da
# We do not use the font "CW", because you then cannot apply bold 
Packit e4b6da
# and italic to it.  Although groff comes with the fonts 
Packit e4b6da
# "CI" and "CB" (in Postscript), they are not defined
Packit e4b6da
# for TTY output! 
Packit e4b6da
#
Packit e4b6da
# Switching the font family (with \Fx) is a groff extension; to be 
Packit e4b6da
# compatible with other man-page processors we use a custom escape
Packit e4b6da
# instead of outputing \Fx directly.  See the handling of the manpage
Packit e4b6da
# element for the definition of these escapes.
Packit e4b6da
#
Packit e4b6da
sub monospace_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if(not $elem->within('tt')) {
Packit e4b6da
        $self->{rw}->escape('*(T<');
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
sub monospace_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if(not $elem->within('tt')) {
Packit e4b6da
        $self->{rw}->escape('*(T>');
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('tt<', \&monospace_start_handler);
Packit e4b6da
$templates->add_rule('tt>', \&monospace_end_handler);
Packit e4b6da
$templates->add_rule('tt<', 'single-line-mode', \&monospace_start_handler);
Packit e4b6da
$templates->add_rule('tt>', 'single-line-mode', \&monospace_end_handler);
Packit e4b6da
$templates->add_rule('tt<', 'table-mode', \&monospace_start_handler);
Packit e4b6da
$templates->add_rule('tt>', 'table-mode', \&monospace_end_handler);
Packit e4b6da
$templates->add_rule('tt<', 'verbatim-mode', \&monospace_start_handler);
Packit e4b6da
$templates->add_rule('tt>', 'verbatim-mode', \&monospace_end_handler);
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Groff URL macros
Packit e4b6da
# 
Packit e4b6da
sub ulink_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    $templates->push_mode('section-mode');
Packit e4b6da
    $self->{output_save} = '';
Packit e4b6da
}
Packit e4b6da
sub ulink_end_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    my $s = $self->{output_save};
Packit e4b6da
    $self->{output_save} = undef;
Packit e4b6da
Packit e4b6da
    $self->{rw}->request('URL', 
Packit e4b6da
        man_escape($elem->attr('url')), $s);
Packit e4b6da
}
Packit e4b6da
sub ulink_start_plain_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
}
Packit e4b6da
sub ulink_end_plain_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{rw}->print_ws(
Packit e4b6da
        ' \(la' . man_escape($elem->attr('ulink')) . '\(ra ');
Packit e4b6da
}
Packit e4b6da
    
Packit e4b6da
$templates->add_rule('ulink<', \&ulink_start_handler);
Packit e4b6da
$templates->add_rule('ulink>', \&ulink_end_handler);
Packit e4b6da
$templates->add_rule('ulink<', 'single-line-mode', \&ulink_start_plain_handler);
Packit e4b6da
$templates->add_rule('ulink>', 'single-line-mode', \&ulink_end_plain_handler);
Packit e4b6da
$templates->add_rule('ulink<', 'table-mode', \&ulink_start_handler);
Packit e4b6da
$templates->add_rule('ulink>', 'table-mode', \&ulink_end_handler);
Packit e4b6da
$templates->add_rule('ulink<', 'verbatim-mode', \&ulink_start_plain_handler);
Packit e4b6da
$templates->add_rule('ulink>', 'verbatim-mode', \&ulink_end_plain_handler);
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Turn off/on hyphenation (used for long URI's and the like)
Packit e4b6da
#
Packit e4b6da
sub hyphenation_off {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    return if $templates->mode() eq 'single-line-mode';
Packit e4b6da
    $self->{rw}->request("'nh");
Packit e4b6da
}
Packit e4b6da
sub hyphenation_on {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    return if $templates->mode() eq 'single-line-mode';
Packit e4b6da
    $self->{rw}->request("'hy");
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('nh<', \&hyphenation_off);
Packit e4b6da
$templates->add_rule('nh>', \&hyphenation_on);
Packit e4b6da
$templates->add_rule('nh<', 'table-mode', \&hyphenation_off);
Packit e4b6da
$templates->add_rule('nh>', 'table-mode', \&hyphenation_on);
Packit e4b6da
$templates->add_rule('nh<', 'single-line-mode', sub {});
Packit e4b6da
$templates->add_rule('nh<', 'verbatim-mode', sub {});
Packit e4b6da
Packit e4b6da
# roff distinguishes between the ASCII hyphen-minus
Packit e4b6da
# and a normal hyphen. 
Packit e4b6da
# See <URL:http://www.cs.tut.fi/~jkorpela/dashes.html>
Packit e4b6da
Packit e4b6da
sub disambiguate_hyphen_minus 
Packit e4b6da
{
Packit e4b6da
    my ($text, $elem) = @_;
Packit e4b6da
    
Packit e4b6da
    if(!$elem or $elem->within('tt') or $elem->within('verbatim')) {
Packit e4b6da
        $text =~ s/-/\\-/g;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    return $text;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub disable_hyphenation
Packit e4b6da
{
Packit e4b6da
    my ($text, $elem) = @_;
Packit e4b6da
    return $text;       # Disable for now
Packit e4b6da
Packit e4b6da
    if($elem and $elem->within('tt') and not $elem->within('verbatim')) {
Packit e4b6da
        $text =~ s/ / \\%/g;
Packit e4b6da
    }
Packit e4b6da
    return $text;
Packit e4b6da
}
Packit e4b6da
 
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# NAME section
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
# The man page database parser needs the names and 
Packit e4b6da
# description all on one line, so enter a special mode to do that.
Packit e4b6da
$templates->add_rule('refnameline<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->parent->ext->{lastchild} = 'block';
Packit e4b6da
    $templates->push_mode('refnameline-mode');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('refnameline>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{rw}->print_ws("\n");
Packit e4b6da
    $elem->ext->{'refnames'} = 0;
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'refnameline-mode',
Packit e4b6da
    \&single_line_text_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('refname<', 'refnameline-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->push_mode('refname');
Packit e4b6da
    $self->{output_save} = '';
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('refname>', 'refnameline-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
Packit e4b6da
    if($elem->parent->ext->{'refnames'}++) {
Packit e4b6da
        $self->{rw}->print_ws(', ');
Packit e4b6da
    }
Packit e4b6da
        
Packit e4b6da
    $self->{rw}->print_ws(man_escape($self->{output_save}));
Packit e4b6da
Packit e4b6da
    # Store the refname; needed later to make the links.
Packit e4b6da
    push(@{$elem->parent->parent->ext->{refnames}}, 
Packit e4b6da
            $self->{output_save});
Packit e4b6da
    $self->{output_save} = undef;
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('text()', 'refname',
Packit e4b6da
    \&save_text_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('refpurpose<', 'refnameline-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{rw}->print_ws(' \- ');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Section headings
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule("SH<", sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->parent->ext->{lastchild} = '';
Packit e4b6da
    $templates->push_mode('section-mode');
Packit e4b6da
    $self->{output_save} = '';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule("SH>", sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{rw}->request('SH', 
Packit e4b6da
        man_normalize_ws(man_escape($self->{output_save})));
Packit e4b6da
    $self->{output_save} = undef;
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule("SS<", sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->parent->ext->{lastchild} = '';
Packit e4b6da
    $templates->push_mode('section-mode');
Packit e4b6da
    $self->{output_save} = '';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule("SS>", sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{rw}->request('SS', 
Packit e4b6da
        man_normalize_ws(man_escape($self->{output_save})));
Packit e4b6da
    $self->{output_save} = undef;
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'section-mode', 
Packit e4b6da
    \&save_text_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('b<', 'section-mode', sub {});
Packit e4b6da
$templates->add_rule('i<', 'section-mode', sub {});
Packit e4b6da
$templates->add_rule('tt<', 'section-mode', sub {});
Packit e4b6da
$templates->add_rule('*<', 'section-mode', \&illegal_element_handler);
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
    my $adstack = $self->{'adjust-stack'};
Packit e4b6da
    if($elem->attr('align') ne '') {
Packit e4b6da
        $self->{rw}->request('fi');
Packit e4b6da
        $self->{rw}->request('ad', $elem->attr('align'));
Packit e4b6da
        push(@$adstack, $elem->attr('align'));
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('para>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $adstack = $self->{'adjust-stack'};
Packit e4b6da
    if($elem->attr('align') ne '') {
Packit e4b6da
        pop(@$adstack);
Packit e4b6da
        $self->{rw}->request('ad', $adstack->[-1]);
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Indented paragraphs of various sorts
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('TP<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->parent->ext->{lastchild} = 'hanging';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPauto<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    
Packit e4b6da
    my $adstack = $self->{'adjust-stack'};
Packit e4b6da
    if($elem->attr('align') ne '') {
Packit e4b6da
        $self->{rw}->request('fi');
Packit e4b6da
        $self->{rw}->request('ad', $elem->attr('align'));
Packit e4b6da
        push(@$adstack, $elem->attr('align'));
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPauto>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $adstack = $self->{'adjust-stack'};
Packit e4b6da
    if($elem->attr('align') ne '') {
Packit e4b6da
        pop(@$adstack);
Packit e4b6da
        $self->{rw}->request('ad', $adstack->[-1]);
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPtag<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($elem->in('TP')) {
Packit e4b6da
        $self->{rw}->request('TP',
Packit e4b6da
            $elem->parent->attr('indent'));
Packit e4b6da
        $templates->push_mode('single-line-mode');
Packit e4b6da
    } elsif($elem->in('TPauto')) {
Packit e4b6da
    } else {
Packit e4b6da
        &illegal_element_handler;
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPtag>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    if($elem->in('TP')) {
Packit e4b6da
        $templates->pop_mode();
Packit e4b6da
        $self->{rw}->print_ws("\n");
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPitem<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($elem->in('TP')) {
Packit e4b6da
    } elsif($elem->in('TPauto')) {
Packit e4b6da
        # This is from Bernd Westphal's old patch 
Packit e4b6da
        # to docbook2man-sgmlspl.
Packit e4b6da
Packit e4b6da
        $self->{rw}->escape('kx');          # Save current horiz. position to x
Packit e4b6da
        $self->{rw}->request('if',          # if too far left, 
Packit e4b6da
            qw{ (\nx>(\n(.l/2)) .nr x (\n(.l/5) });
Packit e4b6da
                                            #    set x to 0.2 * line-length
Packit e4b6da
        $self->{rw}->request("'in",         #  modify indent, suppress newline
Packit e4b6da
            qw{ \n(.iu+\nxu });
Packit e4b6da
    } else {
Packit e4b6da
        &illegal_element_handler;
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPitem>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    if($elem->in('TP')) {
Packit e4b6da
    } elsif($elem->in('TPauto')) {
Packit e4b6da
        $self->{rw}->request("'in",         # restore indent
Packit e4b6da
            qw{ \n(.iu-\nxu });
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Verbatim sections (disable filling and adjusting)
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('verbatim<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    block_start($self, $elem);
Packit e4b6da
    $templates->push_mode('verbatim-mode');
Packit e4b6da
    $self->{rw}->request('nf');
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('verbatim>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{rw}->request('fi');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
Packit e4b6da
Packit e4b6da
 
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Plain old indent
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
$templates->add_rule('indent<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    $elem->parent->ext->{lastchild} = 'block';
Packit e4b6da
    #block_start($self, $elem);
Packit e4b6da
    #
Packit e4b6da
    my $indent = $elem->attr('indent');
Packit e4b6da
    $self->{rw}->request('RS', $indent);
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('indent>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{rw}->request('RE');
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->{rw}->request('sp', 
Packit e4b6da
        $elem->attr('length'));
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('br<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{rw}->request('br');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
##################################################
Packit e4b6da
#
Packit e4b6da
# Tables 
Packit e4b6da
#
Packit e4b6da
##################################################
Packit e4b6da
Packit e4b6da
=head1 Tables
Packit e4b6da
Packit e4b6da
db2x_manxml implements CALS tables directly,
Packit e4b6da
or rather, a XML description of tables that is really
Packit e4b6da
similar to how DocBook CALS tables work.
Packit e4b6da
Packit e4b6da
The differences are:  
Packit e4b6da
Packit e4b6da
=over 4
Packit e4b6da
=item *
Packit e4b6da
Some presentational attributes may not work,
Packit e4b6da
because they are not supported by tbl.
Packit e4b6da
Packit e4b6da
=item *
Packit e4b6da
Character-based alignment does not work.
Packit e4b6da
Packit e4b6da
=item *
Packit e4b6da
tfoot goes after the tbody, not before.
Packit e4b6da
This deviation mainly comes from the fact
Packit e4b6da
that db2x_manxml is stream-based, so content that
Packit e4b6da
goes at the end of the output has to appear later on
Packit e4b6da
the input.  (It could be worked around by buffering
Packit e4b6da
the tfoot data, but since tbl does not actually support
Packit e4b6da
table footers it is not worth the effort.)
Packit e4b6da
Packit e4b6da
=item *
Packit e4b6da
A table cell entry is either an entry or entry element.
Packit e4b6da
The latter uses tbl's T{ ... T} facility to include
Packit e4b6da
entire blocks of text, whereas the former contains only inline
Packit e4b6da
entries.  Theoretically all table cell entries can be considered
Packit e4b6da
as blocks, but for short inline entries using the entry element 
Packit e4b6da
makes the output look nicer.
Packit e4b6da
Packit e4b6da
Actually SGML DocBook (but not XML DocBook) makes this 
Packit e4b6da
inline/block distinction also in the DTD content model
Packit e4b6da
for entry.  For a reference, see "pernicious content" 
Packit e4b6da
in the TDG.
Packit e4b6da
Packit e4b6da
=item *
Packit e4b6da
entrytbl is not supported, obviously.
Packit e4b6da
Packit e4b6da
=back
Packit e4b6da
Packit e4b6da
I am happy to report though that
Packit e4b6da
both horizontal and vertical spans, and sparse cells[*]
Packit e4b6da
are supported.
Packit e4b6da
Packit e4b6da
[*] That is, specifying that an individual table cell should
Packit e4b6da
go into a specific column (previously specified by colspec).
Packit e4b6da
Packit e4b6da
=cut
Packit e4b6da
Packit e4b6da
$templates->add_rule('table<', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{rw}->request('TS');
Packit e4b6da
Packit e4b6da
    my $global_options;
Packit e4b6da
    if($elem->attr('frame') eq '' 
Packit e4b6da
        or $elem->attr('frame') eq 'none') 
Packit e4b6da
    {
Packit e4b6da
    } elsif($elem->attr('frame') eq 'all') {
Packit e4b6da
        $global_options .= 'allbox ';
Packit e4b6da
    } else { 
Packit e4b6da
        $templates->warn_location($elem, "only a frame of 'all' or 'none' is supported");
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if($elem->attr('pgwide')) {
Packit e4b6da
        $global_options .= 'expand';
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $self->{rw}->print_ws($global_options . ";\n")
Packit e4b6da
        if defined $global_options;
Packit e4b6da
    
Packit e4b6da
    $templates->push_mode('table-mode');
Packit e4b6da
    # Global options line
Packit e4b6da
});
Packit e4b6da
$templates->add_rule('table>', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{rw}->request('TE');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
=head2 Algorithm for rendering tables
Packit e4b6da
Packit e4b6da
For simplicity in this description I will concentrate on rendering a
Packit e4b6da
tbody.  Rendering thead and tfoot is similar.  I will also assume that
Packit e4b6da
you know how tbl works and its syntax.
Packit e4b6da
Packit e4b6da
First, when colspec or spanspec is encountered, the data on the column
Packit e4b6da
number, alignment, etc. is stored in the tgroup's node ext structure.
Packit e4b6da
colspecs and spanspecs that occur in thead and tfoot override the ones
Packit e4b6da
in tgroup, and tbody uses the colspecs and spanspecs that physically
Packit e4b6da
occur under tgroup, unchanged, as per the CALS specification.
Packit e4b6da
Packit e4b6da
The most critical information is the column number.  The running column
Packit e4b6da
number is always kept, and is used whenever a colspec does not specify a
Packit e4b6da
column number explicitly.  (Similarly for entry and entrytbl.)
Packit e4b6da
Packit e4b6da
tbl requires that the formatting information for I<all> the table rows
Packit e4b6da
to come before any of the actual data.  However, this information must
Packit e4b6da
be obtained as each row and cell is processed.  (This information mainly
Packit e4b6da
consists of the length of horizontal or vertical spans, if any, as well
Packit e4b6da
as text alignment, which may be customized differently for each cell.)
Packit e4b6da
Packit e4b6da
So all the rows have to be buffered as they are processed, and then, at
Packit e4b6da
the end of the table, the formatting information is output, then all the
Packit e4b6da
buffered rows.  A special buffering mode in RoffWriter is used to do
Packit e4b6da
this.
Packit e4b6da
Packit e4b6da
There is no problem with this buffering when processing thead or tfoot,
Packit e4b6da
since there are usually only one or two lines in those sections.  But
Packit e4b6da
the tbody may have thousands of lines.  Even if we don't exactly run out
Packit e4b6da
of memory, the output would not be very nice because we would have a
Packit e4b6da
thousand format-lines (the formatting information for one particular
Packit e4b6da
row) that are nearly identical.  
Packit e4b6da
Packit e4b6da
Therefore we add a refinement to our procedure: we watch the
Packit e4b6da
format-lines, and if they are the same for the last few rows we flush
Packit e4b6da
our buffer.  If we have a different format-line coming later in our
Packit e4b6da
input, we use the table continuation request (C<.T&>) to change it (in
Packit e4b6da
blocks). So our output looks like this:
Packit e4b6da
Packit e4b6da
 l l l l             # Format lines
Packit e4b6da
 l l l s
Packit e4b6da
 l l l l.            # All the lines from the third line on have this format
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 Data Data Data 
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 Data Data Data Data
Packit e4b6da
 .T&
Packit e4b6da
 l l l s
Packit e4b6da
 l l l l.
Packit e4b6da
 ...
Packit e4b6da
 Data
Packit e4b6da
 ...
Packit e4b6da
 .T&
Packit e4b6da
 l l l r
Packit e4b6da
 l l l l.
Packit e4b6da
 ...
Packit e4b6da
 Data
Packit e4b6da
 ...
Packit e4b6da
 
Packit e4b6da
Of course, theoretically we could use C<.T&> for each any every row, but
Packit e4b6da
this bloats the output.
Packit e4b6da
Packit e4b6da
As I have mentioned, the format-lines contain span information.
Packit e4b6da
Actually cell spans can be handled fairly easily with an
Packit e4b6da
iterative/imperative algorithm.  (As opposed to a functional/recursive
Packit e4b6da
one, which makes it a pain to implement in XSLT, and so we do it here
Packit e4b6da
instead.)
Packit e4b6da
Packit e4b6da
First, there is a default format-line, which is constructed with the
Packit e4b6da
help of the colspec information.  The format-line is represented as a
Packit e4b6da
flat array with each element corresponding to one column, in order from
Packit e4b6da
left-to-right (assume LTR natural languages here, since roff does not
Packit e4b6da
support other writing directions).  The list elements are simply strings
Packit e4b6da
like "l", "r", "c", etc.  i.e. the same sort of column specifiers as
Packit e4b6da
used in tbl.  To make a format-line in the output it is only needed to
Packit e4b6da
C<join> this list.
Packit e4b6da
Packit e4b6da
When a table entry comes along and specifies some horizontal span, the
Packit e4b6da
default format-line is taken as the initial template for the current
Packit e4b6da
line's format-line, and the columns in this format-line array that are
Packit e4b6da
affected by the span are assigned a different specifier (something like
Packit e4b6da
"s").  The newly-changed format-line is the one that is used for the
Packit e4b6da
current line.
Packit e4b6da
Packit e4b6da
Of course, when there is a horizontal span, care must be taken that
Packit e4b6da
we skip over the correct number of 'physical' cells.
Packit e4b6da
Packit e4b6da
For vertical spans, the format-line handling is the same, except with a
Packit e4b6da
different tbl column specifier ("^").  Vertical spanning does require
Packit e4b6da
that subsequent rows know about the vertically spanning cells and
Packit e4b6da
reserve space for them:  this is easily handled with another flat array
Packit e4b6da
in a similar fashion to the format-line array.  Each element of the
Packit e4b6da
current_vspans array would hold a non-negative integer indicating how
Packit e4b6da
many more rows to reserve for each column.  (Zero means there is no
Packit e4b6da
vertical span for that column, naturally.) Once each row is finished,
Packit e4b6da
each number of this list is decremented by one, unless it is zero, in
Packit e4b6da
which it stays at zero.  Whenever a table cell requests vertical span, the
Packit e4b6da
appropriate column of the current_vspans array is re-assigned to.
Packit e4b6da
Packit e4b6da
The current_vspans array is also consulted when advancing columns
Packit e4b6da
(as one row's table cells are processed) so that the current column
Packit e4b6da
is not advanced in the middle of some vertical span from a previous row.
Packit e4b6da
Packit e4b6da
=cut
Packit e4b6da
Packit e4b6da
$templates->add_rule('tgroup<', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
Packit e4b6da
    $elem->ext->{total_cols} = $elem->attr('cols');
Packit e4b6da
Packit e4b6da
    if($elem->attr('cols') !~ /^\d+$/
Packit e4b6da
        or $elem->attr('cols') < 1)
Packit e4b6da
    {
Packit e4b6da
        $templates->warn_location($elem, "fatal error: invalid number of columns for table");
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    $elem->ext->{column_data} = [];
Packit e4b6da
Packit e4b6da
    $elem->ext->{colnames} = {};
Packit e4b6da
    $elem->ext->{spannames} = {};
Packit e4b6da
Packit e4b6da
    $elem->ext->{colspec_current_colnum} = 0;
Packit e4b6da
Packit e4b6da
    $elem->ext->{align} = $elem->attr('align') || 'left';
Packit e4b6da
    $elem->ext->{rowsep} = 
Packit e4b6da
        ( (defined $elem->attr('rowsep'))?
Packit e4b6da
            $elem->attr('rowsep') :
Packit e4b6da
            $elem->parent->attr('rowsep') );
Packit e4b6da
    $elem->ext->{colsep} = 
Packit e4b6da
        ( (defined $elem->attr('colsep'))?
Packit e4b6da
            $elem->attr('colsep') :
Packit e4b6da
            $elem->parent->attr('colsep') );
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('tgroup>', 'table-mode', sub {});
Packit e4b6da
Packit e4b6da
$templates->add_rule('colspec<', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $t_elem = $elem->parent;
Packit e4b6da
    my $tgroup = $t_elem->parent;
Packit e4b6da
Packit e4b6da
    if($t_elem->name ne 'tgroup') {
Packit e4b6da
        $t_elem->ext->{total_cols} = $tgroup->ext->{total_cols};
Packit e4b6da
        $t_elem->ext->{column_data} = [];
Packit e4b6da
        $t_elem->ext->{colnames} = [];
Packit e4b6da
        $t_elem->ext->{spannames} = {};
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    my $colnum;
Packit e4b6da
    if($elem->attr('colnum')) {
Packit e4b6da
        $colnum = $elem->attr('colnum');
Packit e4b6da
        if($colnum !~ /^\d+$/ or $colnum < 1) {
Packit e4b6da
            $templates->warn_location($elem, "invalid column number --- ignoring\n");
Packit e4b6da
            $colnum = $t_elem->ext->{colspec_current_colnum} + 1;
Packit e4b6da
        } elsif($colnum <= $t_elem->ext->{colspec_current_colnum}) {
Packit e4b6da
            $templates->warn_location($elem, "column numbers of colspecs are not given in an increasing sequence\n");
Packit e4b6da
        }
Packit e4b6da
    } else {
Packit e4b6da
        $colnum = $t_elem->ext->{colspec_current_colnum} + 1;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if($colnum > $t_elem->ext->{total_cols}) {
Packit e4b6da
        $templates->warn_location($elem, "column number exceeds total number of columns --- ignoring this colspec\n");
Packit e4b6da
        return;
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    $t_elem->ext->{colspec_current_colnum} = $colnum;
Packit e4b6da
Packit e4b6da
    my $column_data_entry = 
Packit e4b6da
        { colwidth => $elem->attr('colwidth'),
Packit e4b6da
          colname => $elem->attr('colname'),
Packit e4b6da
          colnum => $colnum,
Packit e4b6da
          
Packit e4b6da
          rowsep => $elem->attr('rowsep'),
Packit e4b6da
          colsep => $elem->attr('colsep'),
Packit e4b6da
          align => $elem->attr('align') || $tgroup->ext->{align}
Packit e4b6da
        };
Packit e4b6da
    
Packit e4b6da
    $t_elem->ext->{column_data}->[$colnum-1] = $column_data_entry;
Packit e4b6da
Packit e4b6da
    $t_elem->ext->{colnames}->{$elem->attr('colname')}
Packit e4b6da
        = $column_data_entry
Packit e4b6da
            if $elem->attr('colname') ne '';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('spanspec<', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $t_elem = $elem->parent;
Packit e4b6da
Packit e4b6da
    $t_elem->ext->{spannames}->{$elem->attr('spanname')}
Packit e4b6da
        = {
Packit e4b6da
            start => $t_elem->ext->{colnames}->{$elem->attr('namest')},
Packit e4b6da
            end => $t_elem->ext->{colnames}->{$elem->attr('nameend')},
Packit e4b6da
Packit e4b6da
            rowsep => $elem->attr('rowsep'),
Packit e4b6da
            colsep => $elem->attr('colsep'),
Packit e4b6da
            align => $elem->attr('align') || 
Packit e4b6da
                $t_elem->ext->{colnames}->{$elem->attr('namest')}->{align}
Packit e4b6da
          };
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
sub tbl_build_format_line_helper
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem) = @_;
Packit e4b6da
    my $tgroup = $elem->parent;
Packit e4b6da
    
Packit e4b6da
    my @format_line = ();
Packit e4b6da
    
Packit e4b6da
    for(my $i = 0; $i < $elem->ext->{total_cols}; $i++)
Packit e4b6da
    {
Packit e4b6da
        my $cd = $elem->ext->{column_data}->[$i];
Packit e4b6da
Packit e4b6da
        my $f = tbl_align_to_tbl(
Packit e4b6da
                (defined $cd ? $cd->{align} : $tgroup->ext->{align}));
Packit e4b6da
        $f .= tbl_colwidth_to_tbl($cd->{colwidth})
Packit e4b6da
            if (defined $cd and defined $cd->{colwidth});
Packit e4b6da
        $f .= ' |'
Packit e4b6da
            if ((defined $cd and $cd->{colsep}) or
Packit e4b6da
                    $tgroup->ext->{colsep})
Packit e4b6da
                and ($i != $elem->ext->{total_cols}-1);
Packit e4b6da
Packit e4b6da
        push(@format_line, $f);
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    return \@format_line;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_colwidth_to_tbl
Packit e4b6da
{
Packit e4b6da
    my ($s) = @_;
Packit e4b6da
    # FIXME!
Packit e4b6da
    return '';
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_build_format_line
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem) = @_;
Packit e4b6da
Packit e4b6da
    if($elem->name eq 'tbody' 
Packit e4b6da
        or !defined $elem->ext->{column_data})
Packit e4b6da
    {
Packit e4b6da
        foreach my $x (qw(total_cols column_data colnames spannames)) {
Packit e4b6da
            $elem->ext->{$x} = $elem->parent->ext->{$x};
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $elem->ext->{default_format_line} = 
Packit e4b6da
        tbl_build_format_line_helper($self, $elem);
Packit e4b6da
    
Packit e4b6da
    my @vspans = (0) x $elem->ext->{total_cols};
Packit e4b6da
    $elem->ext->{current_vspans} = \@vspans;
Packit e4b6da
    
Packit e4b6da
    my @vspan_template_format = ('^') x $elem->ext->{total_cols};
Packit e4b6da
    $elem->ext->{vspan_template_format} = \@vspan_template_format;
Packit e4b6da
Packit e4b6da
    my @current_rowseps = ('^') x $elem->ext->{total_cols};
Packit e4b6da
    $elem->ext->{current_rowseps} = \@current_rowseps;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
sub t_elem_start_handler { my ($self, $elem, $templates) = @_;
Packit e4b6da
                           tbl_format_line_buffer_start($elem, $self->{rw}); }
Packit e4b6da
sub t_elem_end_handler   { my ($self, $elem, $templates) = @_;
Packit e4b6da
                           tbl_format_line_buffer_flush($elem, $self->{rw}); }
Packit e4b6da
Packit e4b6da
$templates->add_rule('thead<', 'table-mode', \&t_elem_start_handler);
Packit e4b6da
$templates->add_rule('thead>', 'table-mode', \&t_elem_end_handler);
Packit e4b6da
$templates->add_rule('tfoot<', 'table-mode', \&t_elem_start_handler);
Packit e4b6da
$templates->add_rule('tfoot>', 'table-mode', \&t_elem_end_handler);
Packit e4b6da
$templates->add_rule('tbody<', 'table-mode', \&t_elem_start_handler);
Packit e4b6da
$templates->add_rule('tbody>', 'table-mode', \&t_elem_end_handler);
Packit e4b6da
Packit e4b6da
sub tbl_advance_column
Packit e4b6da
{
Packit e4b6da
    my ($row, $rw, $new_colnum, $relative_advance) = @_;
Packit e4b6da
    
Packit e4b6da
    my $old_colnum = $row->ext->{current_colnum};
Packit e4b6da
    my $total_cols = $row->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
    $rw->print("\t" x ($new_colnum - $old_colnum));
Packit e4b6da
}
Packit e4b6da
    
Packit e4b6da
Packit e4b6da
$templates->add_rule('row<', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $t_elem = $elem->parent;
Packit e4b6da
    my $tgroup = $t_elem->parent;
Packit e4b6da
Packit e4b6da
    if(! $t_elem->ext->{num_rows}++) {
Packit e4b6da
        tbl_build_format_line($self, $t_elem);
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if(defined $tgroup->ext->{last_rowseps}) {
Packit e4b6da
        # tbl has this funny (seemingly undocumented)
Packit e4b6da
        # behavior where if a format line is all underscores
Packit e4b6da
        # then the corresponding (blank) data line _must_ be omitted.
Packit e4b6da
        $self->{rw}->print("\n")
Packit e4b6da
            if grep(/[^_]/, @{$tgroup->ext->{last_rowseps}});
Packit e4b6da
        
Packit e4b6da
        if($t_elem->name ne 'tbody') {
Packit e4b6da
            tbl_format_line_buffer_push($t_elem, 
Packit e4b6da
                $tgroup->ext->{last_rowseps});
Packit e4b6da
        } else {
Packit e4b6da
            tbl_format_line_buffer_push_ex($t_elem, $self->{rw}, 
Packit e4b6da
                $tgroup->ext->{last_rowseps});
Packit e4b6da
        }
Packit e4b6da
Packit e4b6da
        $tgroup->ext->{last_rowseps} = undef;
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    $elem->ext->{current_colnum} = 0;
Packit e4b6da
    tbl_advance_column($elem, $self->{rw}, 0, 1);
Packit e4b6da
    $elem->ext->{this_format_line} = 
Packit e4b6da
        list_copy($t_elem->ext->{default_format_line});
Packit e4b6da
Packit e4b6da
    tbl_copy_vspan_template_format(
Packit e4b6da
        $elem->ext->{this_format_line},
Packit e4b6da
        $t_elem->ext->{current_vspans},
Packit e4b6da
        $t_elem->ext->{vspan_template_format});
Packit e4b6da
Packit e4b6da
    for(my $i = 0; $i < @{$t_elem->ext->{current_vspans}}; $i++)
Packit e4b6da
    {
Packit e4b6da
        if($t_elem->ext->{current_vspans}->[$i] > 0) {
Packit e4b6da
        } elsif(defined $elem->attr('rowsep')) {
Packit e4b6da
            $t_elem->ext->{current_rowseps}->[$i] =
Packit e4b6da
                ( $elem->attr('rowsep') ? '_' : '^' );
Packit e4b6da
        } elsif(defined $t_elem->ext->{column_data}->[$i]) {
Packit e4b6da
            $t_elem->ext->{current_rowseps}->[$i] =
Packit e4b6da
                ( $t_elem->ext->{column_data}->[$i]->{rowsep} ? '_' : '^' );
Packit e4b6da
        } else {
Packit e4b6da
            $t_elem->ext->{current_rowseps}->[$i] =
Packit e4b6da
                ( $tgroup->ext->{rowsep} ? '_' : '^' );
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
sub tbl_copy_vspan_template_format
Packit e4b6da
{
Packit e4b6da
    my ($format_line, $vspans, $vspan_template_format) = @_;
Packit e4b6da
    for(my $i = 0; $i < @$vspans; $i++)
Packit e4b6da
    {
Packit e4b6da
        $format_line->[$i] = $vspan_template_format->[$i]
Packit e4b6da
            if $vspans->[$i] > 0;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    return $format_line;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('row>', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $t_elem = $elem->parent;
Packit e4b6da
    my $tgroup = $t_elem->parent;
Packit e4b6da
    
Packit e4b6da
    tbl_advance_column($elem, $self->{rw}, -1);
Packit e4b6da
    
Packit e4b6da
    $self->{rw}->print_ws("\n");
Packit e4b6da
Packit e4b6da
    if($t_elem->name ne 'tbody') {
Packit e4b6da
        tbl_format_line_buffer_push($t_elem, $elem->ext->{this_format_line});
Packit e4b6da
    } else {
Packit e4b6da
        tbl_format_line_buffer_push_ex($t_elem, $self->{rw}, 
Packit e4b6da
            $elem->ext->{this_format_line});
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    foreach my $c (@{$t_elem->ext->{current_vspans}}) {
Packit e4b6da
        $c-- if $c > 0;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    my @current_rowseps;
Packit e4b6da
    for(my $i = 0; $i < $t_elem->ext->{total_cols}; $i++) {
Packit e4b6da
        $current_rowseps[$i] = 
Packit e4b6da
            ($t_elem->ext->{current_vspans}->[$i] > 0 ?
Packit e4b6da
                '^' :
Packit e4b6da
                $t_elem->ext->{current_rowseps}->[$i]);
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    # We don't output the borders yet, because they are supposed
Packit e4b6da
    # to be separators and we do not want one on the last row.
Packit e4b6da
    # So output them on the next row.
Packit e4b6da
    $tgroup->ext->{last_rowseps} = \@current_rowseps
Packit e4b6da
        if grep { $_ eq '_' } @current_rowseps;
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
# Compare two lists (given as references),
Packit e4b6da
# checking they have the same number of elements
Packit e4b6da
# and each corresponding element is string-wise equal.
Packit e4b6da
#
Packit e4b6da
sub list_eq
Packit e4b6da
{
Packit e4b6da
    my ($a, $b) = @_;
Packit e4b6da
    return 0 if (!defined $a or !defined $b or @$a != @$b);
Packit e4b6da
Packit e4b6da
    for(my $i = 0; $i < @$a; $i++) {
Packit e4b6da
        return 0 if $a->[$i] ne $b->[$i];
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    return 1;
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
# Shallow-copy a list, given a reference,
Packit e4b6da
# into a new list, with a new reference.
Packit e4b6da
# 
Packit e4b6da
sub list_copy
Packit e4b6da
{
Packit e4b6da
    if(wantarray) {
Packit e4b6da
        return map { my @x = @$_; \@x } @_;
Packit e4b6da
    } else { 
Packit e4b6da
        my @x = @{$_[0]};
Packit e4b6da
        return \@x;
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_format_line_buffer_start
Packit e4b6da
{
Packit e4b6da
    my ($t_elem, $rw) = @_;
Packit e4b6da
    $t_elem->ext->{format_lines} = [];
Packit e4b6da
    $t_elem->ext->{same_format_lines} = 0;
Packit e4b6da
    $t_elem->ext->{current_format_line} = undef;
Packit e4b6da
Packit e4b6da
    $rw->buffer_on();
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_format_line_buffer_push
Packit e4b6da
{
Packit e4b6da
    my ($t_elem, $format_line) = @_;
Packit e4b6da
    my $format_lines = $t_elem->ext->{format_lines};
Packit e4b6da
Packit e4b6da
    if(scalar(@$format_lines) != 0 and
Packit e4b6da
        list_eq(
Packit e4b6da
            $format_lines->[-1],
Packit e4b6da
            $format_line))
Packit e4b6da
    {
Packit e4b6da
        $t_elem->ext->{same_format_lines}++;
Packit e4b6da
    } else {
Packit e4b6da
        $t_elem->ext->{same_format_lines} = 0;
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    push(@$format_lines, $format_line);
Packit e4b6da
    
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_format_line_buffer_flush
Packit e4b6da
{
Packit e4b6da
    my ($t_elem, $rw, $n) = @_;
Packit e4b6da
    my $format_lines = $t_elem->ext->{format_lines};
Packit e4b6da
    my $buffered_rows = $rw->buffer_off();
Packit e4b6da
Packit e4b6da
    $t_elem->ext->{same_format_lines} = 0;
Packit e4b6da
    $t_elem->ext->{current_format_line} = undef;
Packit e4b6da
    
Packit e4b6da
    return if @$format_lines == 0;
Packit e4b6da
    
Packit e4b6da
    $rw->request('T&')
Packit e4b6da
        if $t_elem->parent->parent->ext->{'T&'}++;
Packit e4b6da
Packit e4b6da
    $rw->print(
Packit e4b6da
        join("\n", 
Packit e4b6da
            map { join(' ', @$_) } @$format_lines[0 .. ($#$format_lines - $n)])
Packit e4b6da
        . ".\n");
Packit e4b6da
Packit e4b6da
    $rw->write($buffered_rows);
Packit e4b6da
    
Packit e4b6da
    $t_elem->ext->{format_lines} = [];
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_format_line_buffer_push_ex
Packit e4b6da
{
Packit e4b6da
    my ($t_elem, $rw, $format_line) = @_;
Packit e4b6da
Packit e4b6da
    if(defined $t_elem->ext->{current_format_line})
Packit e4b6da
    {
Packit e4b6da
        if(list_eq($t_elem->ext->{current_format_line}, $format_line)) {
Packit e4b6da
            $rw->write($rw->buffer_off());
Packit e4b6da
            $rw->buffer_on();
Packit e4b6da
        }
Packit e4b6da
        else {
Packit e4b6da
            $t_elem->ext->{format_lines} = [];
Packit e4b6da
            $t_elem->ext->{same_format_lines} = 0;
Packit e4b6da
            $t_elem->ext->{current_format_line} = undef;
Packit e4b6da
Packit e4b6da
            tbl_format_line_buffer_push($t_elem, $format_line);
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
    else {
Packit e4b6da
        tbl_format_line_buffer_push($t_elem, $format_line);
Packit e4b6da
        if($t_elem->ext->{same_format_lines} == 3) {
Packit e4b6da
            tbl_format_line_buffer_flush($t_elem, $rw, 
Packit e4b6da
                $t_elem->ext->{same_format_lines});
Packit e4b6da
            $t_elem->ext->{current_format_line} = $format_line;
Packit e4b6da
Packit e4b6da
            $rw->buffer_on();
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_align_to_tbl {
Packit e4b6da
    my ($align) = @_;   
Packit e4b6da
    if   ($align eq 'left')  { return 'l'; }
Packit e4b6da
    elsif($align eq 'right') { return 'r'; }
Packit e4b6da
    elsif($align eq 'center') { return 'c'; }
Packit e4b6da
    else { return 'l' }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
Packit e4b6da
sub tbl_entry_start_handler {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $row = $elem->parent;
Packit e4b6da
    my $t_elem = $elem->parent->parent;
Packit e4b6da
    my $tgroup = $t_elem->parent;
Packit e4b6da
Packit e4b6da
    my $start_col; my $end_col;
Packit e4b6da
    my $align; my $colsep;
Packit e4b6da
Packit e4b6da
    if($elem->attr('colname')) {
Packit e4b6da
        $start_col = $t_elem->{colnames}->{$elem->attr('colname')};
Packit e4b6da
    } elsif($elem->attr('spanname')) {
Packit e4b6da
        my $span = $t_elem->{spannames}->{$elem->attr('spanname')};
Packit e4b6da
        $start_col = $span->{start};
Packit e4b6da
        $end_col = $span->{end};
Packit e4b6da
        $align = $span->{align};
Packit e4b6da
        $colsep = $span->{colsep};
Packit e4b6da
    } elsif($elem->attr('namest')) {
Packit e4b6da
        $start_col = $t_elem->{colnames}->{$elem->attr('namest')};
Packit e4b6da
        $end_col = $t_elem->{colnames}->{$elem->attr('nameend')};
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    $align = $elem->attr('align')
Packit e4b6da
        if defined $elem->attr('align');
Packit e4b6da
    $colsep = $elem->attr('colsep')
Packit e4b6da
        if defined $elem->attr('colsep');
Packit e4b6da
Packit e4b6da
Packit e4b6da
    if(defined $start_col) {
Packit e4b6da
        tbl_advance_column($row, $self->{rw}, $start_col->{colnum});
Packit e4b6da
    }
Packit e4b6da
    
Packit e4b6da
    if(defined $elem->attr('rowsep')) {
Packit e4b6da
        $t_elem->ext->{current_rowseps}->[$row->ext->{current_colnum} - 1] =
Packit e4b6da
            ( $elem->attr('rowsep') ? '_' : '^' );
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if(defined $align) {
Packit e4b6da
        my $t = tbl_align_to_tbl($align);
Packit e4b6da
        $row->ext->{this_format_line}->[$row->ext->{current_colnum} - 1]
Packit e4b6da
            =~ s/^[lrc]/$t/;
Packit e4b6da
    }
Packit e4b6da
    if(defined $colsep) {
Packit e4b6da
        my $t = ($colsep and
Packit e4b6da
                    ($row->ext->{current_colnum} != 
Packit e4b6da
                        $tgroup->ext->{total_cols})) ? ' |' : '';
Packit e4b6da
        $row->ext->{this_format_line}->[$row->ext->{current_colnum} - 1]
Packit e4b6da
            =~ s/( \|)?$/$t/;
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    my $relative_advance = 1;
Packit e4b6da
    if(defined $end_col) {
Packit e4b6da
        for(my $i = $start_col->{colnum}; $i < $end_col->{colnum}; $i++) {
Packit e4b6da
            $row->ext->{this_format_line}->[$i] = 's';
Packit e4b6da
        }
Packit e4b6da
        $relative_advance = $end_col->{colnum} - $start_col->{colnum} + 1;
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
            $templates->warn_location($elem, "invalid morerows value --- ignoring\n");
Packit e4b6da
        } else {
Packit e4b6da
            
Packit e4b6da
            for(my $i = 0; $i < $relative_advance; $i++) {
Packit e4b6da
                my $c = $row->ext->{current_colnum} + $i;
Packit e4b6da
                $t_elem->ext->{current_vspans}->[$c - 1]
Packit e4b6da
                        = $elem->attr('morerows') + 1;
Packit e4b6da
                $t_elem->ext->{vspan_template_format}->[$c - 1]
Packit e4b6da
                        = '^';
Packit e4b6da
                $t_elem->ext->{vspan_template_format}->[$c - 1]
Packit e4b6da
                        .= ' |'
Packit e4b6da
                    if $row->ext->{this_format_line}->[$row->ext->{current_colnum} - 1] =~ /\|$/;
Packit e4b6da
            }
Packit e4b6da
        }
Packit e4b6da
    }
Packit e4b6da
Packit e4b6da
    if($elem->name eq 'entry') {
Packit e4b6da
        $self->{rw}->print("T{\n");
Packit e4b6da
        $templates->push_mode('');
Packit e4b6da
    } else {
Packit e4b6da
        $self->{rw}->print("\\&";;
Packit e4b6da
        $templates->push_mode('single-line-mode');
Packit e4b6da
    }
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub tbl_entry_end_handler
Packit e4b6da
{
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $row = $elem->parent;
Packit e4b6da
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    if($elem->name eq 'entry') {
Packit e4b6da
        $self->{rw}->print_ws("\nT}");
Packit e4b6da
    } 
Packit e4b6da
Packit e4b6da
    tbl_advance_column($row, $self->{rw}, 0, 
Packit e4b6da
        $elem->ext->{relative_advance});
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
$templates->add_rule('simentry<', 'table-mode', \&tbl_entry_start_handler);
Packit e4b6da
$templates->add_rule('simentry>', 'table-mode', \&tbl_entry_end_handler);
Packit e4b6da
$templates->add_rule('entry<', 'table-mode', \&tbl_entry_start_handler);
Packit e4b6da
$templates->add_rule('entry>', 'table-mode', \&tbl_entry_end_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', 'table-mode', \&illegal_element_handler);
Packit e4b6da
$templates->add_rule('text()', 'table-mode', \&illegal_text_handler);
Packit e4b6da
Packit e4b6da
#
Packit e4b6da
# Minimal entrytbl support (from Craig Ruff)
Packit e4b6da
#
Packit e4b6da
$templates->add_rule('entrytbl<', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    
Packit e4b6da
    $elem->ext->{relative_advance} = 1;
Packit e4b6da
    if($elem->name eq 'entrytbl') {
Packit e4b6da
        $self->{rw}->print("T{\n");
Packit e4b6da
	$templates->push_mode('entrytbl-mode');
Packit e4b6da
    }
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('entrytbl>', 'table-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    my $row = $elem->parent;
Packit e4b6da
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    if($elem->name eq 'entrytbl') {
Packit e4b6da
        $self->{rw}->print_ws("\nT}");
Packit e4b6da
    } 
Packit e4b6da
Packit e4b6da
    tbl_advance_column($row, $self->{rw}, 0, 
Packit e4b6da
        $elem->ext->{relative_advance});
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TP<', 'entrytbl-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $elem->parent->ext->{lastchild} = 'hanging';
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TP>', 'entrytbl-mode', sub {});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPtag<', 'entrytbl-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $self->{rw}->request('TP', $elem->parent->attr('indent'));
Packit e4b6da
    $templates->push_mode('single-line-mode');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPtag>', 'entrytbl-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
    $self->{rw}->print_ws("\n");
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPitem<', 'entrytbl-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->push_mode('');
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('TPitem>', 'entrytbl-mode', sub {
Packit e4b6da
    my ($self, $elem, $templates) = @_;
Packit e4b6da
    $templates->pop_mode();
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', 'entrytbl-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
Packit e4b6da
    mixed_inline_start($self, $node);
Packit e4b6da
    $self->{rw}->print_ws(
Packit e4b6da
        disambiguate_hyphen_minus(man_escape($node->{Data}), $node));
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
sub save_text_handler
Packit e4b6da
{
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    $self->{output_save} .= $node->{Data};
Packit e4b6da
}
Packit e4b6da
Packit e4b6da
sub single_line_text_handler {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    my $s = $node->{Data};
Packit e4b6da
Packit e4b6da
    $s =~ tr/\n/ /;
Packit e4b6da
    $s = disambiguate_hyphen_minus(man_escape($s), $node);
Packit e4b6da
    
Packit e4b6da
    mixed_inline_start($self, $node);
Packit e4b6da
Packit e4b6da
    $self->{rw}->print_ws($s);
Packit e4b6da
}
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
$templates->add_rule('*<', 'single-line-mode', \&illegal_element_handler);
Packit e4b6da
$templates->add_rule('text()', 'single-line-mode', 
Packit e4b6da
    \&single_line_text_handler);
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', 'verbatim-mode', \&illegal_element_handler);
Packit e4b6da
$templates->add_rule('text()', 'verbatim-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    $self->{rw}->print(
Packit e4b6da
        disambiguate_hyphen_minus(man_escape($node->{Data})));
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('text()', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
Packit e4b6da
    mixed_inline_start($self, $node);
Packit e4b6da
    
Packit e4b6da
    my $text = man_escape($node->{Data});
Packit e4b6da
    $text = disambiguate_hyphen_minus($text, $node);
Packit e4b6da
    $text = disable_hyphenation($text, $node);
Packit e4b6da
    
Packit e4b6da
    $self->{rw}->print_ws($text, $node);
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
    $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
    $self->{rw}->comment($elem->ext->{content});
Packit e4b6da
});
Packit e4b6da
Packit e4b6da
$templates->add_rule('*<', 'comment-mode', \&illegal_element_handler);
Packit e4b6da
$templates->add_rule('text()', 'comment-mode', sub {
Packit e4b6da
    my ($self, $node, $templates) = @_;
Packit e4b6da
    $node->parent->ext->{content} .= $node->{Data};
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 'man') {
Packit e4b6da
        my $data = $node->{Data};
Packit e4b6da
        $data =~ s/\
/\n/g;
Packit e4b6da
        $data =~ s/\
/\n/g;
Packit e4b6da
        $self->{rw}->print_ws($data);
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\n");
Packit e4b6da
};
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(DocumentHandler => $manxml::templates);
Packit e4b6da
Packit e4b6da
foreach my $file (@ARGV)
Packit e4b6da
{
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