package Lire::ReportParser::HTMLWriter; use strict; use Carp; use File::Basename; use File::Copy; use Locale::TextDomain 'lire'; use POSIX qw/ strftime /; use Lire::Config::Build qw/ ac_path /; use Lire::DlfSchema; use Lire::Error qw/ file_not_readable /; use Lire::DataTypes qw/ check_bool eval_bool /; use Lire::ReportParser::ReportBuilder; use Lire::ReportParser::HTMLDocBookFormatter qw/ dbk2html /; use Lire::Utils qw/ check_param check_object_param create_file indent is_url text_for_width xml_encode /; use Lire::I18N qw/set_fh_encoding /; =pod =head1 NAME Lire::ReportParser::HTMLWriter - converting lire XML reports to HTML =head1 SYNOPSIS use Lire::ReportParser::HTMLWriter; my $writer = new Lire::ReportParser::HTMLWriter(); $writer->write_report( $report, $outputdir, 'xhtml' => 0, 'one_page' => 0 ); =head1 DESCRIPTION An object capable of formatting a Lire::Report to HTML in the directory named $outputdir. =cut sub new { return bless {}, shift; } sub write_report { my ( $self, $report, $outputdir, %args ) = @_; check_object_param( $report, 'report', 'Lire::Report' ); check_param( $outputdir, 'outputdir' ); check_param( $args{'xhtml'}, 'xhtml', \&check_bool, "'xhtml' parameter should be a BOOLEAN (1 or 0)" ) if defined( $args{'xhtml'} ); check_param( $args{'one_page'}, 'one_page', \&check_bool, "'one_page' parameter should be a BOOLEAN (1 or 0)" ) if defined( $args{'one_page'} ); mkdir $outputdir or die "mkdir $outputdir failed: $!\n" unless -d $outputdir; die "can't write to $outputdir" unless -w $outputdir; $self->{'_report'} = $report; $self->{'_outputdir'} = $outputdir; $self->{'_one_page'} = ( defined $args{'one_page'} ? eval_bool( $args{'one_page'} ) : 0 ); $self->{'_xhtml'} = ( defined $args{'xhtml'} ? eval_bool( $args{'xhtml'} ) : 1 ); $self->{'_etag'} = $self->{'_xhtml'} ? ' />' : '>'; $self->start_page( 'index.html' ) if $self->{'_one_page'}; $self->write_toc(); my $i = 1; my $nsections = $report->sections(); foreach my $section ( $report->sections() ) { $self->{'_next'} = ( $i == $nsections ? $report->schemas()->[0] . ".html" : "section-" . ($i + 1) . ".html" ); $self->write_section( $section, $i ); $i++; } $i = 0; my $schemas = $report->schemas(); foreach my $schema ( @$schemas ) { $self->{'_next'} = ( $i == $#{$schemas} ? undef : $schemas->[$i+1] . ".html" ); $self->write_schema( $schema ); $i++; } $self->end_page() if $self->{'_one_page'}; $self->copy_html_files(); return; } sub write_links { my $self = $_[0]; my $css = Lire::Config->get( 'lr_html_css' ); my $link_infos = [ [ 'stylesheet', is_url( $css ) ? $css : basename( $css ), 'text/css', 1 ], [ 'author', 'http://www.logreport.org/', 'The LogReport Foundation', 1 ], [ 'help', 'http://logreport.org/doc/lire/', 'Lire documentation', 1 ], [ 'start', 'index.html', __( 'Table Of Contents' ), ! $self->{'_one_page'} ], [ 'first', 'section-1.html', __( 'First Section' ), ! $self->{'_one_page'} ], [ 'last', "section-" . $self->{'_report'}->sections() . ".html", __( 'Last Section' ), ! $self->{'_one_page'} ], [ 'prev', $self->{'_prev'}, __( 'Previous Page' ), ! $self->{'_one_page'} && $self->{'_prev'} ], [ 'next', $self->{'_next'}, __( 'Next Page' ), ! $self->{'_one_page'} && $self->{'_next'}], ]; my $fh = $self->{'_fh'}; foreach my $link ( @$link_infos ) { my ( $rel, $href, $title, $cond ) = @$link; next unless $cond; print $fh ' {'_etag'}, "\n"; } return; } sub report_title { my ( $self, $title ) = @_; my $report_title = $self->{'_report'}->title() || __( 'Lire Report' ); return xml_encode( $title ? $report_title .' : '. $title : $report_title ); } sub write_chart { my ( $self, $subreport, $chart_config ) = @_; my $fh = $self->{'_fh'}; my $type = $chart_config->type(); my $title = $chart_config->title(); $title = xml_encode( $title ) if $title; my $file = eval { $type->write_chart( $chart_config, $subreport, 'outputdir' => $self->{'_outputdir'}, 'format' => 'png' ) }; if ( $@ ) { print $fh ' ' x 8, '
', "\n", ' ' x 10, __x( 'An error occured while generating the chart: {error}', 'error' => $@ ), ' ' x 8, "
\n"; } elsif ( $file ) { my $base = basename( $file ); print $fh < $title{'_etag'} CHART } return; } sub write_table_header { my ( $self, $subreport ) = @_; my $fh = $self->{'_fh'}; my $info = $subreport->table_info(); print $fh ' ' x 8, "\n", ' ' x 10, "\n"; foreach my $row ( @{ $info->header_rows() } ) { my $skip = 0; print $fh ' ' x 12, '', "\n"; foreach my $cell ( @$row ) { if ( $skip > 0 ) { $skip--; next; } print $fh ' ' x 14; unless ( defined $cell ) { print $fh '', "\n"; next; } if ( $cell->col_start() != $cell->col_end() ) { $skip = $cell->col_end() - $cell->col_start(); print $fh '\n"; } print $fh ' ' x 12, "\n"; } print $fh ' ' x 10, "\n"; return; } sub write_table_footer { my ( $self, $subreport ) = @_; my $fh = $self->{'_fh'}; print $fh ' ' x 10, "\n", ' ' x 12, "\n"; my @values = grep { $_->class() eq 'numerical' } $subreport->table_info()->column_infos(); print $fh ' ' x 14, '\n"; my $next = $values[0]->col_start(); foreach my $col_info ( @values ) { my $curr = $col_info->col_start(); while ( $next < $curr ) { print $fh ' ' x 14, '', "\n"; $next++; } my $value = $subreport->get_summary_value( $col_info->name() ); print $fh ' ' x 14, '\n"; $next = $col_info->col_start() + 1; } print $fh ' ' x 12, "\n", ' ' x 10, "\n"; return; } sub html_value { my ( $self, $string, $max_width ) = @_; $max_width = 180 unless defined $max_width; $max_width = 5 if ( $max_width < 5 ); my $link; my $cropped_string = xml_encode( text_for_width( $string, $max_width ) ); if ( $string =~ m@^[a-zA-Z]+://@ ) { $link = "$cropped_string"; } elsif ( $string =~ m/([\.a-zA-Z0-9_+\#\$-]+@[\.a-zA-Z0-9_+\#\$-]+)/ ) { $link = "$cropped_string"; } else { $link = $cropped_string; } return $link; } sub write_schema { my ($self, $schema_id) = @_; my $schema = Lire::DlfSchema::load_schema( $schema_id ); unless ( $self->{'_one_page'} ) { $self->start_page( "$schema_id.html", $schema->title() ); } my $fh = $self->{'_fh'}; print $fh '
', "\n"; print $fh '

', xml_encode( $schema->title() ), "

\n" if $self->{'_one_page'}; print $fh "
\n"; foreach my $field ( $schema->fields() ) { next if $field->name() =~ /^(dlf_id|dlf_source)$/; my $name = $self->{'_one_page'} ? "schema:$schema_id:" . $field->name() : $field->name(); my $label = xml_encode( $field->label() ); my $desc = $field->description ? dbk2html( $field->description() ) : __( 'No description.' ); print $fh <$label
$desc
TERM } print $fh <
SECTION unless ( $self->{'_one_page'} ) { $self->end_page(); $self->{'_prev'} = "$schema_id.html"; } return; } sub write_table_entries { my ( $self, $subreport ) = @_; my $fh = $self->{'_fh'}; unless ( $subreport->entries() ) { print $fh ' ' x 10, "\n", ' ' x 12, "\n", ' ' x 14, '\n", ' ' x 12, "\n", ' ' x 10, "\n", ' ' x 8, "
'; } else { print $fh ''; } my $schema = $subreport->field_schema( $cell->name() ); print $fh 'name(), '">' if $schema; print $fh xml_encode( $cell->label() ); print $fh '' if $schema; print $fh "
', __x( 'Total for {nrecords} records', 'nrecords' => $subreport->nrecords() ), "', $self->html_value( $value->{'content'}, 50 ), "
', __( 'There is no entries in this table.' ), "
\n"; return; } print $fh ' ' x 10, "\n"; foreach my $row ( @{$subreport->getrows()} ) { my $skip = 0; print $fh ' ' x 12, "\n"; foreach my $cell ( @$row ) { if ( $skip > 0 ) { $skip--; next; } print $fh ' ' x 14; unless ( defined $cell ) { print $fh '', "\n"; next; } my $col_info = $cell->{'col_info'}; if ( $col_info->col_start() != $col_info->col_end() ) { $skip = $col_info->col_end() - $col_info->col_start(); print $fh '', $self->html_value( $cell->{'content'} ), "\n"; } print $fh ' ' x 12, "\n"; } print $fh ' ' x 10, "\n", ' ' x 8, "\n"; return; } sub write_subreport { my ( $self, $subreport ) = @_; my $fh = $self->{'_fh'}; my $id = $subreport->id(); my $title = xml_encode( $subreport->title() ); my $desc = $subreport->description() ? dbk2html( $subreport->description() ) : ''; print $fh <

$title

$desc
SUBREPORT if ( $subreport->is_missing() ) { print $fh ' ' x 8, '
', "\n", ' ' x 10, __x( 'This report is missing: {reason}', 'reason' => xml_encode( $subreport->missing_reason() ) ), "\n", ' ' x 8, '
', "\n", ' ' x 6, "\n"; return; } 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 ); print $fh ' ' x 6, "\n"; return; } sub write_section { my ( $self, $section, $index ) = @_; unless ( $self->{'_one_page'} ) { $self->start_page( "section-$index.html", $section->title() ); } my $fh = $self->{'_fh'}; print $fh '
', "\n", '

'; print $fh '' if $self->{'_one_page'}; print $fh xml_encode( $section->title() ); print $fh '' if $self->{'_one_page'}; print $fh "

\n"; print $fh '
', "\n", ' ', dbk2html( $section->description() ), "\n", '
', "\n" if $section->description(); if ( $section->subreports() ) { foreach my $subreport ( $section->subreports() ) { $self->write_subreport( $subreport ); } } else { print $fh '

', __( 'No subreports were generated for this section.' ), "

\n"; } print $fh "
\n"; unless ( $self->{'_one_page'} ) { $self->end_page(); $self->{'_prev'} = "section-$index.html"; } return; } sub write_toc { my $self = $_[0]; unless ( $self->{'_one_page'} ) { $self->{'_next'} = 'section-1.html'; $self->start_page( 'index.html', __( 'Table Of Contents' ) ); } my $fh = $self->{'_fh'}; print $fh '
', "\n"; print $fh '

', __( 'Table Of Contents' ), "

\n" if $self->{'_one_page'}; print $fh '
    ', "\n"; my $i = 1; foreach my $section ( $self->{'_report'}->sections() ) { $self->write_section_toc( $section, $i ); $i += 1; } foreach my $schema_id ( @{$self->{'_report'}->schemas()} ) { my $schema = Lire::DlfSchema::load_schema( $schema_id ); my $href = $self->{'_one_page'} ? "#schema:" . $schema_id : $schema_id . ".html"; $self->write_toc_link( $href, $schema->title(), 8 ); } print $fh <
TOC unless ( $self->{'_one_page'} ) { $self->end_page(); $self->{'_toc'} = 'index.html'; $self->{'_prev'} = 'index.html'; } return; } sub write_section_toc { my ( $self, $section, $idx ) = @_; my $href = $self->{'_one_page'} ? "#section-$idx" : "section-$idx.html"; my $title = xml_encode( $section->title() ); my $fh = $self->{'_fh'}; print $fh <
$title
    SECTION foreach my $subreport ( $section->subreports() ) { my $href = $self->{'_one_page'} ? '#' . $subreport->id() : "section-$idx.html#" . $subreport->id(); $self->write_toc_link( $href, $subreport->title(), 14 ); } print $fh <
SECTION return; } sub write_toc_link { my ( $self, $href, $title, $indent ) = @_; print {$self->{'_fh'}} ' ' x $indent, '
  • ', xml_encode( $title ), '
  • ', "\n"; return; } sub write_navbar { my $self = $_[0]; return if $self->{'_one_page'}; my $fh = $self->{'_fh'}; print $fh ' \n"; return; } sub write_report_infos { my $self = $_[0]; my $fh = $self->{'_fh'}; my $format = '%Y-%m-%d %H:%M:%S %Z'; print $fh '
    ', "\n"; print $fh ' ', __x( 'Report generated on: {date}', 'date' => strftime( $format, localtime( $self->{'_report'}->date() ) ) ); print $fh "{'_etag'}\n"; my $period; if ( $self->{'_report'}->timespan_start() ) { $period = strftime( $format, localtime( $self->{'_report'}->timespan_start() ) ) . ' - ' . strftime( $format, localtime( $self->{'_report'}->timespan_end() ) ); } else { $period = __( 'Unknown Period' ); } print $fh ' ', __x( 'Reporting on period: {period}', 'period' => $period ), "{'_etag'}\n
    \n"; return; } sub write_logo { my $self = $_[0]; my $logo = Lire::Config->get( 'lr_logo' ); return unless $logo; my $url = Lire::Config->get( 'lr_logo_url' ); my $fh = $self->{'_fh'}; print $fh ' ', "\n"; return; } sub start_page { my ( $self, $page, $title ) = @_; open $self->{'_fh'}, "> $self->{'_outputdir'}/$page" or die "can't write to '$self->{'_outputdir'}/$page': $!\n"; set_fh_encoding( $self->{'_fh'}, 'utf-8' ); my $fh = $self->{'_fh'}; if ( $self->{'_xhtml'} ) { print $fh < DTD } else { print $fh < DTD } my $page_title = $self->report_title( $title ); print $fh < $page_title {'_etag'} HEAD $self->write_links(); print $fh <
    BODY $self->write_logo(); $self->write_navbar(); $title = $title ? xml_encode( $title ) : $self->report_title(); print $fh <$title TITLE $self->write_report_infos(); print $fh "
    \n"; return; } sub end_page { my $self = $_[0]; print {$self->{'_fh'}} < HTML $self->{'_fh'}->close(); $self->{'_fh'} = undef; return; } sub copy_html_files { my $self = $_[0]; my $logo = Lire::Config->get( 'lr_logo' ); copy( $logo, $self->{'_outputdir'} ) unless is_url( $logo ); my $css = Lire::Config->get( 'lr_html_css' ); copy( "$css", $self->{'_outputdir'} ) unless is_url( $css ); unless ( $self->{'_one_page'} ) { my $html_dir = ac_path( 'datadir', 'PACKAGE' ) . '/html'; foreach my $file ( qw/ toc.png prev.png next.png / ) { copy( "$html_dir/$file", $self->{'_outputdir'} ); } } return; } # keep perl happy 1; __END__ =pod =head1 AUTHORS Wolfgang Sourdeau Francis J. Lacoste =head1 VERSION $Id: HTMLWriter.pm,v 1.35 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. =cut