Added new tests, fixed a bug, and cleaned up more code: Intermediate check-in
[p5sagit/Excel-Template.git] / lib / Excel / Template.pm
1 package Excel::Template;
2
3 use strict;
4
5 BEGIN {
6     use Excel::Template::Base;
7     use vars qw ($VERSION @ISA);
8
9     $VERSION  = '0.24';
10     @ISA      = qw( Excel::Template::Base );
11 }
12
13 use File::Basename;
14 use XML::Parser;
15 use IO::Scalar;
16
17 use constant RENDER_NML => 'normal';
18 use constant RENDER_BIG => 'big';
19 use constant RENDER_XML => 'xml';
20
21 my %renderers = (
22     RENDER_NML, 'Spreadsheet::WriteExcel',
23     RENDER_BIG, 'Spreadsheet::WriteExcel::Big',
24     RENDER_XML, 'Spreadsheet::WriteExcelXML',
25 );
26
27 sub new
28 {
29     my $class = shift;
30     my $self = $class->SUPER::new(@_);
31
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};
37
38     my @renderer_classes = ( 'Spreadsheet::WriteExcel' );
39
40     if (exists $self->{RENDERER} && $self->{RENDERER})
41     {
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         }
50     }
51     elsif (exists $self->{BIG_FILE} && $self->{BIG_FILE})
52     {
53         warn "Use of BIG_FILE is deprecated.\n";
54         unshift @renderer_classes, 'Spreadsheet::WriteExcel::Big';
55     }
56
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 ($@) {
66             warn "Could not find or compile '$class'\n" if $^W;
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
78     $self->{USE_UNICODE} = ~~0
79         if $] >= 5.008;
80
81     return $self;
82 }
83
84 sub 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.
89     push @_, %{shift @_} while ref $_[0] eq 'HASH';
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
98     return ~~1;
99 }
100
101 sub 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
109     eval {
110         $self->_prepare_output($xls);
111     };
112
113     $xls->close;
114
115     return if $@;
116
117     return ~~1;
118 }
119
120 sub output
121 {
122     my $self = shift;
123
124     my $output;
125     tie *XLS, 'IO::Scalar', \$output;
126
127     $self->write_file(\*XLS)
128         or return;
129
130     return $output;
131 }
132
133 sub parse_xml
134 {
135     my $self = shift;
136     my ($file) = @_;
137
138     my @stack;
139     my @parms = (
140         Handlers => {
141             Start => sub {
142                 shift;
143
144                 my $name = uc shift;
145
146                 my $node = Excel::Template::Factory->_create_node($name, @_);
147                 die "'$name' (@_) didn't make a node!\n" unless defined $node;
148
149                 if ( $node->isa( 'WORKBOOK' ) )
150                 {
151                     $self->{WORKBOOK} = $node;
152                 }
153                 elsif ( $node->is_embedded )
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
194     if ( ref $file )
195     {
196         *INFILE = $file;
197     }
198     else
199     {
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     }
208
209     my $parser = XML::Parser->new( @parms );
210     $parser->parse(do { local $/ = undef; <INFILE> });
211
212     close INFILE
213         unless ref $file;
214
215     return ~~1;
216 }
217 *parse = *parse = \&parse_xml;
218
219 sub _prepare_output
220 {
221     my $self = shift;
222     return unless $self->{WORKBOOK};
223
224     my ($xls) = @_;
225
226     my $context = Excel::Template::Factory->_create(
227         'CONTEXT',
228
229         XLS       => $xls,
230         PARAM_MAP => [ $self->{PARAM_MAP} ],
231         UNICODE   => $self->{UNICODE},
232     );
233
234     $self->{WORKBOOK}->render($context);
235
236     return ~~1;
237 }
238
239 sub register { shift; Excel::Template::Factory->register(@_) }
240
241 1;
242 __END__
243
244 =head1 NAME
245
246 Excel::Template - Excel::Template
247
248 =head1 SYNOPSIS
249
250 First, make a template. This is an XML file, describing the layout of the
251 spreadsheet.
252
253 For example, test.xml:
254
255   <workbook>
256       <worksheet name="tester">
257           <cell text="$HOME" />
258           <cell text="$PATH" />
259       </worksheet>
260   </workbook>
261
262 Now, create a small program to use it:
263
264   #!/usr/bin/perl -w
265
266   use strict;
267
268   use Excel::Template;
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
283 If everything worked, then you should have a spreadsheet called text.xls in your working directory that looks something like:
284
285              A                B                C
286     +----------------+----------------+----------------
287   1 | /home/me       | /bin:/usr/bin  |
288     +----------------+----------------+----------------
289   2 |                |                |
290     +----------------+----------------+----------------
291   3 |                |                |
292
293 =head1 DESCRIPTION
294
295 This 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>.
296
297 =head1 MOTIVATION
298
299 I 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.
300
301 =head1 USAGE
302
303 =head2 new()
304
305 This creates a Excel::Template object.
306
307 =head3 Parameters
308
309 =over 4
310
311 =item * FILE / FILENAME
312
313 Excel::Template will parse the template in the given file or filehandle automatically. (You can also use the parse() method, described below.)
314
315 If you want to use the __DATA__ section, you can do so by passing
316
317   FILE => \*DATA
318
319 =item * RENDERER
320
321 The default rendering engine is L<Spreadsheet::WriteExcel>. You may, if you choose, change that to another choice. The legal values are:
322
323 =over 4
324
325 =item * Excel::Template->RENDER_NML
326
327 This is the default of L<Spreadsheet::WriteExcel>.
328
329 =item * Excel::Template->RENDER_BIG
330
331 This attempts to load L<Spreadsheet::WriteExcel::Big>.
332
333 =item * Excel::Template->RENDER_XML
334
335 This attempts to load L<Spreadsheet::WriteExcelXML>.
336
337 =back
338
339 =item * USE_UNICODE
340
341 This 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.
342
343 The USE_UNICODE parameter will be ignored if you are using Perl 5.8 or higher as Perl's internal string handling is unicode-aware.
344
345 NOTE: 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.
346
347 =back
348
349 =head3 Deprecated
350
351 =over 4
352
353 =item * BIG_FILE
354
355 Instead, use RENDERER => Excel::Template->RENDER_BIG
356
357 =back
358
359 =head2 param()
360
361 This method is exactly like L<HTML::Template>'s param() method.
362
363 =head2 parse() / parse_xml()
364
365 This 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
367 If 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.
368
369 =head2 write_file()
370
371 Create 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.)
372
373 =head2 output()
374
375 It 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.)
376
377 =head2 register()
378
379 This allows you to register a class as handling a node. q.v. L<Excel::Template::Factory> for more info.
380
381 =head1 SUPPORTED NODES
382
383 This is a partial list of nodes. See the other classes in this distro for more details on specific parameters and the like.
384
385 Every node can set the ROW and COL parameters. These are the actual ROW/COL values that the next CELL-type tag will write into.
386
387 =over 4
388
389 =item * L<WORKBOOK|Excel::Template::Container::Workbook>
390
391 This is the node representing the workbook. It is the parent for all other nodes.
392
393 =item * L<WORKSHEET|Excel::Template::Container::Worksheet>
394
395 This is the node representing a given worksheet.
396
397 =item * L<IF|Excel::Template::Container::Conditional>
398
399 This node represents a conditional expression. Its children may or may not be rendered. It behaves just like L<HTML::Template>'s TMPL_IF.
400
401 =item * L<LOOP|Excel::Template::Container::Loop>
402
403 This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP.
404
405 =item * L<ROW|Excel::Template::Container::Row>
406
407 This node represents a row of data. This is the A in A1.
408
409 =item * L<FORMAT|Excel::Template::Container::Format>
410
411 This 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>.
412
413 =item * L<BACKREF|Excel::Template::Element::Backref>
414
415 This refers back to a cell previously named.
416
417 =item * L<CELL|Excel::Template::Element::Cell>
418
419 This is the actual cell in a spreadsheet.
420
421 =item * L<FORMULA|Excel::Template::Element::Formula>
422
423 This is a formula in a spreadsheet.
424
425 =item * L<RANGE|Excel::Template::Element::Range>
426
427 This is a BACKREF for a number of identically-named cells.
428
429 =item * L<VAR|Excel::Template::Element::Var>
430
431 This is a variable. It is generally used when the 'text' attribute isn't
432 sufficient.
433
434 =back
435
436 =head1 BUGS
437
438 None, that I know of.
439
440 =head1 SUPPORT
441
442 This is production quality software, used in several production web applications.
443
444 =head1 AUTHOR
445
446     Rob Kinyon (rob.kinyon@gmail.com)
447
448 =head1 CONTRIBUTORS
449
450 There is a mailing list at http://groups.google.com/group/ExcelTemplate or exceltemplate@googlegroups.com
451
452 =head2 Robert Graff
453
454 =over 4
455
456 =item * Finishing formats
457
458 =item * Fixing several bugs in worksheet naming
459
460 =back
461
462 =head1 TEST COVERAGE
463
464 I used L<Devel::Cover> to test the coverage of my tests. Every release, I intend to improve these numbers.
465
466 Excel::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
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   -----------------------------------------------------------------------
501
502 =head1 COPYRIGHT
503
504 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
505
506 The full text of the license can be found in the LICENSE file included with this module.
507
508 =head1 SEE ALSO
509
510 perl(1), L<HTML::Template>, L<Spreadsheet::WriteExcel>
511
512 =cut