File Coverage

blib/lib/PDF/ReportWriter/Report.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             # vim: ts=8 sw=8 tw=0 ai nu noet
2             #
3             # PDF::ReportWriter::Report
4             #
5             # (C) Daniel Kasak: dan@entropy.homelinux.org ...
6             # ... with contributions from Bill Hess and Cosimo Streppone
7             # ( see the changelog for details )
8             #
9             # See COPYRIGHT file for full license
10             #
11             # See 'perldoc PDF::ReportWriter::Report',
12             # 'perldoc PDF::ReportWriter' for full documentation
13             #
14              
15             package PDF::ReportWriter::Report;
16 2     2   2670 use strict;
  2         4  
  2         73  
17 2     2   836 use XML::Simple;
  0            
  0            
18             use PDF::ReportWriter::Datasource;
19              
20             BEGIN {
21             $PDF::ReportWriter::Report::VERSION = '1.0';
22             }
23              
24             sub new {
25            
26             my ( $class, $opt ) = @_;
27             $class = ref($class) || $class;
28            
29             # Make $opt a hashref if it isn't already
30             # Passing a string with report filename should work.
31             if( ! ref $opt ) {
32             if( ( defined $opt ) && ( $opt ne '' ) ) {
33             $opt = { report => $opt };
34             } else {
35             $opt = {};
36             }
37             }
38            
39             my $self = { %$opt };
40             my $file;
41            
42             if( exists $self->{report} ) {
43             $file = $self->{report};
44             }
45            
46             if( defined $file && ! -e $file )
47             {
48             return(undef);
49             }
50            
51             bless $self, $class;
52            
53             }
54              
55             #
56             # Returns current filename of xml report
57             #
58             sub file {
59             my $self = $_[0];
60             return $self->{report};
61             }
62              
63             #
64             # Loads xml report definition
65             #
66             sub load {
67             my $self = shift;
68             my $file = shift || $self->file();
69             my $cfg = $self->config();
70            
71             # Return already loaded configuration instead of
72             # reloading from scratch
73             return $cfg if $cfg;
74            
75             my $xml = XML::Simple->new(
76            
77             # Don't create group name keys
78             #
79             # FIXME Is there a way to avoid `name' hash keys creation,
80             # without issuing warnings about nonexistent `_' key?
81            
82             KeyAttr => {
83             group => '_',
84             field => '_',
85             },
86            
87             GroupTags => {
88             header => 'cell',
89             footer => 'cell',
90             groups => 'group',
91             fields => 'field',
92             },
93            
94             Variables => $self->get_macros(),
95            
96             );
97            
98             $cfg = $xml->XMLin( $file );
99            
100             if( ! ref( $cfg ) )
101             {
102             warn qq(Can't read from xml file $file);
103             return ( undef );
104             }
105            
106             $self->_adjust_struct( $cfg );
107            
108             # Store configuration inside object
109             $self->config( $cfg );
110            
111             return ( $cfg );
112            
113             }
114              
115             #
116             # Returns all available data sources found in xml file
117             # There should be one `detail' data source at least
118             #
119             sub data_sources
120             {
121             my $self = $_[0];
122             my $ds = $self->config->{data}->{datasource};
123             my %ds;
124            
125             if ( ref $ds eq 'HASH' ) {
126             $ds = [ $ds ];
127             }
128            
129             for( @$ds )
130             {
131             $ds{$_->{name}} = $_;
132             }
133            
134             return(\%ds);
135             }
136              
137             #
138             # Report's get_data() passes the work to PDF::ReportWriter::Datasource objects
139             #
140             {
141             # Main cache for datasources actual data, to avoid
142             # useless repeated calls to get_data()
143             my %ds_cache;
144            
145             sub get_data
146             {
147             my $self = $_[0];
148             my $dsname = $_[1] || 'detail';
149            
150             # Check if cached data already exists
151             # Here we assume that while report is generating, data does not change
152             if( exists $ds_cache{$dsname} )
153             {
154             return $ds_cache{$dsname};
155             }
156            
157             # Get all available d.s.
158             my $ds = $self->data_sources();
159            
160             # Datasource does not exists, return blank data
161             if( ! exists $ds->{$dsname} )
162             {
163             #warn 'Data source '.$dsname.' does not exist';
164             return ();
165             }
166            
167             # Try to create a P::R::Datasource object
168             my $ds_obj = PDF::ReportWriter::Datasource->new($ds->{$dsname});
169            
170             if( ! defined $ds_obj )
171             {
172             warn 'Data source '.$dsname.' not available!';
173             return ();
174             }
175            
176             # Ok, datasource object loaded, call get_data() on it
177             return ( $ds_cache{$dsname} = $ds_obj->get_data($self) );
178             }
179            
180             }
181              
182             #
183             # Default implementation for get_macros() returns nothing.
184             # Get variables should be used for search&replace in the XML file.
185             # before loading.
186             #
187             sub get_macros {
188             return undef;
189             }
190              
191             #
192             # Returns (or modifies) current report configuration (profile)
193             #
194             sub config {
195             my $self = shift;
196            
197             # If passed a parameter, change config member to that
198             if( @_ ) {
199             $self->{_config} = $_[0];
200             }
201            
202             return ( $self->{_config} );
203             }
204              
205             #
206             # Sanity checks and adjustments on data structures
207             #
208             # TODO 1: here XML::Simple does magic but not enough to simply
209             # pass the data structure plainly as read.
210             #
211             # TODO 2: Should we avoid at all this thing?
212             # Probably modifying PDF::ReportWriter to handle
213             # all the different cases (HASH, ARRAY, ...)
214             #
215             sub _adjust_struct {
216            
217             my ( $self, $config ) = @_;
218             my $data = $config->{data};
219             local $_;
220            
221             # Force `fields' to be an array even with 1 element
222             if( ref ( $data->{fields} ) eq 'HASH' ) {
223             $data->{fields} = [ $data->{fields} ];
224             }
225            
226             if( ref ( $data->{groups} ) eq 'HASH' ) {
227             $data->{groups} = [ $data->{groups} ];
228             }
229            
230             # Now for `groups' section
231             for(@{ $data->{groups} }) {
232            
233             # Remove empty header/footer sections
234             # Force header/footer to array even if they have 1 element
235             if( ref ( $_->{header} ) eq 'HASH' ) {
236             if( keys %{$_->{header}} ) {
237             $_->{header} = [ $_->{header} ];
238             } else {
239             delete $_->{header};
240             }
241             }
242            
243             if( ref ( $_->{footer} ) eq 'HASH' ) {
244             if( keys %{$_->{footer}} ) {
245             $_->{footer} = [ $_->{footer} ];
246             } else {
247             delete $_->{footer};
248             }
249             }
250             }
251            
252             # Same as above for `page' structure
253             my $page = $data->{page};
254             if( ref ( $page->{header} ) eq 'HASH' ) {
255             $page->{header} = [ $page->{header} ];
256             }
257             if( ref ( $page->{footer} ) eq 'HASH' ) {
258             $page->{footer} = [ $page->{footer} ];
259             }
260            
261             # Same as above for font structure
262             if( ! ref $config->{definition}->{font} )
263             {
264             $config->{definition}->{font} = [ $config->{definition}->{font} ];
265             }
266            
267             return ( $config );
268             }
269              
270             #
271             # ->save( \%config [, filename] )
272             #
273             # Saves the report back to XML format.
274             # This is mainly an experiment. Don't know if it works correctly,
275             # even if the test suite seems to say so.
276             #
277             # %config = (
278             #
279             # definition => {
280             # ...,
281             # info => { ... },
282             # ...,
283             # },
284             #
285             # page => {
286             # header => [ ],
287             # footer => [ ],
288             # },
289             #
290             # data => {
291             # fields => ...,
292             # groups => ...,
293             # ...
294             # },
295             # )
296             #
297             sub save
298             {
299            
300             my($self, $cfg, $file) = @_;
301            
302             # $cfg is a hash:
303             # $cfg = {
304             # data ... the 'data' part of the PDF::ReportWriter object
305             # definition ... the top-level part of the PDF::ReportWriter object ( minus data )
306            
307             $file ||= $self->file();
308            
309             my $xml = XML::Simple->new(
310             AttrIndent => 1,
311             ForceArray => 1,
312             KeepRoot => 1,
313             NoAttr => 1,
314             NoSort => 1,
315             RootName => 'report',
316             XMLDecl => 1,
317             );
318            
319             my $xml_stream = $xml->XMLout($cfg);
320             my $ok = 0;
321            
322             #warn 'opening file '.$file;
323            
324             if( open(XML_REPORT, '>' . $file) )
325             {
326             #warn 'opened file';
327             $ok = print XML_REPORT $xml_stream;
328             #warn 'printed '.$ok.' on it';
329             $ok &&= close(XML_REPORT);
330             #warn 'closed '.$ok;
331             }
332            
333             # Report saved?
334             return($ok);
335            
336             }
337              
338              
339             1;
340              
341             =head1 NAME
342              
343             PDF::ReportWriter::Report
344              
345             =head1 DESCRIPTION
346              
347             PDF::ReportWriter::Report is a PDF::ReportWriter class that represents a single report.
348             It handles the conversions from/to XML to PDF::ReportWriter correct data structures,
349             and can provide the data to be used in the report.
350             XML::Simple module is used for data structures serialization to XML and restore.
351              
352             This class is designed in a way that should be simple to be overloaded,
353             and thus provide alternative classes that load reports in a totally different way,
354             or supply data connecting automatically to a DBI DSN, or who knows...
355              
356             =head1 USAGE
357              
358             The most useful usage for this class is through the C
359             call. If you really want an example of usage of standalone Report object, here it is:
360              
361             # Create a blank report object
362             my $report = PDF::ReportWriter::Report->new();
363             my $config;
364              
365             # Load XML report definition
366             eval { $config = $report->load('/home/cosimo/myreport.xml') };
367             if( $@ ) {
368             # Incorrect xml file!
369             print 'Error in XML report:', $@, "\n";
370             }
371              
372             # Now save the report object to xml file
373             my $ok = $report->save($config);
374             my $ok = $report->save($config, 'Copy of report.xml');
375              
376             =head1 METHODS
377              
378             =head2 new( options )
379              
380             Creates a new C object. C is a hash reference.
381             Its only required key is C, which is the xml filename of the report definition.
382             It is stored inside the object, allowing to later B your report in that filename.
383              
384             =head2 get_data( ds_name )
385              
386             The only parameter required is the datasource name C.
387             If no data is supplied to the C call,
388             this method checks for all available data sources defined in your xml
389             report. They must be included in the C section.
390             Check out the examples.
391              
392             The main data source that provides the data for report main table must
393             be called C, or you get an empty report.
394             Additional data sources can be defined, as in the following (fake) example:
395              
396            
397             ...
398            
399             ...
400            
401             192.168.0.1
402             389
403             o=Users,dc=domain,dc=com
404             cn=DirectoryManager,dc=domain,dc=com
405             secret
406            
407             ...
408            
409             ...
410            
411              
412             =head2 get_macros()
413              
414             Should be used to return all text macros that must be searched and replaced inside
415             the XML content before converting it into the C profile.
416             Example:
417              
418             ...
419            
420            
421            
422             My Report
423            
424             ${AUTHOR}
425             ...
426              
427             A corresponding C method should return:
428              
429             sub get_macros {
430             return { 'AUTHOR' => 'Isaac Asimov' };
431             }
432              
433             The default implementation returns no macro.
434              
435             =head2 load( [xml_file] )
436              
437             Loads the report definition from C. No, don't be afraid! This is a friendly
438             and nice xml file, not those ugly monsters that populate JavaLand. :-)
439             Return value is an hashref with complete report profile.
440              
441             my $report = PDF::ReportWriter::Report->new();
442             my $profile = $report->load('myreport.xml');
443             if( ! $profile ) {
444             print "Something wrong in the XML?";
445             }
446              
447             =head2 save( config [, xml_file ] )
448              
449             Saves the report profile passed in C parameter (as a hashref)
450             to the file defined in C or to C if supplied.
451              
452             The result won't be exactly equal to the source xml file, but should
453             be equivalent when loading the data structures to build your final report.
454              
455             =head1 CUSTOM REPORT CLASSES
456              
457             The design of C allows one to build a custom class
458             that provides alternative behavior for C and C methods.
459              
460             C method can do anything, but it must return a complete report
461             data structure to be fed into C object. That consists
462             into several hashrefs:
463              
464             =over *
465              
466             =item definition
467              
468             All high-level report properties, such as C,
469             C, ...
470              
471             =item page
472              
473             Page header and footer list of cells. See xml report samples in
474             the C folder.
475              
476             =item data
477              
478             The main section which defines C and C.
479             Check out the examples and use Data::Dumper on results
480             of C method. Sorry. :-)
481              
482             =back
483              
484             =head1 TODO
485              
486             =over *
487              
488             =item Complete documentation?
489              
490             =back
491              
492             =head1 AUTHORS
493              
494             =over 4
495              
496             =item Dan
497              
498             =item Cosimo Streppone
499              
500             =back
501              
502             =head1 Other cool things you should know about:
503              
504             =over 4
505              
506             This module is part of an umbrella project, 'Axis Not Evil', which aims to make
507             Rapid Application Development of database apps using open-source tools a reality.
508             The project includes:
509              
510             Gtk2::Ex::DBI - forms
511              
512             Gtk2::Ex::Datasheet::DBI - datasheets
513              
514             PDF::ReportWriter - reports
515              
516             All the above modules are available via cpan, or for more information, screenshots, etc, see:
517             http://entropy.homelinux.org/axis_not_evil
518              
519             =back
520              
521             =cut