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( "Test" ); =head1 DESCRIPTION This package implements methods that can handle the content of C 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 link to their parents' version using C. 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 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 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( '' . '' . ensure_utf8( $docbook_str ) . '' ); } 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 =cut