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", <{'_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 =cut