package Lire::ReportParser::AsciiWriter;
use strict;
use base qw/ Lire::ReportParser::RowColHandler /;
use Text::Wrap qw/ wrap /;
use Lire::I18N qw/ set_fh_encoding /;
use Lire::ReportParser::AsciiDocBookFormatter qw/dbk2txt/;
sub new {
my $self = shift->SUPER::new( @_, 'summary_when' => 'after' );
my %args = @_;
$self->{'columns'} = $args{'columns'} || 74;
$self->{'max_name_len'} = $args{'max_name_length'} || 180;
$self->{'_fh'} = $args{'output'} || \*STDOUT;
$self->{'_encoding'} = $args{'encoding'} || '';
set_fh_encoding( $self->{'_fh'}, $self->{'_encoding'} )
if $self->{'_encoding'};
return $self;
}
sub report_start {
my $self = shift;
$self->SUPER::report_start( @_ );
$self->{'print_timespan'} = 1;
return;
}
sub print_text {
my $self = shift;
my $fh = $self->{'_fh'};
print $fh @_;
}
sub handle_timespan {
my ( $self, $timespan ) = @_;
if ( $self->{'print_timespan'} ) {
my $date = $self->current_date();
my $timespan = $self->current_timespan();
$self->print_text( "Report generated: ", $date->{'date'}, "\n" )
if $date;
if ( $timespan ) {
$self->print_text( "Reporting on period:\n",
$timespan->{'timespan'}, "\n\n" );
}
$self->{'print_timespan'} = 0;
}
}
sub handle_title {
my ( $self, $title ) = @_;
# Trim the title
$title =~ s/^\s*//;
$title =~ s/\s*$//;
if ($self->in_element( "lire:section" ) ) {
# Section's title
my $len = length $title;
if ( $len > $self->{'columns'} - 12 ) {
local $Text::Wrap::columns = $self->{'columns'} - 12;
$self->print_text( wrap( " ", " ", $title ), "\n",
" ", "-" x ($self->{'columns'} - 12), "\n\n"
);
} else {
my $pad = int( ($self->{'columns'} - $len) / 2);
$self->print_text( ' ' x $pad, $title, "\n", ' ' x $pad,
'-' x $len, "\n\n" );
}
} else {
local $Text::Wrap::columns = $self->{'columns'} - 12;
$self->print_text( wrap( " ", " ", $title ), "\n\n" );
}
}
sub section_end {
my $self = shift;
$self->print_text( " No subreports were generated for this section.\n\n" )
if ( ! $self->current_section_subreport_count() );
$self->SUPER::section_end( @_ );
}
sub subreport_start {
my $self = shift;
$self->SUPER::subreport_start( @_ );
$self->{'printed_description'} = 0;
return;
}
sub subreport_end {
my $self = shift;
$self->SUPER::subreport_end( @_ );
$self->print_text( "\n" );
return;
}
sub table_start {
my $self = shift;
$self->SUPER::table_start( @_ );
$self->{'curr_headers'} = [];
return;
}
sub table_end {
my $self = shift;
$self->print_text( " No content in report.\n\n" )
unless ( $self->current_table_entry_count() );
$self->SUPER::table_end( @_ );
# Reset
delete $self->{'curr_cols_empty_fmt'};
delete $self->{'curr_cols_print_fmt'};
return;
}
sub table_info_end {
my $self = shift;
$self->SUPER::table_info_end( @_ );
return;
}
#------------------------------------------------------------------------
# Method set_formatters()
#
# Create the formatter that will be used to print the value of each
# column.
sub set_formatters {
my ( $self ) = @_;
my $width = $self->compute_cols_width();
my @cols = $self->current_table_info()->column_infos();
$self->{'curr_cols_empty_fmt'} = [];
$self->{'curr_cols_print_fmt'} = [];
$self->{'curr_sep'} = ' ';
# Empty formatter is only a space specification
for (my $i=0; $i<@$width; $i++) {
$self->{'curr_cols_empty_fmt'}[$i] = ' ' x $width->[$i];
}
# Create a printf specification that can be used
# to print a value for each column or when printing an empty
foreach my $c ( @cols ) {
my $start = $c->col_start();
my $end = $c->col_end();
my $w = 0;
for (my $i=$start; $i<=$end; $i++) {
$w += $width->[$i];
# Add the sep's space
$w++ if $i < $end;
}
if ( $c->class() eq 'categorical' ) {
$self->{'curr_cols_print_fmt'}[$start] = '%-' . $w . 's';
# Create a -- sep for this categorical column on the first row
if ( $c->group_info()->row_idx() == 0 ) {
$self->{'curr_sep'} .= '-' x $w . ' ';
}
} else {
$self->{'curr_cols_print_fmt'}[$start] = '%' . $w . 's';
# Value column always need generate a sep and reset the
# need for one (the next categorical column will generate one)
$self->{'curr_sep'} .= '-' x $w;
$self->{'curr_sep'} .= ' ' unless $end == @cols -1;
}
# Sets the col_width in the ColumnInfo object
$c->col_width( $w );
}
}
#------------------------------------------------------------------------
# Method compute_cols_width()
#
# Determine the width of each columns in the table. Distribute (or crop)
# the space difference between the suggested columns width and the
# width allocated for formatting (the columns parameter).
#
# The distribution is allocated based on a 1 character space inserted
# between the columns and 2 spaces indenting columns.
#
# Returns the resulting columns size as an array reference.
sub compute_cols_width {
my ( $self ) = @_;
my $info = $self->current_table_info();
my @cols = $info->column_infos();
my @width = ( 0 x @cols );
my $sep = @cols - 1;
my $total = 0;
foreach my $c ( @cols ) {
$width[ $c->col_start() ] = $c->col_width();
$total += $c->col_width;
}
my $diff = $self->{'columns'} - ( $total + $sep ) - 2; # '-2' => row indent
# Extra/missing space is added/removed to/from categorical
# columns
# We only select the categorical columns that have columns width
# of more than 10 chars
my @cat = grep { $_->class() eq 'categorical' &&
$_->col_width() > 10 } @cols;
# None! Grab all then
@cat = grep { $_->class() eq 'categorical' } @cols unless @cat;
if ($diff > 0 ) {
# Allocate evenly between columns
my $extra = int( $diff / @cat );
foreach my $c ( @cat ) {
$width[ $c->col_start() ] += $extra;
}
my $rest = $diff % @cat; # Assigned to the first column
$width[ $cat[0]->col_start() ] += $rest;
} else {
$diff = abs $diff;
my $found = 0;
foreach my $c ( @cat ) {
my $w = $width[ $c->col_start() ];
# Reduce columns proportionnally: bigger columns are
# reduced more
my $shrink = int( $diff * ( $w / $total ));
if ( $w - $shrink < 10 ) {
# Prevent columns from shrinking too much
$shrink = $w - 10;
}
$width[ $c->col_start() ] -= $shrink;
$found += $shrink
}
$diff = $diff - $found;
if ( $diff > 0 ) {
# Try to shrink real hard or overflow the table at worst
foreach my $c ( sort { $width[$b->col_start() ] <=> $width[ $a->col_start() ] } @cat ) {
my $w = $width[ $c->col_start() ];
my $shrink = $w - $diff > 10 ? $diff : $w - 10;
$width[ $c->col_start() ] -= $shrink;
$diff -= $shrink;
last if $diff <= 0;
}
}
}
return \@width;
}
sub handle_header_row {
my ( $self, $row ) = @_;
my @header_row = ();
for (my $i=0; $i<@$row; $i++) {
my $c = $row->[$i];
if ( $c ) {
$header_row[$i] = { 'content' => $c->label,
'col_info' => $c,
};
} else {
$header_row[$i] = undef;
}
}
push @{$self->{'curr_headers'}}, \@header_row;
return;
}
sub handle_row {
my ( $self, $row ) = @_;
if ($self->{'curr_headers'}) {
foreach my $r ( @{$self->{'curr_headers'}}) {
$self->print_row( $r );
}
$self->print_sep;
delete $self->{'curr_headers'};
}
$self->print_row( $row );
return;
}
sub handle_table_summary {
my ( $self, $nrecords, $row ) = @_;
return unless $nrecords && $self->current_table_entry_count();
$row->[0] = {
'content' => "Total for $nrecords records",
'col_info' => $self->current_table_info->column_info_by_col_start( 0 )
};
$self->print_sep;
$self->print_row( $row );
$self->print_text( "\n" );
return;
}
sub handle_description {
my ( $self, $docbook ) = @_;
$self->print_text( dbk2txt( $docbook, $self->{'columns'} ) );
return;
}
sub print_sep {
my $self = $_[0];
$self->set_formatters()
unless exists $self->{'curr_cols_empty_fmt'};
$self->print_text( $self->{'curr_sep'}, "\n" );
return;
}
sub print_row {
my ( $self, $row ) = @_;
$self->set_formatters()
unless exists $self->{'curr_cols_empty_fmt'};
my @overflow = @$row;
# Initial row indent
$self->print_text( " " );
for ( my $i=0; $i< @$row; $i++ ) {
my $c = $row->[$i];
if ( $c ) {
my $len = length $c->{'content'};
my $w = $c->{'col_info'}->col_width();
# Crop to absolute maximum
if ( $len > $self->{'max_name_len'} ) {
$c->{'content'} = substr $c->{'content'}, 0, $self->{'max_name_len'};
$len = $self->{'max_name_len'};
}
# Check if the content is wider than the column's width
my $value;
if ( $len > $w ) {
$value = substr( $c->{'content'}, 0, $w-1) . "\\";
# Keep the cell content for an overflowing row
$c->{'content'} = ' ' . substr $c->{'content'}, $w-1;
$overflow[$i] = $c;
} else {
$value = $c->{'content'};
$overflow[$i] = undef;
}
$self->print_text( sprintf( $self->{'curr_cols_print_fmt'}[$i],
$value ) );
# Skip after the column span
$i = $c->{'col_info'}->col_end();
} else {
$self->print_text( $self->{'curr_cols_empty_fmt'}[$i] );
}
$self->print_text( " " ) unless $i + 1 == @$row;
}
$self->print_text( "\n" );
# Print overflow row if needed
$self->print_row( \@overflow ) if grep { defined $_ } @overflow;
return;
}
1;
__END__
=pod
=head1 NAME
Lire::ReportParser::AsciiWriter - Lire::ReportParser processor that formats the report in ASCII
=head1 SYNOPSIS
use Lire::ReportParser::AsciiWriter;
my $parser = new Lire::ReportParser::AsciiWriter;
eval { $parser->parsefile( "report.xml" ) };
print "Parse failed: $@" if $@;
=head1 DESCRIPTION
This is a Lire::ReportParser processor which will print the Lire XML
Report in ASCII on STDOUT.
Its constructor takes the following parameters:
=over 4
=item columns
The number of columsn for which the report should be formatted. Defaults
to 80.
=item max_name_length
The maximum number of characters that will be kept in name element.
Defaults to 180.
=item userlevel
In description, the maximum level of elements that will be displayed.
Defaults to C<normal> (i.e. C<advanced> elements aren't formatted.
=back
=head1 SEE ALSO
Lire::ReportParser(3pm) Lire::AsciiDocBookFormatter(3pm)
=head1 VERSION
$Id: AsciiWriter.pm,v 1.41 2006/07/23 13:16:31 vanbaal Exp $
=head1 COPYRIGHT
Copyright (C) 2001-2002 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