package Lire::ReportParser::LaTeXDocBookFormatter;
use strict;
use base qw/ Exporter Lire::DocBookParser /;
use Lire::Utils qw/deep_copy/;
use Lire::I18N qw/ensure_utf8/;
our @EXPORT_OK = qw/ dbk2latex /;
=pod
=head1 NAME
Lire::ReportParser::LaTeXDocBookFormatter - Lire::DocBookParser subclass
which formats description to LaTeX.
=head1 SYNOPSIS
To convert a DocBook decription to LaTeX:
use Lire::ReportParser::LaTeXDocBookFormatter qw/dbk2latex/;
my $LaTeX = dbk2latex( "<para>Test</para>" );
=head1 DESCRIPTION
This package implements methods that can handle the content of
C<description> elements and it can be used by a subclass of
Lire::ReportParser. Client only have to inherit from this module so
that a handle_description() method is available to process the text
formatted DocBook description.
This module also provide a convenient dbk2txt() function which can be
used to format a string containing DocBook elements into an ASCII
equivalent.
=head1 USING Lire::ReportParser::LaTeXDocBookFormatter
Lire::ReportParser processors that would like to work with text
version of the description should inherit from
Lire::ReportParser::LaTeXDocBookFormatter in addition to
Lire::ReportParser. If they override the description_start(),
description_end()methods, they B<must> link to their parents' version
using C<SUPER::>.
Additionnally, they should merge the value elements_spec() in their
elements_spec() implementation.
The Lire::ReportParser::LaTeXDocBookFormatter should be listed before
Lire::ReportParser in the @ISA. The LaTeXDocBookFormatter doesn't inherit
directly from Lire::ReportParser so that it can be used in multiple
inheritance scenario.
=cut
sub new {
my $self = shift->SUPER::new( @_ );
return $self;
}
=pod
=head2 dbk_start_processing()
Initializes the parser's structure for formatting DocBook XML as ASCII.
This is used from the description_start implementation.
=cut
sub dbk_start_processing {
my $self = $_[0];
$self->{'dbk_process'} = 1;
$self->init_stack( 'latex' );
$self->stack_push( 'latex', Lire::LaTeX::Node->new() );
return;
}
=pod
=head2 dbk_end_processing()
Cleans the parser structure. This is used from the description_end()
implementation.
=cut
sub dbk_end_processing {
my $self = $_[0];
delete $self->{'dbk_process'};
die "stack 'latex' should be empty"
unless $self->is_stack_empty( 'latex' );
return;
}
sub namespaces {
my $self = $_[0];
return { "http://www.logreport.org/LRML/" => 'lire' };
}
sub elements_spec {
my $self = $_[0];
my $spec = deep_copy( $self->Lire::DocBookParser::elements_spec() );
foreach my $element ( keys %$spec ) {
$spec->{$element}{'start'} = 'latex_start';
$spec->{$element}{'end'} = 'latex_end';
$spec->{$element}{'char'} = 'latex_char'
if exists $spec->{$element}{'char'};
}
$spec->{'lire:description'} = [ @Lire::DocBookParser::top_levels ];
return $spec;
}
sub description_start {
$_[0]->dbk_start_processing();
return;
}
sub description_end {
my $self = $_[0];
return unless $self->{'dbk_process'};
my $root = $self->stack_pop( 'latex' );
$self->handle_description( $root->as_string() );
$self->dbk_end_processing();
return;
}
=pod
=head2 handle_description( $description )
This method is invoked after the closing tag of the C<description>
element is encountered. The $description contains the description
converter to LaTeX.
=cut
sub handle_description {
$_[0]{'saved_latex'} = $_[1];
return;
}
sub parse_end {
return $_[0]{'saved_latex'};
}
my %dbk2latex = ( 'para' => 'Para',
'emphasis' => 'Emph',
'foreignphrase' => 'Emph',
'ulink' => 'Href',
'email' => 'Email',
'superscript' => 'Superscript',
'subscript' => 'Subscript',
'optional' => 'Slanted',
'parameter' => 'Slanted',
'varname' => 'Slanted',
'command' => 'TextTT',
'constant' => 'TextTT',
'computeroutput' => 'TextTT',
'filename' => 'TextTT',
'userinput' => 'TextTT',
'quote' => 'Quote',
'itemizedlist' => 'List',
'orderedlist' => 'List',
'variablelist' => 'List',
'listitem' => 'Item',
'varlistentry' => 'NamedItem',
'term' => 'Term',
'title' => 'Title',
'warning' => 'Admonition',
'caution' => 'Admonition',
'important' => 'Admonition',
'note' => 'Admonition',
'tip' => 'Admonition',
);
sub latex_start {
my ( $self, $name, $attr ) = @_;
my $class = "Lire::LaTeX::" . ( $dbk2latex{$name} || 'Node' );
my $node = $class->new( $name, $attr );
$self->stack_peek( 'latex' )->add_child( $node );
$self->stack_push( 'latex', $node );
return;
}
sub latex_end {
my ( $self, $name ) = @_;
$self->stack_pop( 'latex' );
return;
}
sub latex_char {
my ( $self, $text ) = @_;
$self->stack_peek( 'latex' )->add_child( Lire::LaTeX::Text->new( $text ) );
return;
}
=pod
=head1 PROCESSING LaTeX DESCRIPTION
The generate LaTeX is encoded in UTF-8, so Omega must be used to
process it. Additionnally, the result of calling
dbk_latex_environment() should be added to the document preambule. It
adds some environment and command definitions used by the formatter.
=head1 FORMATTING DocBook STRINGS
If you have DocBook content in a string, like you can obtain from some
of the Report Specifications object, you can convert it to LaTeX by
using the dbx2latex() function.
=cut
sub dbk_latex_environment {
return <<'EOF';
EOF
}
=pod
=head2 dbk2txt( $docbook_str, [$columns] )
Returns a plain text version of the DocBook XML fragment $docbook_str. The
C<columns> parameter sets the number of columns in which the DocBook text
should be formatted.
This method will die() in case of error.
=cut
sub dbk2latex {
my $docbook_str = $_[0];
my $parser =
new Lire::ReportParser::LaTeXDocBookFormatter();
return $parser->parse( '<?xml version="1.0" encoding="utf-8"?>'
. '<lire:description xmlns:lire='
. '"http://www.logreport.org/LRML/">'
. ensure_utf8( $docbook_str )
. '</lire:description>' );
}
package Lire::LaTeX::Node;
sub new {
return bless { 'children' => [] }, shift;
}
sub add_child {
my ( $self, $child ) = @_;
push @{$self->{'children'}}, $child;
return;
}
sub as_string {
my $self = $_[0];
my $str = '';
foreach my $child ( @{$self->{'children'}} ) {
$str .= $child->as_string();
}
return $str;
}
package Lire::LaTeX::Displayed;
use base qw/Lire::LaTeX::Node/;
package Lire::LaTeX::Inlined;
use base qw/Lire::LaTeX::Node/;
package Lire::LaTeX::Para;
use base qw/Lire::LaTeX::Displayed/;
use Text::Wrap;
sub as_string {
my $self = $_[0];
local $Text::Wrap::columns = 72;
my $str = '';
my $run = '';
foreach my $child ( @{$self->{'children'}} ) {
if ( $child->isa( 'Lire::LaTeX::Displayed' ) ) {
$str .= $self->wrap_run( $run, length $str );
$str .= $child->as_string();
$run = '';
} else {
$run .= $child->as_string();
}
}
$str .= $self->wrap_run( $run, length $str );
return $str;
}
sub wrap_run {
my ( $self, $run, $noindent ) = @_;
return '' unless length $run;
$run =~ s/\s+/ /g;
return ( $noindent ? "\\noindent\n" : '' ) . wrap( '', '', $run ) . "\n\n";
}
package Lire::LaTeX::Emph;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\emph{' . shift->SUPER::as_string() . '}';
}
package Lire::LaTeX::Slanted;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\textsl{' . shift->SUPER::as_string() . '}';
}
package Lire::LaTeX::TextTT;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\texttt{' . shift->SUPER::as_string() . '}';
}
package Lire::LaTeX::Subscript;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\raisebox{-1ex}{' . shift->SUPER::as_string() . '}';
}
package Lire::LaTeX::Superscript;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\raisebox{1ex}{' . shift->SUPER::as_string() . '}';
}
package Lire::LaTeX::Quote;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '``' . shift->SUPER::as_string() . "''";
}
package Lire::LaTeX::Href;
use base qw/Lire::LaTeX::Inlined/;
sub new {
my ( $class, $name, $attr ) = @_;
my $self = $class->SUPER::new( $attr );
$self->{'href'} = $attr->{'url'};
return $self;
}
sub as_string {
my $self = $_[0];
return '\href{' . $self->{'href'} . '}{' . $self->SUPER::as_string() . '}';
}
package Lire::LaTeX::Email;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
my $self = $_[0];
my $email = $self->SUPER::as_string();
return "\\href{mailto:$email}{$email}";
}
package Lire::LaTeX::Text;
use base qw/Lire::LaTeX::Inlined/;
use Lire::Utils qw/latex_encode/;
sub new {
my ( $pkg, $content ) = @_;
return bless \$content, $pkg;
}
sub as_string {
my $self = $_[0];
return latex_encode( $$self );
}
package Lire::LaTeX::Admonition;
use base qw/Lire::LaTeX::Displayed/;
use Locale::TextDomain 'lire';
my %admonition_titles = ( 'note' => N__( 'Note:' ),
'warning' => N__( 'Warning:' ),
'tip' => N__( 'Tip:' ),
'caution' => N__( 'Caution:' ),
'important' => N__( 'Important:' ),
);
sub new {
my ( $class, $name, $attr ) = @_;
my $self = $class->SUPER::new();
$self->{'title'} = Lire::LaTeX::Title->new();
$self->{'title'}->add_child( Lire::LaTeX::Text->new( $__{$admonition_titles{$name}} ) );
return $self;
}
sub add_child {
my ( $self, $child ) = @_;
if ( $child->isa( 'Lire::LaTeX::Title' ) ) {
$self->{'title'} = $child;
} else {
$self->SUPER::add_child( $child );
}
}
sub as_string {
my $self = $_[0];
return join( "", "\\begin{quote}\n",
$self->{'title'}->as_string(), "\n",
$self->SUPER::as_string(),
"\\end{quote}\n\n" );
}
package Lire::LaTeX::List;
use base qw/Lire::LaTeX::Displayed/;
my %dbk2latex_list = ( 'itemizedlist' => 'itemize',
'orderedlist' => 'enumerate',
'variablelist' => 'description',
);
sub new {
my ( $class, $name, $attr ) = @_;
my $self = bless { 'title' => undef,
'preamble' => [],
'type' => $dbk2latex_list{$name},
'items' => [] }, $class;
return $self;
}
sub add_child {
my ( $self, $child ) = @_;
if ( $child->isa( 'Lire::LaTeX::Title' ) ) {
$self->{'title'} = $child;
} elsif ( $child->isa( 'Lire::LaTeX::Item' ) ) {
push @{$self->{'items'}}, $child;
} else {
push @{$self->{'preamble'}}, $child;
}
}
sub as_string {
my $self = $_[0];
my $str = '';
if ( $self->{'title'} ) {
$str .= $self->{'title'}->as_string() . "\n\n\\nopagebreak[3]\n\n";
}
if ( @{$self->{'preamble'}} ) {
$str .= join( "", ( map { $_->as_string() } @{$self->{'preamble'}} ),
"\\nopagebreak[2]\n\n" );
}
$str .= join ( "", "\\begin{$self->{'type'}}\n",
( map { $_->as_string() } @{$self->{'items'}} ),
"\\end{$self->{'type'}}\n\n" );
return $str;
}
package Lire::LaTeX::Title;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\large{\textbf{' . shift->SUPER::as_string() . '}}';
}
package Lire::LaTeX::Item;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\item ' . shift->SUPER::as_string();
}
package Lire::LaTeX::NamedItem;
use base qw/Lire::LaTeX::Item/;
sub new {
return bless { 'term' => undef,
'def' => undef }, shift;
}
sub add_child {
my ( $self, $child ) = @_;
if ( $child->isa( 'Lire::LaTeX::Term' ) ) {
$self->{'term'} = $child;
} else {
$self->{'def'} = $child;
}
}
sub as_string {
my $self = $_[0];
return $self->{'term'}->as_string() . ' '
. $self->{'def'}->Lire::LaTeX::Node::as_string();
}
package Lire::LaTeX::Term;
use base qw/Lire::LaTeX::Inlined/;
sub as_string {
return '\item[' . shift->SUPER::as_string() . ']';
}
1;
__END__
=head1 SEE ALSO
Lire::ReportParser(3pm)
=head1 VERSION
$Id: LaTeXDocBookFormatter.pm,v 1.4 2006/07/23 13:16:31 vanbaal Exp $
=head1 COPYRIGHT
Copyright (C) 2004 Stichting LogReport Foundation LogReport@LogReport.org
This file is part of Lire.
Lire is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program (see COPYING); if not, check with
http://www.gnu.org/copyleft/gpl.html.
=head1 AUTHOR
Francis J. Lacoste <flacoste@logreport.org>
=cut
syntax highlighted by Code2HTML, v. 0.9.1