package Lire::ReportParser::LaTeXWriter;
use strict;
use Lire::Utils qw/check_param check_object_param latex_encode file_content/;
use Lire::I18N qw/ set_fh_encoding/;
use Lire::ReportParser::LaTeXDocBookFormatter qw/dbk2latex/;
use Lire::DlfSchema;
use File::Basename qw/dirname basename/;
use POSIX qw/strftime/;
use Locale::TextDomain 'lire';
use Lire::Config;
=pod
=head1 NAME
Lire::ReportParser::LaTeXWriter - Lire::ReportParser processor that formats the report in LaTeX
=head1 SYNOPSIS
use Lire::ReportParser::LaTeXWriter;
use Lire::ReportParser::ReportBuilder;
my $writer = new Lire::ReportParser::LaTeXWriter(),
);
my $parser = new Lire::ReportParser::ReportBuilder();
my $report = $parser->parsefile( 'report.xml' );
$parser->write_report( $report, 'report.pdf', 'pdf',
'preamble' => 'myfont.tex' );
=head1 DESCRIPTION
This is a Lire::ReportParser processor which will format the XML
report into PDF, PS or DVI format using LaTeX as intermediary format.
=head1 METHODS
=head2 new()
Returns an new LaTeXWriter.
=cut
sub new {
return bless {}, shift;
}
=pod
=head2 write_report( $report, $outputfile, $format, [ $preamble ] )
This will write the report Lire::Report in $outputfile in $format
output format.
=over
=item outputfile
This manadatory parameter specifies the file where the file will be
written.
=item format
This mandatory parameter may be one of 'pdf', 'ps', 'dvi' or 'latex'.
=item preamble
This optional parameter can be used to specify a file that will be
included in the LaTeX preamble. It can be use to change the fonts for
example.
=cut
sub write_report {
my ( $self, $report, $outputfile, $format, $preamble ) = @_;
check_object_param( $report, 'report', 'Lire::Report' );
check_param( $outputfile, 'outputfile' );
# \s is for broken cperl-mode in Emacs, *sigh*
check_param( $format, 'format', qr/^(dvi|ps|pdf|latex)$/,
'format should be one of dvi, ps, pdf or latex' );
check_param( $preamble, 'preamble', sub { -r $preamble },
"preamble file isn't readable" )
if $preamble;
$self->{'_chart_files'} = [];
$self->{ '_format'} = $format;
$self->{'_outputfile'} = $outputfile;
$self->{'_outputdir'} = dirname( $outputfile );
$self->{'_outputbase'} = $outputfile;
$self->{'_outputbase'} =~ s/\.\w+?$//;
$self->{'_preamble'} = $preamble;
open $self->{'_fh'}, "> $self->{'_outputbase'}.tex"
or die "can't write to $self->{'_outputbase'}.tex\n";
set_fh_encoding( $self->{'_fh'}, 'utf-8' );
$self->write_header();
$self->write_titlepage( $report );
foreach my $section ( $report->sections() ) {
$self->write_section( $section );
}
$self->write_appendix( $report );
print {$self->{'_fh'}} "\\end{document}\n";
$self->process_latex();
return;
}
sub write_header {
my $self = $_[0];
my $hyperref = $self->{'_format'} eq 'pdf' ? 'ps2pdf' : 'dvips';
my $graphics = $self->{'_format'} eq 'pdf' ? 'dvipdf' : 'dvips';
my $unicode = Lire::Config->get( 'unicode.tex' );
my $fh = $self->{'_fh'};
print $fh <<'EOF';
\ocp\MyTexUTF=inutf8
\InputTranslation currentfile \MyTexUTF
\documentclass{report}
\addtolength{\textwidth}{2\oddsidemargin}
\addtolength{\textheight}{2\topmargin}
\setlength{\oddsidemargin}{0cm}
\setlength{\topmargin}{0cm}
EOF
print $fh '\input ', $unicode, "\n", <<EOF;
\\usepackage[$graphics]{graphics}
\\usepackage{longtable}
\\usepackage[$hyperref,bookmarks,bookmarksopen,bookmarksnumbered]{hyperref}
EOF
print $fh '\input ', $self->{'_preamble'}, "\n"
if $self->{'_preamble'};
print $fh "\n", '\begin{document}', "\n";
return;
}
sub write_titlepage {
my ( $self, $report ) = @_;
my $fh = $self->{'_fh'};
if ( $report->title() ) {
print $fh '\title{', latex_encode( $report->title() ) ,"}\n"
} else {
print $fh '\title{}', "\n";
}
print $fh '\author{}', "\n";
my $fmt = '%Y-%m-%d %H:%M';
my $start = strftime( $fmt, localtime( $report->timespan_start() ) );
my $end = strftime( $fmt, localtime( $report->timespan_end() ) );
my $date = strftime( $fmt, localtime( $report->date() ) );
print $fh '\date{', __x( "Report for {start} -- {end}",
'start' => $start, 'end' => $end ), "\\\\\n",
__x( 'Report generated {date}', 'date' => $date ), "}\n",
'\maketitle', "\n", '\tableofcontents', "\n\n";
return;
}
sub write_section {
my ( $self, $section ) = @_;
my $fh = $self->{'_fh'};
print $fh '\chapter{', latex_encode( $section->title() ), "}\n\n";
print $fh dbk2latex( $section->description() )
if $section->description();
foreach my $subreport ( $section->subreports() ) {
if ( $subreport->is_missing() ) {
$self->write_missing_subreport( $subreport );
} else {
$self->write_subreport( $subreport );
}
}
return;
}
sub write_subreport {
my ( $self, $subreport ) = @_;
my $fh = $self->{'_fh'};
print $fh '\section{', latex_encode( $subreport->title() ), "}\n\n";
print $fh dbk2latex( $subreport->description() )
if $subreport->description();
foreach my $cfg ( @{$subreport->chart_configs()} ) {
$self->write_chart( $subreport, $cfg );
}
$self->write_table_header( $subreport );
$self->write_table_footer( $subreport );
$self->write_table_entries( $subreport );
return;
}
sub write_missing_subreport {
my ( $self, $subreport ) = @_;
my $fh = $self->{'_fh'};
print $fh '\section{', latex_encode( $subreport->title() ), "}\n\n";
print $fh '\emph{', __x( 'This report is missing: {reason}',
'reason' => latex_encode( $subreport->missing_reason() ) ), "}\n\n";
return;
}
sub write_chart {
my ( $self, $subreport, $chart_config ) = @_;
my $fh = $self->{'_fh'};
my $type = $chart_config->type();
my $font = Lire::Config->get( 'lr_chart_font' );
$font ||= '/Helvetica';
if ( substr( $font, 0, 1) ne '/' ) {
warn "'lr_chart_font' contains an invalid PostScript font ('$font'), using '/Helvetica'";
$font = '/Helvetica';
}
my $file = eval { $type->write_chart( $chart_config, $subreport,
'outputdir' => $self->{'_outputdir'},
'format' => 'eps',
'font' => $font ) };
if ( $@ ) {
print $fh '\emph{', latex_encode( __x( 'An error occured while generating the chart: {error}', 'error' => $@ ) ), "}\n\n";
} elsif ( $file ) {
my $base = basename( $file );
push @{$self->{'_chart_files'}}, $file;
print $fh "\\begin{center}\n\\includegraphics{$base}\n\\end{center}\n\n";
}
return;
}
sub write_table_header {
my ( $self, $subreport ) = @_;
my $fh = $self->{'_fh'};
my $info = $subreport->table_info();
my $col_spec = join( '', '|', ( map { $_->class() eq 'categorical' ? 'l' : 'r' } $info->column_infos() ), '|' );
print $fh "\\begin{longtable}{$col_spec}\n\\hline\n";
my @col_infos = $info->column_infos();
foreach my $row ( @{ $info->header_rows() } ) {
my @data = ();
foreach my $cell ( @$row ) {
next unless defined $cell;
my $i = $cell->col_start();
my $schema = $subreport->field_schema( $cell->name() );
if ( $schema ) {
$data[$i] = '\bfseries \hyperlink{' . $schema . ':'
. $cell->name() . '}{' . latex_encode( $cell->label() )
. '}';
} else {
$data[$i] = '\bfseries ' . latex_encode( $cell->label() );
}
}
$self->write_table_row( \@col_infos, \@data );
}
print $fh "\\hline\n\\endhead\n\n";
return;
}
sub write_table_entries {
my ( $self, $subreport ) = @_;
my $fh = $self->{'_fh'};
unless ( $subreport->entries() ) {
print $fh '\multicolumn{',
$subreport->table_info()->ncols(), '}{|l|}{\emph{',
__( 'There is no entries in this table.' ),
"}}\\\\\n\\end{longtable}\n\n";
return;
}
my @col_infos = $subreport->table_info()->column_infos();
foreach my $row ( @{$subreport->getrows()} ) {
my @data = map { defined $_ ? latex_encode( $_->{'content'} ) : undef } @$row;
$self->write_table_row( \@col_infos, \@data );
}
print $fh "\\end{longtable}\n\n";
return;
}
sub write_table_row {
my ( $self, $infos, $row, $link ) = @_;
my $fh = $self->{'_fh'};
my $skip = 0;
for ( my $i=0; $i < @$infos; $i++ ) {
if ( $skip ) {
$skip--;
next;
}
my $col_info = $infos->[$i];
if ( defined $row->[$i] && $col_info->col_start() != $col_info->col_end() ) {
my $spec = '';
$spec .= '|' if $col_info->col_start() eq 0;
$spec .= $col_info->class() eq 'categorical' ? 'l' : 'r';
$spec .= '|' if $col_info->col_start() eq $#$infos;
print $fh '\multicolumn{',
($col_info->col_end() - $col_info->col_start()) + 1,
'}{', $spec, '}{', $row->[$i], '}';
$skip = $col_info->col_end() - $col_info->col_start();
} elsif ( defined $row->[$i] ) {
print $fh $row->[$i];
}
print $fh " & " if $i != $#$infos;
}
print $fh "\\\\\n";
return;
}
sub write_table_footer {
my ( $self, $subreport ) = @_;
my $fh = $self->{'_fh'};
print $fh "\\hline\n\\multicolumn{",
$subreport->table_info()->ncols(),
'}{r}{\emph{', __( 'continued on next page' ), "}}\n\\endfoot\n\\hline\n";
my @values = grep { $_->class() eq 'numerical' } $subreport->table_info()->column_infos();
print $fh '\multicolumn{', $values[0]->col_start(), '}{|l}{\emph{',
__x( 'Total for {nrecords} records',
'nrecords' => $subreport->nrecords() ), '}} & ';
my @row = ();
foreach my $col_info ( @values ) {
$row[ $col_info->col_start() ] =
latex_encode( $subreport->get_summary_value( $col_info->name() )->{'content'} );
}
print $fh join ( ' & ', map { defined $_ ? $_ : '' }
@row[ $values[0]->col_start() .. $#row ] );
print $fh "\\\\\n\\hline\n\\endlastfoot\n\n";
return;
}
sub write_appendix {
my ( $self, $report ) = @_;
my $fh = $self->{'_fh'};
print $fh "\\appendix\n";
foreach my $schema ( @{ $report->schemas() } ) {
$self->write_schema( $schema );
}
return;
}
sub write_schema {
my ( $self, $schema_id ) = @_;
my $schema = Lire::DlfSchema::load_schema( $schema_id );
my $fh = $self->{'_fh'};
print $fh '\chapter{', latex_encode( $schema->title() ), "}\n\n";
print $fh dbk2latex( $schema->description() )
if $schema->description();
print $fh '\begin{description}', "\n";
foreach my $field ( $schema->fields() ) {
next if $field->name() =~ /(dlf_id|dlf_source)/; # Skip internal
print $fh '\item[\hypertarget{', $schema_id, ":",
$field->name(), '}{', latex_encode( $field->label() ), "}]\n";
print $fh dbk2latex( $field->description() )
if $field->description();
}
print $fh '\end{description}', "\n\n";
return;
}
sub process_latex {
my $self = $_[0];
return if $self->{'_format'} eq 'latex';
$self->create_dvi();
$self->create_ps()
if $self->{'_format'} =~ /ps|pdf/;
$self->create_pdf()
if $self->{'_format'} eq 'pdf';
my @clean = ( "$self->{'_outputbase'}.tex",
"$self->{'_outputbase'}.log", "$self->{'_outputbase'}.aux",
"$self->{'_outputbase'}.toc", "$self->{'_outputbase'}.out",
"$self->{'_outputbase'}.ofl");
push @clean, "$self->{'_outputbase'}.dvi", @{$self->{'_chart_files'}}
if $self->{'_format'} =~ /^(ps|pdf)$/;
push @clean, "$self->{'_outputbase'}.ps"
if $self->{'_format'} eq 'pdf';
unlink( @clean );
return;
}
sub create_dvi {
my $self = $_[0];
my $lambda = Lire::Config->get( 'lambda_path' );
my $status = system( "cd $self->{'_outputdir'} && $lambda -interaction=batchmode $self->{'_outputbase'} >/dev/null 2>/dev/null" );
my $log = file_content( "$self->{'_outputbase'}.log" );
if ( !$status ) {
# No error, check for rerun
$self->create_dvi() if $log =~ /rerun/i;
} else {
die "Error processing LaTeX file $self->{'_outputbase'}.tex:\n" .
join ( "", $log =~ /^(!.*)$/mg );
}
return;
}
sub create_ps {
my $self = $_[0];
my $odvips = Lire::Config->get( 'odvips_path' );
my $printer = $self->{'_format'} eq 'pdf' ? 'pdf' : 'ps';
my $status = system( "cd $self->{'_outputdir'} && $odvips -o $self->{'_outputbase'}.ps -P$printer -q $self->{'_outputbase'} > $self->{'_outputbase'}.log 2>&1 " );
die ("Error converting file $self->{'_outputbase'}.dvi to PS:\n",
file_content( "$self->{'_outputbase'}.log" ) )
if $status;
return;
}
sub create_pdf {
my $self = $_[0];
my $ps2pdf = Lire::Config->get( 'ps2pdf_path' );
my $status = system( "cd $self->{'_outputdir'} && $ps2pdf -dAutoRotatePages=/None $self->{'_outputbase'}.ps >$self->{'_outputbase'}.log 2>&1" );
die ("Error converting file $self->{'_outputbase'}.ps to PDF:\n",
file_content( "$self->{'_outputbase'}.log" ) )
if $status;
return;
}
1;
__END__
=pod
=head1 SEE ALSO
Lire::ReportParser::LaTeXDocBookFormatter(3pm)
Lire::OutputFormat(3pm) Lire::OutputFormats::LaTeX(3pm)
=head1 VERSION
$Id: LaTeXWriter.pm,v 1.16 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