| Data-Report documentation | Contained in the Data-Report distribution. |
Data::Report - Framework for flexible reporting
use Data::Report;
# Create a new reporter.
my $rep = Data::Report::->create(type => "text"); # or "html", or "csv", ...
# Define the layout.
$rep->set_layout
([ { name => "acct", title => "Acct", width => 6 },
{ name => "desc", title => "Description", width => 40, align => "<" },
{ name => "deb", title => "Debet", width => 10, align => ">" },
{ name => "crd", title => "Credit", width => 10, align => ">" },
]);
# Start the reporter.
$rep->start;
# Add data, row by row.
$rep->add({ acct => 1234, desc => "Received", deb => "242.33" });
$rep->add({ acct => 5678, desc => "Paid", crd => "699.45" });
$rep->add({ acct => 1259, desc => "Taxes", deb => "12.00", crd => "244.00" });
$rep->add({ desc => "TOTAL", deb => "254.33", crd => "943.45" });
# Finish the reporter.
$rep->finish;
Data::Report is a flexible, plugin-driven reporting framework. It makes it easy to define reports that can be produced in text, HTML and CSV. Textual ornaments like extra empty lines, dashed lines, and cell lines can be added in a way similar to HTML style sheets.
The Data::Report framework consists of three parts:
Plugins implement a specific type of report. Standard plugins provided
are Data::Report::Plugin::Text for textual reports,
Data::Report::Plugin::Html for HTML reports, and
Data::Report::Plugin::Csv for CSV (comma-separated) files.
Users can, and are encouraged, to develop their own plugins to handle different styles and types of reports.
The base class Data::Report::Base implements the functionality
common to all reporters, plus a number of utility functions the
plugins can use.
The actual Data::Report module is a factory that creates a
reporter for a given report type by selecting the appropriate plugin
and returning an instance thereof.
Note that except for the create method, all other methods are
actually handled by the plugins and their base class.
Reporter objects are created using the class method create. This
method takes a hash (or hashref) of arguments to initialise the
reporter object.
The actual reporter object is implemented by one of the plugin
modules, selected by the type argument. Standard plugins are
provided for text, HTML and CSV report types. The default
type is text.
When looking for a plugin to support report type foo, the create
method will first try to load a module My::Package::Foo where
My::Package is the invocant class. If this module cannot be loaded,
it will fall back to Data::Report::Plugin::Foo. Note that, unless
subclassed, the current class will be Data::Report.
All other initialisation arguments correspond to attribute setting methods provided by the plugins. For example, the hypothetical call
my $rpt = Data::Report->create(foo => 1, bar => "Hello!");
is identical to:
my $rpt = Data::Report->create;
$rpt->set_foo(1);
$rpt->set_bar("Hello!");
You can choose any combination at your convenience.
This method indicates that all setup has been completed, and starts
the reporter. Note that no output is generated until the add method
is called.
start takes no arguments.
Although this method could be eliminated by automatically starting the
reporter upon the first call to add, it turns out that an aplicit
start makes the API much cleaner and makes it easier to catch mistakes.
This method adds a new entry to the report. It takes one single argument, a hash ref of column names and the corresponding values. Missing columns are left blank.
In addition to the column names and values, you can add the special
key _style to designate a particular style for this entry. What
that means depends on the plugin that implements this reporter. For
example, the standard HTML reporter plugin prefixes the given style
with r_ to form the class name for the row.
The style name should be a simple name, containing letters, digits and
underscores, starting with a letter.
Example
$rpt->add({ date => "2006-04-31",
amount => 1000,
descr => "First payment",
_style => "plain" });
This method indicates that report generation is complete. After this,
you can call start again to initiate a new report.
finish takes no arguments.
This is a convenience method. If the output stream was set up by the
reporter itself (see set_output, below), the stream will be
closed. Otherwise, this method will be a no-op.
close takes no arguments.
The reporter type.
This is the most important attribute, since it effectively defines the report layout.
This method takes one argument, an array reference. Each element of
the array is a hash reference that corresponds to one column in the
report. The order of elements definines the order of the columns in
the report, but see set_fields below.
The following keys are possible in the hash reference:
nameThe name of this column. The name should be a simple name, containing letters, digits and underscores, starting with a letter.
The standard HTML reporter plugin uses the column name to form a class
name for each cell by prefixing with c_. Likewise, the classes for
the table headings will be formed by prefixing the column names with
h_. See ADVANCED EXAMPLES, below.
titleThe title of this column. This title is placed in the column heading.
widthThe width of this column. Relevant for textual reporters only.
By default, if a value does not fit in the given width, it will be
spread over multiple rows in a pseudo-elegant way. See also the
truncate key, below.
alignThe alignment of this column. This can be either < for
left-aligned columns, or > to indicate a right-aligned column.
truncateIf true, the values in this column will be truncated to fit the width of the column. Relevant for textual reporters only.
This method can be used to set an arbitrary style (a string) whose meaning depends on the implementing plugin. For example, a HTML plugin could use this as the name of the style sheet to use.
The name should be a simple name, containing letters, digits and underscores, starting with a letter.
Returns the style, or default if none.
Designates the destination for the report. The argument can be
All output will be appended to the designated scalar.
All output lines will be pushed onto the array.
A file will be created with the given name, and all output will be
written to this file. To close the file, use the close method described above.
Anything else will be considered to be a file handle, and treated as such.
The stylist is a powerful method to control the appearance of the report at the row and cell level. The basic idea is taken from HTML style sheets. By using a stylist, it is possible to add extra spaces and lines to rows and cells in a declarative way.
When used, the stylist should be a reference to a possibly anonymous
subroutine with three arguments: the reporter object, the style of a
row (as specified with _style in the add method), and the name
of a column as defined in the layout. For table headings, the row name
_head is used.
The stylist routine will be repeatedly called by the reporter to obtain formatting properties for rows and cells. It should return either nothing, or a hash reference with properties.
When called with only the row argument, it should return the
properties for this row.
When called with row equal to "*" and a column name, it should return the properties for the given column.
When called with a row and a column name, it should return the properties for the given row/column (cell).
All appropriate properties are merged to form the final set of properties to apply.
The following row properties are recognised. Between parentheses the backends that support them.
skip_before(Text) Produce an empty line before printing the current row.
skip_after(Text) Produce an empty line after printing the current row, but only if other data follows.
line_before(Text) Draw a line of dashes before printing the current row.
line_after(Text) Draw a line of dashes after printing the current row.
cancel_skip(Text) Cancel the effect of a pending skip_after
ignore(All) Ignore this row. Useful for CSV backends where only the raw data matters, and not the totals and such.
The following cell properties are recognised. Between parentheses the backends that support them.
indent(Text) Indent the contents of this cell with the given amount.
wrap_indent(Text) Indent wrapped contents of this cell with the given amount.
truncate(Text) If true, truncate the contents of this cell to fit the column width.
line_before(Text) Draw a line in the cell before printing the current row. The value of
this property indicates the symbol to use to draw the line. If it is
1, dashes are used.
line_after(Text) Draw a line in the cell after printing the current row. The value of
this property indicates the symbol to use to draw the line. If it is
1, dashes are used.
raw_html(Html) Do not escape special HTML characters, allowing pre-prepared HTML code to be placed in the output. Use with care.
ignore(All) Ignore this column. Note that to prevent surprising results, the
column must be ignored in all applicable styles, including the special
style "_head" that controls the heading.
class(Html) Class name to be used for this cell. Default class name is "h_CNAME" for table headings and "c_CNAME" for table rows, where CNAME is the name of the column.
Example:
$rep->set_stylist(sub {
my ($rep, $row, $col) = @_;
unless ( $col ) {
return { line_after => 1 } if $row eq "total";
return;
}
return { line_after => 1 } if $col eq "amount";
return;
});
Each reporter provides a standard (dummy) stylist called
_std_stylist. Overriding this method is equivalent to using
set_stylist.
Returns the current stylist, if any.
Headings consist of two parts, the top heading, and the standard heading. Bij default, the top heading is empty, and the standard heading has the names of the columns with a separator line (depnendent on the plugin used).
This method can be used to designate a subroutine that will provide the top heading of the report.
Example:
$rpt->set_topheading(sub {
my $self = shift;
$self->_print("Title line 1\n");
$self->_print("Title line 2\n");
$self->_print("\n");
});
Note the use of the reporter provided _print method to produce output.
When subclassing a reporter, a method _top_heading can be defined
to provide the top heading. This is equivalent to an explicit call to
set_topheading, but doesn't need to be repeatedly and explicitly
executed for each new reporter.
Returns the current top heading routine, if any.
This method can be used to designate a subroutine that provides the standard heading of the report.
In normal cases using this method is not necessary, since setting the top heading will be sufficient.
Each reporter plugin provides a standard heading, implemented in a
method called _std_header. This is the default value for the
heading attribute. A user-defined heading can use
$self->SUPER::_std_header;
to still get the original standard heading produced.
Example:
$rpt->set_heading(sub {
my $self = shift;
$self->_print("Title line 1\n");
$self->_print("Title line 2\n");
$self->_print("\n");
$self->SUPER::_std_heading;
$self->_print("\n");
});
Note the use of the reporter provided _print method to produce output.
When subclassing a reporter, the method _std_heading can be
overridden to provide a customized top heading. This is equivalent to
an explicit call to set_topheading, but doesn't need to be
repeatedly and explicitly executed for each new reporter.
Returns the current standard heading routine, if any.
This method can be used to define what columns (fields) should be included in the report and the order they should appear. It takes an array reference with the names of the desired columns.
Example:
$rpt->set_fields([qw(descr amount date)]);
Returns the current set of selected columns.
This method defines the width for one or more columns. It takes a hash reference with column names and widths. The width may be an absolute number, a relative number (to increase/decrease the width, or a percentage.
Example:
$rpt->set_width({ amount => 10, desc => '80%' });
Returns a hash with all column names and widths.
This example subclasses Data::Report with an associated plugin for
type text. Note the use of overriding _top_heading and
_std_stylist to provide special defaults for this reporter.
package POC::Report;
use base qw(Data::Report);
package POC::Report::Text;
use base qw(Data::Report::Plugin::Text);
sub _top_heading {
my $self = shift;
$self->_print("Title line 1\n");
$self->_print("Title line 2\n");
$self->_print("\n");
}
sub _std_stylist {
my ($rep, $row, $col) = @_;
if ( $col ) {
return { line_after => "=" }
if $row eq "special" && $col =~ /^(deb|crd)$/;
}
else {
return { line_after => 1 } if $row eq "total";
}
return;
}
It can be used as follows:
my $rep = POC::Report::->create(type => "text");
$rep->set_layout
([ { name => "acct", title => "Acct", width => 6 },
{ name => "desc", title => "Report", width => 40, align => "<" },
{ name => "deb", title => "Debet", width => 10, align => "<" },
{ name => "crd", title => "Credit", width => 10, align => ">" },
]);
$rep->start;
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "special"});
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "total" });
$rep->finish;
The output will look like:
Title line 1
Title line 2
Acct Report Debet Credit
------------------------------------------------------------------------
one two three four
one two three four
one two three four
========== ==========
one two three four
------------------------------------------------------------------------
This is a similar example for a HTML reporter:
package POC::Report;
use base qw(Data::Report);
package POC::Report::Html;
use base qw(Data::Report::Plugin::Html);
sub start {
my $self = shift;
$self->{_title1} = shift;
$self->{_title2} = shift;
$self->{_title3} = shift;
$self->SUPER::start;
}
sub _top_heading {
my $self = shift;
$self->_print("<html>\n",
"<head>\n",
"<title>", $self->_html($self->{_title1}), "</title>\n",
'<link rel="stylesheet" href="css/', $self->get_style, '.css">', "\n",
"</head>\n",
"<body>\n",
"<p class=\"title\">", $self->_html($self->{_title1}), "</p>\n",
"<p class=\"subtitle\">", $self->_html($self->{_title2}), "<br>\n",
$self->_html($self->{_title3}), "</p>\n");
}
sub finish {
my $self = shift;
$self->SUPER::finish;
$self->_print("</body>\n</html>\n");
}
Note that it defines an alternative start method, that is used to
pass in additional parameters for title fields.
The method _html is a convenience method provided by the framework.
It returns its argument with sensitive characters escaped by HTML
entities.
It can be used as follows:
package main;
my $rep = POC::Report::->create(type => "html");
$rep->set_layout
([ { name => "acct", title => "Acct", width => 6 },
{ name => "desc", title => "Report", width => 40, align => "<" },
{ name => "deb", title => "Debet", width => 10, align => "<" },
{ name => "crd", title => "Credit", width => 10, align => ">" },
]);
$rep->start(qw(Title_One Title_Two Title_Three_Left&Right));
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "normal" });
$rep->add({ acct => "one", desc => "two", deb => "three", crd => "four", _style => "total" });
$rep->finish;
The output will look like this:
<html> <head> <title>Title_One</title> <link rel="stylesheet" href="css/default.css"> </head> <body> <p class="title">Title_One</p> <p class="subtitle">Title_Two<br> Title_Three_Left&Right</p> <table class="main"> <tr class="head"> <th align="left" class="h_acct">Acct</th> <th align="left" class="h_desc">Report</th> <th align="right" class="h_deb">Debet</th> <th align="right" class="h_crd">Credit</th> </tr> <tr class="r_normal"> <td align="left" class="c_acct">one</td> <td align="left" class="c_desc">two</td> <td align="right" class="c_deb">three</td> <td align="right" class="c_crd">four</td> </tr> <tr class="r_normal"> <td align="left" class="c_acct">one</td> <td align="left" class="c_desc">two</td> <td align="right" class="c_deb">three</td> <td align="right" class="c_crd">four</td> </tr> <tr class="r_normal"> <td align="left" class="c_acct">one</td> <td align="left" class="c_desc">two</td> <td align="right" class="c_deb">three</td> <td align="right" class="c_crd">four</td> </tr> <tr class="r_total"> <td align="left" class="c_acct">one</td> <td align="left" class="c_desc">two</td> <td align="right" class="c_deb">three</td> <td align="right" class="c_crd">four</td> </tr> </table> </body> </html>
See also the examples in t/09poc*.t.
Johan Vromans, <jvromans at squirrel.nl>
Disclaimer: This module is derived from actual working code, that I turned into a generic CPAN module. During the process, some features may have become unstable, but that will be cured in time. Also, it is possible that revisions of the API will be necessary when new functionality is added.
Please report any bugs or feature requests to
bug-data-report at rt.cpan.org, or through the web interface at
http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Data-Report.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
You can find documentation for this module with the perldoc command.
perldoc Data::Report (user API)
perldoc Data::Report::Base (plugin writer documentation)
You can also look for information at:
Copyright 2006,2008 Squirrel Consultancy, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Data-Report documentation | Contained in the Data-Report distribution. |
# Data::Reporter.pm -- Framework for flexible reporting # RCS Info : $Id: Report.pm,v 1.17 2008/08/18 09:51:23 jv Exp $ # Author : Johan Vromans # Created On : Wed Dec 28 13:18:40 2005 # Last Modified By: Johan Vromans # Last Modified On: Mon Aug 18 11:52:01 2008 # Update Count : 265 # Status : Unknown, Use with caution! package Data::Report;
$VERSION = "0.10";
use strict; use warnings; use Carp;
sub create { my $class = shift; my $args; if ( @_ == 1 && UNIVERSAL::isa($_[0], 'HASH') ) { $args = shift; } else { $args = { @_ }; } # 'type' attribute is mandatory. my $type = ucfirst(lc($args->{type})); #croak("Missing \"type\" attribute") unless $type; $type = "Text" unless $type; # Try to load class specific plugin. my $plugin = $class . "::" . $type; $plugin =~ s/::::/::/g; # Strategy: load the class, and see if it exists. # A plugin does not necessary have to be external, if one of the # other classes did define the requested plugin we'll use that # one. # First, try the plugin in this invocant class. eval "use $plugin"; unless ( _loaded($plugin) ) { # Try to load generic plugin. $plugin = __PACKAGE__ . "::Plugin::" . $type; $plugin =~ s/::::/::/g; eval "use $plugin"; } croak("Unsupported type (Cannot load plug-in for \"$type\")\n$@") unless _loaded($plugin); # Return the plugin instance. # The constructor gets all args passed, including 'type'. $plugin->new($args); } sub _loaded { my $class = shift; no strict "refs"; %{$class . "::"} ? 1 : 0; }
# End of Data::Report