Improved tests and documentation. Fixed a few bugs in register()
[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.23';
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 UNIVERSAL::isa($_[0], '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     $self->_prepare_output($xls);
110
111     $xls->close;
112
113     return ~~1;
114 }
115
116 sub output
117 {
118     my $self = shift;
119
120     my $output;
121     tie *XLS, 'IO::Scalar', \$output;
122
123     $self->write_file(\*XLS);
124
125     return $output;
126 }
127
128 sub parse_xml
129 {
130     my $self = shift;
131     my ($file) = @_;
132
133     my @stack;
134     my @parms = (
135         Handlers => {
136             Start => sub {
137                 shift;
138
139                 my $name = uc shift;
140
141                 my $node = Excel::Template::Factory->_create_node($name, @_);
142                 die "'$name' (@_) didn't make a node!\n" unless defined $node;
143
144                 if ( $node->isa( 'WORKBOOK' ) )
145                 {
146                     push @{$self->{WORKBOOKS}}, $node;
147                 }
148                 elsif ( $node->is_embedded )
149                 {
150                     return unless @stack;
151                                                                                 
152                     if (exists $stack[-1]{TXTOBJ} &&
153                         $stack[-1]{TXTOBJ}->isa('TEXTOBJECT'))
154                     {
155                         push @{$stack[-1]{TXTOBJ}{STACK}}, $node;
156                     }
157  
158                 }
159                 else
160                 {
161                     push @{$stack[-1]{ELEMENTS}}, $node
162                         if @stack;
163                 }
164                 push @stack, $node;
165             },
166             Char => sub {
167                 shift;
168                 return unless @stack;
169
170                 my $parent = $stack[-1];
171
172                 if (
173                     exists $parent->{TXTOBJ}
174                         &&
175                     $parent->{TXTOBJ}->isa('TEXTOBJECT')
176                 ) {
177                     push @{$parent->{TXTOBJ}{STACK}}, @_;
178                 }
179             },
180             End => sub {
181                 shift;
182                 return unless @stack;
183
184                 pop @stack if $stack[-1]->isa(uc $_[0]);
185             },
186         },
187     );
188
189     if ( ref $file )
190     {
191         *INFILE = $file;
192     }
193     else
194     {
195         my ($filename, $dirname) = fileparse($file);
196  
197         push @parms, Base => $dirname;
198
199         open( INFILE, "<$file" )
200             || die "Cannot open '$file' for reading: $!\n";
201
202     }
203
204     my $parser = XML::Parser->new( @parms );
205     $parser->parse(do { local $/ = undef; <INFILE> });
206
207     unless ( ref $file )
208     {
209         close INFILE;
210     }
211
212     return ~~1;
213 }
214 *parse = \&parse_xml;
215
216 sub _prepare_output
217 {
218     my $self = shift;
219     my ($xls) = @_;
220
221     my $context = Excel::Template::Factory->_create(
222         'CONTEXT',
223
224         XLS       => $xls,
225         PARAM_MAP => [ $self->{PARAM_MAP} ],
226         UNICODE   => $self->{UNICODE},
227     );
228
229     $_->render($context) for @{$self->{WORKBOOKS}};
230
231     return ~~1;
232 }
233
234 sub register { shift; Excel::Template::Factory->register(@_) }
235
236 1;
237 __END__
238
239 =head1 NAME
240
241 Excel::Template - Excel::Template
242
243 =head1 SYNOPSIS
244
245 First, make a template. This is an XML file, describing the layout of the
246 spreadsheet.
247
248 For example, test.xml:
249
250   <workbook>
251       <worksheet name="tester">
252           <cell text="$HOME" />
253           <cell text="$PATH" />
254       </worksheet>
255   </workbook>
256
257 Now, create a small program to use it:
258
259   #!/usr/bin/perl -w
260
261   use strict;
262
263   use Excel::Template;
264
265   # Create the Excel template
266   my $template = Excel::Template->new(
267       filename => 'test.xml',
268   );
269
270   # Add a few parameters
271   $template->param(
272       HOME => $ENV{HOME},
273       PATH => $ENV{PATH},
274   );
275
276   $template->write_file('test.xls');
277
278 If everything worked, then you should have a spreadsheet called text.xls in your working directory that looks something like:
279
280              A                B                C
281     +----------------+----------------+----------------
282   1 | /home/me       | /bin:/usr/bin  |
283     +----------------+----------------+----------------
284   2 |                |                |
285     +----------------+----------------+----------------
286   3 |                |                |
287
288 =head1 DESCRIPTION
289
290 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>.
291
292 =head1 MOTIVATION
293
294 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.
295
296 =head1 USAGE
297
298 =head2 new()
299
300 This creates a Excel::Template object.
301
302 =head3 Parameters
303
304 =over 4
305
306 =item * FILE / FILENAME
307
308 Excel::Template will parse the template in the given file or filehandle automatically. (You can also use the parse() method, described below.)
309
310 If you want to use the __DATA__ section, you can do so by passing
311
312   FILE => \*DATA
313
314 =item * RENDERER
315
316 The default rendering engine is L<Spreadsheet::WriteExcel>. You may, if you choose, change that to another choice. The legal values are:
317
318 =over 4
319
320 =item * Excel::Template->RENDER_NML
321
322 This is the default of L<Spreadsheet::WriteExcel>.
323
324 =item * Excel::Template->RENDER_BIG
325
326 This attempts to load L<Spreadsheet::WriteExcel::Big>.
327
328 =item * Excel::Template->RENDER_XML
329
330 This attempts to load L<Spreadsheet::WriteExcelXML>.
331
332 =back
333
334 =item * USE_UNICODE
335
336 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.
337
338 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.
339
340 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.
341
342 =back
343
344 =head3 Deprecated
345
346 =over 4
347
348 =item * BIG_FILE
349
350 Instead, use RENDERER => Excel::Template->RENDER_BIG
351
352 =back
353
354 =head2 param()
355
356 This method is exactly like L<HTML::Template>'s param() method.
357
358 =head2 parse() / parse_xml()
359
360 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.
361
362 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.
363
364 =head2 write_file()
365
366 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.)
367
368 =head2 output()
369
370 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.)
371
372 =head2 register()
373
374 This allows you to register a class as handling a node. q.v. L<Excel::Template::Factory> for more info.
375
376 =head1 SUPPORTED NODES
377
378 This is a partial list of nodes. See the other classes in this distro for more details on specific parameters and the like.
379
380 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.
381
382 =over 4
383
384 =item * L<WORKBOOK|Excel::Template::Container::Workbook>
385
386 This is the node representing the workbook. It is the parent for all other nodes.
387
388 =item * L<WORKSHEET|Excel::Template::Container::Worksheet>
389
390 This is the node representing a given worksheet.
391
392 =item * L<IF|Excel::Template::Container::Conditional>
393
394 This node represents a conditional expression. Its children may or may not be rendered. It behaves just like L<HTML::Template>'s TMPL_IF.
395
396 =item * L<LOOP|Excel::Template::Container::Loop>
397
398 This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP.
399
400 =item * L<ROW|Excel::Template::Container::Row>
401
402 This node represents a row of data. This is the A in A1.
403
404 =item * L<FORMAT|Excel::Template::Container::Format>
405
406 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>.
407
408 =item * L<BACKREF|Excel::Template::Element::Backref>
409
410 This refers back to a cell previously named.
411
412 =item * L<CELL|Excel::Template::Element::Cell>
413
414 This is the actual cell in a spreadsheet.
415
416 =item * L<FORMULA|Excel::Template::Element::Formula>
417
418 This is a formula in a spreadsheet.
419
420 =item * L<RANGE|Excel::Template::Element::Range>
421
422 This is a BACKREF for a number of identically-named cells.
423
424 =item * L<VAR|Excel::Template::Element::Var>
425
426 This is a variable. It is generally used when the 'text' attribute isn't
427 sufficient.
428
429 =back
430
431 =head1 BUGS
432
433 None, that I know of.
434
435 =head1 SUPPORT
436
437 This is production quality software, used in several production web applications.
438
439 =head1 AUTHOR
440
441     Rob Kinyon (rob.kinyon@gmail.com)
442
443 =head1 CONTRIBUTORS
444
445 There is a mailing list at http://groups.google.com/group/ExcelTemplate or exceltemplate@googlegroups.com
446
447 =head2 Robert Graff
448
449 =over 4
450
451 =item * Finishing formats
452
453 =item * Fixing several bugs in worksheet naming
454
455 =back
456
457 =head1 TEST COVERAGE
458
459 I used L<Devel::Cover> to test the coverage of my tests. Every release, I intend to improve these numbers.
460
461 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.
462
463   -----------------------------------------------------------------------
464   File                           stmt brnch  cond   sub   pod  time total
465   -----------------------------------------------------------------------
466   blib/lib/Excel/Template.pm     96.0  62.5  58.8 100.0 100.0  25.2  86.2
467   ...ib/Excel/Template/Base.pm   87.0  50.0  66.7  81.8  87.5   8.7  83.0
468   ...cel/Template/Container.pm   71.4  50.0  33.3  70.0  80.0   4.9  68.4
469   ...emplate/Container/Bold.pm  100.0   n/a   n/a 100.0   0.0   0.7  95.0
470   .../Container/Conditional.pm   64.9  57.5  66.7 100.0   0.0   0.7  63.9
471   ...plate/Container/Format.pm  100.0   n/a   n/a 100.0   0.0   0.7  96.6
472   ...plate/Container/Hidden.pm  100.0   n/a   n/a 100.0   0.0   0.1  95.0
473   ...plate/Container/Italic.pm  100.0   n/a   n/a 100.0   0.0   0.1  95.0
474   ...plate/Container/Locked.pm  100.0   n/a   n/a 100.0   0.0   0.1  95.0
475   ...emplate/Container/Loop.pm   90.9  50.0  50.0 100.0  50.0   0.5  80.4
476   ...late/Container/Outline.pm  100.0   n/a   n/a 100.0   0.0   0.1  95.0
477   ...Template/Container/Row.pm  100.0  75.0   n/a 100.0  50.0   0.3  93.8
478   ...mplate/Container/Scope.pm  100.0   n/a   n/a 100.0   n/a   0.1 100.0
479   ...plate/Container/Shadow.pm  100.0   n/a   n/a 100.0   0.0   0.1  95.0
480   ...te/Container/Strikeout.pm  100.0   n/a   n/a 100.0   0.0   0.1  95.0
481   ...ate/Container/Workbook.pm  100.0   n/a   n/a 100.0   n/a   0.9 100.0
482   ...te/Container/Worksheet.pm   94.1  50.0   n/a 100.0   0.0   0.9  88.0
483   ...Excel/Template/Context.pm   84.3  53.4  54.2 100.0  92.3  19.5  76.0
484   ...Excel/Template/Element.pm  100.0   n/a   n/a 100.0   n/a   0.5 100.0
485   ...mplate/Element/Backref.pm  100.0  50.0  33.3 100.0   0.0   0.1  87.1
486   .../Template/Element/Cell.pm   95.8  65.0  80.0 100.0  66.7   3.8  86.9
487   ...mplate/Element/Formula.pm  100.0   n/a   n/a 100.0   0.0   0.3  94.1
488   ...Template/Element/Range.pm  100.0  66.7   n/a 100.0  66.7   0.2  93.3
489   ...l/Template/Element/Var.pm  100.0   n/a   n/a 100.0   0.0   0.1  94.1
490   ...Excel/Template/Factory.pm  100.0  73.1   n/a 100.0 100.0  16.3  92.6
491   .../Excel/Template/Format.pm   98.3  81.2  33.3 100.0 100.0  10.0  93.2
492   ...xcel/Template/Iterator.pm   98.6  80.0  70.6 100.0  83.3   1.9  90.3
493   ...el/Template/TextObject.pm   92.9  62.5  33.3 100.0  50.0   3.1  83.0
494   Total                          92.0  63.5  58.3  97.5  98.5 100.0  86.0
495   -----------------------------------------------------------------------
496
497 =head1 COPYRIGHT
498
499 This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
500
501 The full text of the license can be found in the LICENSE file included with this module.
502
503 =head1 SEE ALSO
504
505 perl(1), L<HTML::Template>, L<Spreadsheet::WriteExcel>
506
507 =cut