File Coverage

blib/lib/HTML/Table/FromDatabase.pm
Criterion Covered Total %
statement 18 115 15.6
branch 0 56 0.0
condition 0 27 0.0
subroutine 6 12 50.0
pod 1 1 100.0
total 25 211 11.8


line stmt bran cond sub pod time code
1             package HTML::Table::FromDatabase;
2              
3 2     2   63711 use 5.005000;
  2         10  
  2         91  
4 2     2   15 use strict;
  2         4  
  2         86  
5 2     2   24 no warnings 'uninitialized';
  2         18  
  2         84  
6 2     2   28 use base qw(HTML::Table);
  2         4  
  2         3807  
7 2     2   57864 use vars qw($VERSION);
  2         34  
  2         118  
8 2     2   11 use HTML::Table;
  2         4  
  2         2381  
9              
10             $VERSION = '1.10';
11              
12             # $Id$
13              
14             =head1 NAME
15              
16             HTML::Table::FromDatabase - a subclass of HTML::Table to easily generate a HTML table from the result of a database query
17              
18             =head1 SYNOPSIS
19              
20             my $sth = $dbh->prepare('select * from my_table')
21             or die "Failed to prepare query - " . $dbh->errstr;
22             $sth->execute() or die "Failed to execute query - " . $dbh->errstr;
23              
24             my $table = HTML::Table::FromDatabase->new( -sth => $sth );
25             $table->print;
26              
27             =head1 DESCRIPTION
28              
29             Subclasses L, providing a quick and easy way to produce HTML
30             tables from the result of a database query.
31              
32             I often find myself writing scripts which fetch data from a database and
33             present it in a HTML table; often resulting in pointlessly repeated code
34             to take the results and turn them into a table.
35              
36             L itself helps here, but this module makes it even simpler.
37              
38             Column headings are taken from the field names returned by the query, unless
39             overridden with the I<-override_headers> or I<-rename_headers> options.
40              
41             All options you pass to the constructor will be passed through to HTML::Table,
42             so you can use all the usual L features.
43              
44              
45             =head1 INTERFACE
46              
47             =over 4
48              
49             =item new
50              
51             Constructor method - consult L's documentation, the only
52             difference here is the addition of the following parameters:
53              
54             =over 4
55              
56             =item C<-sth>
57              
58             (required) a DBI statement handle which has been executed and is ready
59             to fetch data from
60              
61             =item C<-callbacks>
62              
63             (optional) specifies callbacks/transformations which should be applied as the
64             table is built up (see the L section below).
65              
66             =item C<-html>
67              
68             (optional) can be I or I if you want HTML to be escaped
69             (angle brackets replaced with < and >) or stripped out with HTML::Strip.
70              
71             =item C<-override_headers>
72              
73             (optional) provide a list of names to be used as the column headings, instead of
74             using the names of the columns returned by the SQL query. This should be an
75             arrayref containing the heading names, and the number of heading names must
76             match the number of columns returned by the query.
77              
78             =item C<-rename_headers>
79              
80             (optional) provide a hashref of oldname => newname pairs to rename some or all
81             of the column names returned by the query when generating the table headings.
82              
83             =item C<-auto_pretty_headers>
84              
85             (optional, boolean) - automatically make column names nicer for headings,
86             using titlecase and swapping underscores for spaces etc (e.g. C
87             becomes C)
88              
89             =item C<-pad_empty_cells>
90              
91             (optional, default 1) pad empty cells with an C< > to ensure they're
92             rendered with borders appropriately. Many browsers "skip" empty cells, leading
93             to missing borders around them, which many people consider broken. To stop
94             this, by default empty cells receive a non-breaking space as their content. If
95             you don't want this behaviour, set this option to a false value.
96              
97             =back
98              
99             =cut
100              
101             sub new {
102 0     0 1   my $class = shift;
103            
104 0           my %flags = @_;
105 0           my $sth = delete $flags{-sth};
106            
107 0 0 0       if (!$sth || !ref $sth || !$sth->isa('DBI::st')) {
      0        
108 0           warn "HTML::Table::FromDatabase->new requires the -sth argument,"
109             ." which must be a valid DBI statement handle.";
110 0           return;
111             }
112              
113 0           my $callbacks = delete $flags{-callbacks};
114 0 0 0       if ($callbacks && ref $callbacks ne 'ARRAY') {
115 0           warn "Unrecognised -callbacks parameter; "
116             ."expected a arrayref of hashrefs";
117 0           return;
118             }
119              
120 0           my $row_callbacks = delete $flags{-row_callbacks};
121 0 0 0       if ($row_callbacks && ref $row_callbacks ne 'ARRAY') {
122 0           warn "Unrecognised -row_callbacks parameter; "
123             . "expected an arrayref of coderefs";
124 0           return;
125             }
126              
127 0           my $override_headers = delete $flags{-override_headers};
128 0 0 0       if ($override_headers && ref $override_headers ne 'ARRAY') {
129 0           warn "Unrecognised -override_headers parameter; "
130             ."expected an arrayref";
131 0           return;
132             }
133              
134 0           my $rename_headers = delete $flags{-rename_headers};
135 0 0 0       if ($rename_headers && ref $rename_headers ne 'HASH') {
136 0           warn "Unrecognised -rename_headers parameter; "
137             ."expected a hashref";
138 0           return;
139             }
140              
141 0 0         $flags{-pad_empty_cells} = 1 unless exists $flags{-pad_empty_cells};
142              
143 0           my $auto_pretty_headers = delete $flags{-auto_pretty_headers};
144              
145              
146             # if we're going to encode or escape HTML, prepare to do so:
147 0           my $preprocessor;
148 0 0         if (my $handle_html = delete $flags{-html}) {
149 0 0 0       if ($handle_html eq 'strip') {
    0          
150 0           eval "require HTML::Strip;";
151 0 0         if ($@) {
152 0           warn "Failed to load HTML::Strip - cannot strip HTML";
153 0           return;
154             }
155 0           my $hs = new HTML::Strip;
156 0     0     $preprocessor = sub { $hs->eof; return $hs->parse(shift) };
  0            
  0            
157             } elsif ($handle_html eq 'encode' || $handle_html eq 'escape') {
158 0           eval "require HTML::Entities;";
159 0 0         if ($@) {
160 0           warn "Failed to load HTML::Entities - cannot encode HTML";
161 0           return;
162             }
163 0     0     $preprocessor = sub { HTML::Entities::encode_entities(shift); };
  0            
164             } else {
165 0           warn "Unrecognised -html option.";
166 0           return;
167             }
168             }
169            
170             # Create a HTML::Table object, passing along any other options we were
171             # given:
172 0           my $self = HTML::Table->new(%flags);
173            
174             # Find the names;
175 0           my @columns = @{ $sth->{NAME} };
  0            
176              
177             # Default to using the column names as headings, unless we've been given
178             # an -override_headers or -rename_headers option (if we got the
179             # -auto_pretty_headers option, prettify them somewhat):
180 0           my @heading_names = @columns;
181 0           for (@heading_names) {
182 0 0         if (exists $rename_headers->{$_}) {
    0          
183 0           $_ = $rename_headers->{$_};
184             } elsif ($auto_pretty_headers) {
185 0           $_ = _prettify($_);
186             }
187             }
188              
189 0 0         if ($override_headers) {
190 0 0         if (@$override_headers != @heading_names) {
191 0           warn "Incorrect number of header names in -override_headers option"
192             ." - got " . @$override_headers . ", needed " . @heading_names;
193             }
194 0           @heading_names = @$override_headers;
195             }
196            
197 0           $self->addSectionRow('thead', 0, @heading_names);
198 0           $self->setSectionRowHead('thead', 0, 1);
199            
200             # Add all the rows:
201             row:
202 0           while (my $row = $sth->fetchrow_hashref) {
203             # First, if there are any row callbacks, call them:
204 0           for my $callback (@$row_callbacks) {
205 0           $callback->($row);
206             }
207              
208             # If the callback undefined $row, we should skip it:
209 0 0         next row if !defined $row;
210              
211             # Now, work through each field
212 0           my @fields;
213 0           for my $column (@columns) {
214 0           my $value = $row->{$column};
215              
216 0 0         if ($preprocessor) {
217 0           $value = $preprocessor->($value);
218             }
219              
220              
221             # If we have a callback to perform for this field, do it:
222 0           for my $callback (@$callbacks) {
223             # See what we need to match against, and if it matches, call
224             # the specified transform callback to potentially change the
225             # value.
226 0 0         if (exists $callback->{column}) {
227 0 0         if (_callback_matches($callback->{column}, $column)) {
228 0           $value = _perform_callback(
229             $callback, $column, $value, $row
230             );
231             }
232             }
233 0 0         if (exists $callback->{value}) {
234 0 0         if (_callback_matches($callback->{value}, $value)) {
235 0           $value = _perform_callback(
236             $callback, $column, $value, $row
237             );
238             }
239             }
240             }
241              
242             # If the value is empty, turn it into a non-breaking space to make
243             # the cell still display correctly (otherwise it looks ugly):
244 0 0 0       $value = ' ' if $value eq '' && $flags{-pad_empty_cells};
245            
246             # Add this field to the list to deal with:
247 0           push @fields, $value;
248             }
249            
250 0           $self->addRow(@fields);
251             }
252            
253             # All done, re-bless into our class and return
254 0           bless $self, $class;
255 0           return $self;
256             };
257              
258             # Abstract out the different kind of matches (regexp, coderef or straight
259             # scalar)
260             sub _callback_matches {
261 0     0     my ($match, $against) = @_;
262 0 0         if (ref $match eq 'Regexp') {
    0          
    0          
263 0           return $against =~ /$match/;
264             } elsif (ref $match eq 'CODE') {
265 0           return $match->($against);
266             } elsif (ref $match) {
267             # A reference to something we don't understand:
268 0           warn "Unrecognised callback match [$match]";
269 0           return;
270             } else {
271             # Must be a straight scalar
272 0           return $match eq $against;
273             }
274             }
275              
276             # A callback spec matched, so perform any callback it requests, and apply
277             # any transformation it described:
278             sub _perform_callback {
279 0     0     my ($callback, $column, $value,$row) = @_;
280              
281             # Firstly, if there's a callback to perform, we call it (but don't
282             # care what it returns):
283 0 0 0       if (exists $callback->{callback} and ref $callback->{callback} eq 'CODE')
284             {
285 0           $callback->{callback}->($value, $row);
286             }
287              
288             # Now, look for a transformation we might have to perform:
289 0 0         if (!exists $callback->{transform}) {
290             # We don't have a transform to perform, so just return the value
291             # unchanged:
292 0           return $value;
293             }
294 0 0         if (ref $callback->{transform} ne 'CODE') {
295 0           warn "Unrecognised transform action";
296 0           return $value;
297             }
298              
299             # OK, apply the transformation to the value:
300 0           return $callback->{transform}->($value, $row);
301             }
302              
303             sub _prettify {
304 0     0     s{_}{ }g; s{\b(\w)}{\u$1}g; $_;
  0            
  0            
305             }
306              
307             1;
308             __END__;