Added new tests, fixed a bug, and cleaned up more code: Intermediate check-in
[p5sagit/Excel-Template.git] / lib / Excel / Template.pm
CommitLineData
d0eafc11 1package Excel::Template;
2
3use strict;
4
5BEGIN {
6 use Excel::Template::Base;
7 use vars qw ($VERSION @ISA);
8
d01e4722 9 $VERSION = '0.24';
d0eafc11 10 @ISA = qw( Excel::Template::Base );
11}
12
13use File::Basename;
14use XML::Parser;
d0eafc11 15use IO::Scalar;
16
6dd4c89d 17use constant RENDER_NML => 'normal';
18use constant RENDER_BIG => 'big';
19use constant RENDER_XML => 'xml';
20
21my %renderers = (
22 RENDER_NML, 'Spreadsheet::WriteExcel',
23 RENDER_BIG, 'Spreadsheet::WriteExcel::Big',
24 RENDER_XML, 'Spreadsheet::WriteExcelXML',
25);
26
d0eafc11 27sub new
28{
29 my $class = shift;
30 my $self = $class->SUPER::new(@_);
31
a084e413 32 $self->{FILE} = $self->{FILENAME}
33 if !defined $self->{FILE} && defined $self->{FILENAME};
34
35 $self->parse_xml($self->{FILE})
36 if defined $self->{FILE};
d0eafc11 37
38 my @renderer_classes = ( 'Spreadsheet::WriteExcel' );
6dd4c89d 39
40 if (exists $self->{RENDERER} && $self->{RENDERER})
d0eafc11 41 {
6dd4c89d 42 if (exists $renderers{ lc $self->{RENDERER} })
43 {
44 unshift @renderer_classes, $renderers{ lc $self->{RENDERER} };
45 }
46 elsif ($^W)
47 {
48 warn "'$self->{RENDERER}' is not recognized\n";
49 }
d0eafc11 50 }
6dd4c89d 51 elsif (exists $self->{BIG_FILE} && $self->{BIG_FILE})
9d172425 52 {
6dd4c89d 53 warn "Use of BIG_FILE is deprecated.\n";
54 unshift @renderer_classes, 'Spreadsheet::WriteExcel::Big';
9d172425 55 }
56
d0eafc11 57 $self->{RENDERER} = undef;
58 foreach my $class (@renderer_classes)
59 {
60 (my $filename = $class) =~ s!::!/!g;
61 eval {
62 require "$filename.pm";
63 $class->import;
64 };
65 if ($@) {
dee1f239 66 warn "Could not find or compile '$class'\n" if $^W;
d0eafc11 67 } else {
68 $self->{RENDERER} = $class;
69 last;
70 }
71 }
72
73 defined $self->{RENDERER} ||
74 die "Could not find a renderer class. Tried:\n\t" .
75 join("\n\t", @renderer_classes) .
76 "\n";
77
8c63e224 78 $self->{USE_UNICODE} = ~~0
79 if $] >= 5.008;
80
d0eafc11 81 return $self;
82}
83
84sub param
85{
86 my $self = shift;
87
88 # Allow an arbitrary number of hashrefs, so long as they're the first things # into param(). Put each one onto the end, de-referenced.
d01e4722 89 push @_, %{shift @_} while ref $_[0] eq 'HASH';
d0eafc11 90
91 (@_ % 2)
92 && die __PACKAGE__, "->param() : Odd number of parameters to param()\n";
93
94 my %params = @_;
95 $params{uc $_} = delete $params{$_} for keys %params;
96 @{$self->{PARAM_MAP}}{keys %params} = @params{keys %params};
97
8c63e224 98 return ~~1;
d0eafc11 99}
100
101sub write_file
102{
103 my $self = shift;
104 my ($filename) = @_;
105
106 my $xls = $self->{RENDERER}->new($filename)
107 || die "Cannot create XLS in '$filename': $!\n";
108
d01e4722 109 eval {
110 $self->_prepare_output($xls);
111 };
d0eafc11 112
113 $xls->close;
114
d01e4722 115 return if $@;
116
8c63e224 117 return ~~1;
d0eafc11 118}
119
120sub output
121{
122 my $self = shift;
123
124 my $output;
125 tie *XLS, 'IO::Scalar', \$output;
126
d01e4722 127 $self->write_file(\*XLS)
128 or return;
d0eafc11 129
130 return $output;
131}
132
d0eafc11 133sub parse_xml
134{
135 my $self = shift;
a084e413 136 my ($file) = @_;
d0eafc11 137
d0eafc11 138 my @stack;
a084e413 139 my @parms = (
d0eafc11 140 Handlers => {
141 Start => sub {
142 shift;
143
144 my $name = uc shift;
145
6dd4c89d 146 my $node = Excel::Template::Factory->_create_node($name, @_);
d0eafc11 147 die "'$name' (@_) didn't make a node!\n" unless defined $node;
148
37513eae 149 if ( $node->isa( 'WORKBOOK' ) )
d0eafc11 150 {
d01e4722 151 $self->{WORKBOOK} = $node;
d0eafc11 152 }
37513eae 153 elsif ( $node->is_embedded )
d0eafc11 154 {
155 return unless @stack;
156
157 if (exists $stack[-1]{TXTOBJ} &&
158 $stack[-1]{TXTOBJ}->isa('TEXTOBJECT'))
159 {
160 push @{$stack[-1]{TXTOBJ}{STACK}}, $node;
161 }
162
163 }
164 else
165 {
166 push @{$stack[-1]{ELEMENTS}}, $node
167 if @stack;
168 }
169 push @stack, $node;
170 },
171 Char => sub {
172 shift;
173 return unless @stack;
174
175 my $parent = $stack[-1];
176
177 if (
178 exists $parent->{TXTOBJ}
179 &&
180 $parent->{TXTOBJ}->isa('TEXTOBJECT')
181 ) {
182 push @{$parent->{TXTOBJ}{STACK}}, @_;
183 }
184 },
185 End => sub {
186 shift;
187 return unless @stack;
188
189 pop @stack if $stack[-1]->isa(uc $_[0]);
190 },
191 },
192 );
193
a084e413 194 if ( ref $file )
195 {
196 *INFILE = $file;
197 }
198 else
d0eafc11 199 {
a084e413 200 my ($filename, $dirname) = fileparse($file);
201
202 push @parms, Base => $dirname;
203
204 open( INFILE, "<$file" )
205 || die "Cannot open '$file' for reading: $!\n";
206
207 }
d0eafc11 208
a084e413 209 my $parser = XML::Parser->new( @parms );
210 $parser->parse(do { local $/ = undef; <INFILE> });
d0eafc11 211
d01e4722 212 close INFILE
213 unless ref $file;
d0eafc11 214
8ca5e726 215 return ~~1;
d0eafc11 216}
d01e4722 217*parse = *parse = \&parse_xml;
d0eafc11 218
219sub _prepare_output
220{
221 my $self = shift;
d01e4722 222 return unless $self->{WORKBOOK};
223
d0eafc11 224 my ($xls) = @_;
225
6dd4c89d 226 my $context = Excel::Template::Factory->_create(
d0eafc11 227 'CONTEXT',
228
229 XLS => $xls,
230 PARAM_MAP => [ $self->{PARAM_MAP} ],
8c63e224 231 UNICODE => $self->{UNICODE},
d0eafc11 232 );
233
d01e4722 234 $self->{WORKBOOK}->render($context);
d0eafc11 235
8ca5e726 236 return ~~1;
d0eafc11 237}
238
e976988f 239sub register { shift; Excel::Template::Factory->register(@_) }
d0eafc11 240
2411;
242__END__
243
244=head1 NAME
245
246Excel::Template - Excel::Template
247
248=head1 SYNOPSIS
249
250First, make a template. This is an XML file, describing the layout of the
251spreadsheet.
252
253For example, test.xml:
254
255 <workbook>
256 <worksheet name="tester">
e976988f 257 <cell text="$HOME" />
258 <cell text="$PATH" />
d0eafc11 259 </worksheet>
260 </workbook>
261
262Now, create a small program to use it:
263
264 #!/usr/bin/perl -w
e976988f 265
266 use strict;
267
9d172425 268 use Excel::Template;
d0eafc11 269
270 # Create the Excel template
271 my $template = Excel::Template->new(
272 filename => 'test.xml',
273 );
274
275 # Add a few parameters
276 $template->param(
277 HOME => $ENV{HOME},
278 PATH => $ENV{PATH},
279 );
280
281 $template->write_file('test.xls');
282
e976988f 283If everything worked, then you should have a spreadsheet called text.xls in your working directory that looks something like:
d0eafc11 284
285 A B C
286 +----------------+----------------+----------------
287 1 | /home/me | /bin:/usr/bin |
288 +----------------+----------------+----------------
289 2 | | |
290 +----------------+----------------+----------------
291 3 | | |
292
293=head1 DESCRIPTION
294
e976988f 295This is a module used for templating Excel files. Its genesis came from the need to use the same datastructure as L<HTML::Template>, but provide Excel files instead. The existing modules don't do the trick, as they require replication of logic that's already been done within L<HTML::Template>.
d0eafc11 296
d0eafc11 297=head1 MOTIVATION
298
e976988f 299I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been, I've been asked for HTML, PDF, and Excel. L<HTML::Template> provides the first, and L<PDF::Template> does the second pretty well. But, generating Excel was the sticking point. I already had the data structure for the other templating modules, but I just didn't have an easy mechanism to get that data structure into an XLS file.
d0eafc11 300
301=head1 USAGE
302
303=head2 new()
304
a084e413 305This creates a Excel::Template object.
d0eafc11 306
6dd4c89d 307=head3 Parameters
308
309=over 4
310
a084e413 311=item * FILE / FILENAME
312
313Excel::Template will parse the template in the given file or filehandle automatically. (You can also use the parse() method, described below.)
314
315If you want to use the __DATA__ section, you can do so by passing
316
317 FILE => \*DATA
318
6dd4c89d 319=item * RENDERER
320
e976988f 321The default rendering engine is L<Spreadsheet::WriteExcel>. You may, if you choose, change that to another choice. The legal values are:
6dd4c89d 322
323=over 4
324
325=item * Excel::Template->RENDER_NML
326
e976988f 327This is the default of L<Spreadsheet::WriteExcel>.
8c63e224 328
6dd4c89d 329=item * Excel::Template->RENDER_BIG
330
e976988f 331This attempts to load L<Spreadsheet::WriteExcel::Big>.
6dd4c89d 332
333=item * Excel::Template->RENDER_XML
334
e976988f 335This attempts to load L<Spreadsheet::WriteExcelXML>.
6dd4c89d 336
337=back
338
339=item * USE_UNICODE
340
341This will use L<Unicode::String> to represent strings instead of Perl's internal string handling. You must already have L<Unicode::String> installed on your system.
8c63e224 342
e976988f 343The USE_UNICODE parameter will be ignored if you are using Perl 5.8 or higher as Perl's internal string handling is unicode-aware.
8ca5e726 344
e976988f 345NOTE: Certain older versions of L<OLE::Storage_Lite> and mod_perl clash for some reason. Upgrading to the latest version of L<OLE::Storage_Lite> should fix the problem.
8ca5e726 346
6dd4c89d 347=back
348
349=head3 Deprecated
350
351=over 4
352
353=item * BIG_FILE
354
355Instead, use RENDERER => Excel::Template->RENDER_BIG
356
357=back
358
d0eafc11 359=head2 param()
360
8ca5e726 361This method is exactly like L<HTML::Template>'s param() method.
d0eafc11 362
363=head2 parse() / parse_xml()
364
a084e413 365This method actually parses the template file. It can either be called separately or through the new() call. It will die() if it runs into a situation it cannot handle.
366
e976988f 367If a filename is passed in (vs. a filehandle), the directory name will be passed in to L<XML::Parser> as the I<Base> parameter. This will allow for XML directives to work as expected.
d0eafc11 368
369=head2 write_file()
370
e976988f 371Create the Excel file and write it to the specified filename, if possible. (This is when the actual merging of the template and the parameters occurs.)
d0eafc11 372
373=head2 output()
374
e976988f 375It will act just like L<HTML::Template>'s output() method, returning the resultant file as a stream, usually for output to the web. (This is when the actual merging of the template and the parameters occurs.)
d0eafc11 376
6dd4c89d 377=head2 register()
378
379This allows you to register a class as handling a node. q.v. L<Excel::Template::Factory> for more info.
380
d0eafc11 381=head1 SUPPORTED NODES
382
e976988f 383This is a partial list of nodes. See the other classes in this distro for more details on specific parameters and the like.
d0eafc11 384
e976988f 385Every node can set the ROW and COL parameters. These are the actual ROW/COL values that the next CELL-type tag will write into.
d0eafc11 386
387=over 4
388
8ca5e726 389=item * L<WORKBOOK|Excel::Template::Container::Workbook>
390
e976988f 391This is the node representing the workbook. It is the parent for all other nodes.
8ca5e726 392
393=item * L<WORKSHEET|Excel::Template::Container::Worksheet>
394
395This is the node representing a given worksheet.
396
397=item * L<IF|Excel::Template::Container::Conditional>
398
e976988f 399This node represents a conditional expression. Its children may or may not be rendered. It behaves just like L<HTML::Template>'s TMPL_IF.
8ca5e726 400
401=item * L<LOOP|Excel::Template::Container::Loop>
402
403This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP.
404
405=item * L<ROW|Excel::Template::Container::Row>
406
407This node represents a row of data. This is the A in A1.
408
409=item * L<FORMAT|Excel::Template::Container::Format>
410
e976988f 411This node varies the format for its children. All formatting options supported in L<Spreadsheet::WriteExcel> are supported here. There are also a number of formatting shortcuts, such as L<BOLD|Excel::Template::Container::Bold> and L<ITALIC|Excel::Template::Container::Italic>.
8ca5e726 412
413=item * L<BACKREF|Excel::Template::Element::Backref>
414
415This refers back to a cell previously named.
d0eafc11 416
8ca5e726 417=item * L<CELL|Excel::Template::Element::Cell>
d0eafc11 418
8ca5e726 419This is the actual cell in a spreadsheet.
d0eafc11 420
8ca5e726 421=item * L<FORMULA|Excel::Template::Element::Formula>
d0eafc11 422
8ca5e726 423This is a formula in a spreadsheet.
d0eafc11 424
8ca5e726 425=item * L<RANGE|Excel::Template::Element::Range>
d0eafc11 426
8ca5e726 427This is a BACKREF for a number of identically-named cells.
d0eafc11 428
8ca5e726 429=item * L<VAR|Excel::Template::Element::Var>
d0eafc11 430
8ca5e726 431This is a variable. It is generally used when the 'text' attribute isn't
432sufficient.
d0eafc11 433
6dd4c89d 434=back
d0eafc11 435
436=head1 BUGS
437
438None, that I know of.
439
440=head1 SUPPORT
441
e976988f 442This is production quality software, used in several production web applications.
d0eafc11 443
444=head1 AUTHOR
445
8ca5e726 446 Rob Kinyon (rob.kinyon@gmail.com)
a8441e01 447
448=head1 CONTRIBUTORS
449
c11fa570 450There is a mailing list at http://groups.google.com/group/ExcelTemplate or exceltemplate@googlegroups.com
d0eafc11 451
c11fa570 452=head2 Robert Graff
37513eae 453
454=over 4
455
456=item * Finishing formats
457
458=item * Fixing several bugs in worksheet naming
459
6dd4c89d 460=back
37513eae 461
c11fa570 462=head1 TEST COVERAGE
463
e976988f 464I used L<Devel::Cover> to test the coverage of my tests. Every release, I intend to improve these numbers.
c11fa570 465
466Excel::Template is also part of the CPAN Kwalitee initiative, being one of the top 100 non-core modules downloaded from CPAN. If you wish to help out, please feel free to contribute tests, patches, and/or suggestions.
467
e976988f 468 -----------------------------------------------------------------------
469 File stmt brnch cond sub pod time total
470 -----------------------------------------------------------------------
471 blib/lib/Excel/Template.pm 96.0 62.5 58.8 100.0 100.0 25.2 86.2
472 ...ib/Excel/Template/Base.pm 87.0 50.0 66.7 81.8 87.5 8.7 83.0
473 ...cel/Template/Container.pm 71.4 50.0 33.3 70.0 80.0 4.9 68.4
474 ...emplate/Container/Bold.pm 100.0 n/a n/a 100.0 0.0 0.7 95.0
475 .../Container/Conditional.pm 64.9 57.5 66.7 100.0 0.0 0.7 63.9
476 ...plate/Container/Format.pm 100.0 n/a n/a 100.0 0.0 0.7 96.6
477 ...plate/Container/Hidden.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0
478 ...plate/Container/Italic.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0
479 ...plate/Container/Locked.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0
480 ...emplate/Container/Loop.pm 90.9 50.0 50.0 100.0 50.0 0.5 80.4
481 ...late/Container/Outline.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0
482 ...Template/Container/Row.pm 100.0 75.0 n/a 100.0 50.0 0.3 93.8
483 ...mplate/Container/Scope.pm 100.0 n/a n/a 100.0 n/a 0.1 100.0
484 ...plate/Container/Shadow.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0
485 ...te/Container/Strikeout.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0
486 ...ate/Container/Workbook.pm 100.0 n/a n/a 100.0 n/a 0.9 100.0
487 ...te/Container/Worksheet.pm 94.1 50.0 n/a 100.0 0.0 0.9 88.0
488 ...Excel/Template/Context.pm 84.3 53.4 54.2 100.0 92.3 19.5 76.0
489 ...Excel/Template/Element.pm 100.0 n/a n/a 100.0 n/a 0.5 100.0
490 ...mplate/Element/Backref.pm 100.0 50.0 33.3 100.0 0.0 0.1 87.1
491 .../Template/Element/Cell.pm 95.8 65.0 80.0 100.0 66.7 3.8 86.9
492 ...mplate/Element/Formula.pm 100.0 n/a n/a 100.0 0.0 0.3 94.1
493 ...Template/Element/Range.pm 100.0 66.7 n/a 100.0 66.7 0.2 93.3
494 ...l/Template/Element/Var.pm 100.0 n/a n/a 100.0 0.0 0.1 94.1
495 ...Excel/Template/Factory.pm 100.0 73.1 n/a 100.0 100.0 16.3 92.6
496 .../Excel/Template/Format.pm 98.3 81.2 33.3 100.0 100.0 10.0 93.2
497 ...xcel/Template/Iterator.pm 98.6 80.0 70.6 100.0 83.3 1.9 90.3
498 ...el/Template/TextObject.pm 92.9 62.5 33.3 100.0 50.0 3.1 83.0
499 Total 92.0 63.5 58.3 97.5 98.5 100.0 86.0
500 -----------------------------------------------------------------------
c11fa570 501
d0eafc11 502=head1 COPYRIGHT
503
e976988f 504This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
d0eafc11 505
e976988f 506The full text of the license can be found in the LICENSE file included with this module.
d0eafc11 507
508=head1 SEE ALSO
509
e976988f 510perl(1), L<HTML::Template>, L<Spreadsheet::WriteExcel>
d0eafc11 511
512=cut