File Coverage

lib/DBIx/ReportBuilder.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             # $File: //member/autrijus/DBIx-ReportBuilder/lib/DBIx/ReportBuilder.pm $ $Author: autrijus $
2             # $Revision: #42 $ $Change: 8942 $ $DateTime: 2003/11/18 18:08:05 $
3              
4             package DBIx::ReportBuilder;
5             $DBIx::ReportBuilder::VERSION = '0.00_15';
6              
7 1     1   1741 use strict;
  1         2  
  1         53  
8 1     1   6 no warnings 'redefine';
  1         1  
  1         43  
9              
10 1     1   5 use base 'XML::Twig';
  1         1  
  1         903  
11             use base 'Exporter';
12              
13             =head1 NAME
14              
15             DBIx::ReportBuilder - Interactive SQL report generator
16              
17             =head1 VERSION
18              
19             This document describes version 0.00_15 of DBIx::ReportBuilder, released
20             November 19, 2003.
21              
22             =head1 SYNOPSIS
23              
24             use DBIx::ReportBuilder;
25             my $obj = DBIx::ReportBuilder->new(
26             Driver => 'mysql',
27             Host => 'localhost',
28             User => 'rt_user',
29             Password => 'rt_pass',
30             Database => 'rt3',
31             );
32              
33             $obj->PartInsertP( text => "My Test Report" );
34             $obj->PartInsertTable( table => 'users', text => 'User List' );
35              
36             $obj->ClauseInsertLimit( field => 'id', operator => '<', value => 20 );
37             $obj->ClauseInsertCell( field => 'id', text => 'Id' );
38             $obj->ClauseInsertCell( field => 'name', text => 'Name' );
39              
40             $obj->ClauseUp; # move up; switch Name and Id
41             $obj->ClauseDown; # move down; switch Name and Id back
42             $obj->ClauseRemove; # delete the current clause
43              
44             print $obj->RenderHTML; # prints a HTML rendered document
45             print $obj->RenderEdit; # prints an interactive Web UI
46             print $obj->RenderPDF; # prints PDF (not yet)
47              
48             =head1 DESCRIPTION
49              
50             This module is a subclass of B, specially tailored to render
51             SQL reports in various formats, based on B.
52              
53             Its API is designed to interact with users via B's
54             Web UI, which can incrementally construct complex reports.
55              
56             =head1 NOTES
57              
58             This is B code. Until the official release of B,
59             using this module for anything (except for learning purporses) is strongly
60             discouraged.
61              
62             For more details on how to use this module, see the F file in
63             the source distribution.
64              
65             =head1 METHODS
66              
67             =cut
68              
69             use constant Sections => qw( preamble header content footer postamble );
70             use constant Parts => qw( p img table graph include );
71             use constant Clauses => qw( join limit groupby orderby cell );
72             use constant Parameters => qw( name handle trigger clause_id part_id );
73             use constant Callbacks => qw( loc search_hook describe_report render_report );
74             use constant BaseClass => __PACKAGE__;
75              
76             our $AUTOLOAD;
77             our @EXPORT_OK = qw( Sections Parts Clauses BaseClass Atts Att ucase lcase encode_src );
78             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
79              
80             sub ucase {
81             my $self = shift;
82             return join('', map ucfirst, split(/_/, +shift));
83             }
84              
85             sub lcase {
86             my $self = shift;
87             my $text = lcfirst(+shift);
88             $text =~ s/([A-Z])/_\l$1/g;
89             return $text;
90             }
91              
92             BEGIN {
93             no strict 'refs';
94             foreach my $item (Parameters, Callbacks) {
95             my $accessor = BaseClass->ucase($item);
96             *{"$accessor"} = sub { $_[0]->{$item} };
97             *{"Set$accessor"} = sub { $_[0]->{$item} = $_[1] };
98             # alias for "HandleObj", etc
99             *{"${accessor}Obj"} = sub { $_[0]->{$item} };
100             *{"Set${accessor}Obj"} = sub { $_[0]->{$item} = $_[1] };
101             }
102             foreach my $item (Callbacks) {
103             *{"$item"} = sub {
104             my $code = +shift->{$item} or return;
105             return $code->(@_);
106             };
107             }
108             }
109              
110             =head2 new(%args)
111              
112             Constructor. Takes either a C object as the
113             named C<$Handle> parameter, or a set of parameters for
114             C.
115              
116             Also takes an optional C<$Content> parameter to parse; if unspecified, use
117             the default blank content structure.
118              
119             =cut
120              
121             sub new {
122             my $class = shift;
123             my %args = @_;
124             my $self = $class->SUPER::new(
125             twig_handlers => {
126             (map { $_ => sub { $_->set_id($_[0]->NextSection) } } $class->Sections),
127             (map { $_ => sub { $_->set_id($_[0]->NextPart) } } $class->Parts),
128             (map { $_ => sub { $_->set_id($_[0]->NextClause) } } $class->Clauses),
129             },
130             pretty_print => 'indented_c',
131             );
132             $self->SetLoc( $args{Loc} || sub { $_[0] } );
133             $self->SetDescribeReport( $args{DescribeReport} || sub { "#$_[0]" } );
134             $self->SetRenderReport( $args{RenderReport} || sub { "
#$_[0]
" } );
135             $self->SetHandle( $args{Handle} || $self->NewHandle( %args ) );
136             $self->SetName( $args{Name} || $self->loc('(new)') );
137             $self->Parse( $args{Content} );
138             $self->SetPartId( $args{PartId} || 0);
139             $self->SetClauseId( $args{ClauseId} || 0 );
140             $self->ResetCounts;
141             return $self;
142             }
143              
144             sub Parse {
145             my $self = shift;
146             $self->SUPER::parse( defined($_[0]) ? $_[0] : $self->NewContent );
147             }
148              
149             sub NewContent {
150             my $self = shift;
151              
152             my $obj = $self->BaseClass->SUPER::new( @_ );
153             $obj->parse(
154             ''.
155             ''
156             );
157              
158             my $root = $obj->root;
159             my $head = $root->insert_new_elt( last_child => 'head' );
160             $head->insert_new_elt( last_child => 'meta', { name => $_, auto => 1 } )
161             foreach $self->VarObj->Vars;
162              
163             my $body = $root->insert_new_elt( last_child => 'body' );
164             $body->insert_new_elt( last_child => $_ )->insert_new_elt( 'p' )
165             foreach $self->Sections;
166              
167             $head->set_att(orientation => 'portrait');
168             $head->set_att(paper => 'A4');
169             foreach my $key (map "margin_$_", qw/top bottom left right/) {
170             $head->set_att($key => '200');
171             }
172              
173             return $obj->sprint;
174             }
175              
176             sub NewHandle {
177             my $self = shift;
178              
179             require DBIx::SearchBuilder::Handle;
180             my $obj = DBIx::SearchBuilder::Handle->new;
181             $obj->Connect( DisconnectHandleOnDestroy => 1, @_ ) or die $! if @_;
182              
183             return $obj;
184             }
185              
186             sub GraphObj {
187             my $self = shift;
188             return eval { $self->spawn(Graph => @_) };
189             }
190              
191             sub SearchObj {
192             my ($self, %args) = @_;
193             $args{Handle} ||= $self->Handle or return;
194             return eval { $self->spawn(Search => %args) };
195             }
196              
197             sub RenderObj {
198             my $self = shift;
199             my $type = shift || 'HTML';
200             return $self->spawn("Render::$type" => ( Object => $self ) );
201             }
202              
203             sub Render {
204             my $self = shift;
205             return $self->RenderObj(@_)->Render;
206             }
207              
208             sub Reload {
209             my $self = shift;
210             $self->Parse($self->sprint);
211             $self->ResetCounts;
212             $self->VarObj->Reload;
213             return $self;
214             }
215              
216             sub Recount {
217             my $self = shift;
218             my $root = $self->root;
219              
220             $_->set_id($self->NextSection) for sort { $a->cmp($b) }
221             map { $root->descendants($_) } $self->Sections;
222             $_->set_id($self->NextPart) for sort { $a->cmp($b) }
223             map { $root->descendants($_) } $self->Parts;
224             $_->set_id($self->NextClause) for sort { $a->cmp($b) }
225             map { $root->descendants($_) } $self->Clauses;
226              
227             $self->ResetCounts;
228              
229             return $self->PartId unless wantarray;
230             return ($self->PartId, $self->ClauseId);
231             }
232              
233             sub Section { +shift->_do(Section => @_) }
234             sub Part { +shift->_do(Part => @_) }
235             sub Clause { +shift->_do(Clause => @_) }
236              
237             sub SectionObj { +shift->_obj(Section => @_) }
238             sub PartObj { +shift->_obj(Part => @_) }
239             sub ClauseObj { +shift->_obj(Clause => @_) }
240              
241             sub NextSection { 'Section' . ++$_[0]{next_section} }
242             sub NextPart { 'Part' . ++$_[0]{next_part} }
243             sub NextClause { 'Clause' . ++$_[0]{next_clause} }
244             sub ResetCounts { @{$_[0]}{$_} = 0 for qw(next_section next_part next_clause) }
245              
246             sub SectionId {
247             my $self = shift;
248             return $self->PartObj->parent->pos;
249             }
250              
251             sub SetSectionId {
252             my $self = shift;
253             my $id = shift;
254              
255             $self->SetPartId(
256             $self->root->first_child('body')
257             ->first_child((Sections)[$id-1])
258             ->first_child->id
259             ) unless ($self->SectionId == $id);
260              
261             return $id;
262             }
263              
264             sub ClauseId {
265             my $self = shift;
266             $self->{clause_id} or return 0;
267             my $obj = $self->ClauseObj($self->{clause_id}) or return 0;
268              
269             # if clause_id no longer belong into part, adjust it to 0
270             $self->{clause_id} = 0
271             if eval { $obj->parent->parent->id ne $self->PartObj->id };
272              
273             return $self->{clause_id};
274             }
275              
276             sub SetPartId {
277             my ($self, $id) = @_;
278             $id ||= $self->root->first_child('body')
279             ->first_child('content')->first_child->id;
280             $id =~ s/^Part//;
281             $self->{part_id} = $id;
282             }
283              
284             sub Atts {
285             my ($self, $tag) = @_;
286             $tag ||= $self->tag if $self->can('tag');
287             return BaseClass->spawn('Attribute', Tag => $tag)->Attributes;
288             }
289              
290             sub Att {
291             my ($self, $att, $tag) = @_;
292             $tag ||= $self->tag if $self->can('tag');
293             return BaseClass->spawn(
294             'Attribute',
295             Object => $self, Att => $att, Tag => $tag,
296             );
297             }
298              
299             sub VarObj {
300             my $self = shift;
301             my $var = $self->lcase(+shift);
302             return BaseClass->spawn(
303             'Variable',
304             Object => $self, Var => $var,
305             );
306             }
307              
308             sub Vars {
309             my $self = shift;
310             return map $self->ucase($_), $self->VarObj->Vars;
311             }
312              
313             sub Var { +shift->VarObj(+shift)->Value }
314             sub VarDefault { +shift->VarObj(+shift)->DefaultValue }
315             sub VarDescription { +shift->VarObj(+shift)->Description }
316             sub RemoveVar { +shift->VarObj(+shift)->Remove(@_) }
317             sub SetVar { +shift->VarObj(+shift)->SetValue(@_) }
318             sub SetVarDefault { +shift->VarObj(+shift)->SetDefaultValue(@_) }
319             sub SetVarDescription { +shift->VarObj(+shift)->SetDescription(@_) }
320              
321             sub VarInsert {
322             my ($self, $var) = @_;
323             my $obj = $self->ClauseObj || $self->PartObj or return;
324             return unless $obj->Atts->[-1] eq 'text';
325             $obj->insert_new_elt( last_child => 'var', { name => $self->lcase($var) } );
326             return $obj->Id;
327             }
328              
329             sub _do {
330             my $self = shift;
331             my $class = shift;
332             my $Op = shift or return;
333              
334             my $getObj = "${class}Obj";
335             my $getId = "${class}Id";
336             my $setId = "Set${class}Id";
337              
338             my $obj = $self->$getObj($self->$getId) || $self->spawn($class);
339             $obj->can($Op) or return;
340              
341             my $rv = $obj->$Op(@_, Object => $self);
342             $self->Recount if defined($rv);
343             if (ref($rv)) {
344             $rv = substr($rv->id, length($class));
345             }
346             else {
347             $rv = $self->$getId + ($rv || 0);
348             }
349             return $self->$setId( $rv );
350             }
351              
352             sub _obj {
353             my $self = shift;
354             my $class = shift;
355             my $getId = "${class}Id";
356             my $elt = $self->elt_id($class . (shift || $self->$getId)) or return;
357             return $self->spawn($class => $elt);
358             }
359              
360             sub spawn {
361             my $class = shift;
362             my $subclass = shift;
363             my $pkg = $class = $class->BaseClass . "::$subclass";
364             $pkg =~ s{::}{/}g;
365             require "$pkg.pm";
366             return $class->new(@_);
367             }
368              
369             sub encode_src {
370             require MIME::Base64;
371             return "data:image/png;base64,".MIME::Base64::encode_base64($_[1]);
372             };
373              
374             sub DESTROY {}
375              
376             # PartInsertTable
377              
378             sub AUTOLOAD {
379             no warnings 'uninitialized';
380              
381             my $self = shift;
382             $AUTOLOAD =~ /\b(VarInsert|Part|Clause|Render)(\w+?)(Obj)?$/
383             or die "Undefined subroutine $AUTOLOAD";
384              
385             my $method = $1 . $3;
386             my $op = $2;
387             my $tag = $2 if ($method ne 'VarInsert')
388             and ($op =~ s/^([A-Z][a-z]+)((?:[A-Z][a-z]*)+)$/$1/);
389             $self->$method($op => ($tag ? (tag => lc($tag)) : ()), @_);
390             }
391              
392             1;
393              
394             =head1 SEE ALSO
395              
396             L, L, L
397              
398             =head1 AUTHORS
399              
400             Autrijus Tang Eautrijus@autrijus.orgE
401              
402             =head1 COPYRIGHT
403              
404             Copyright 2003 by Autrijus Tang Eautrijus@autrijus.orgE.
405              
406             This program is free software; you can redistribute it and/or
407             modify it under the same terms as Perl itself.
408              
409             See L
410              
411             =cut