Intermediate commit
[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.20';
10     @ISA      = qw( Excel::Template::Base );
11 }
12
13 use File::Basename;
14 use XML::Parser;
15 use IO::Scalar;
16
17 sub new
18 {
19     my $class = shift;
20     my $self = $class->SUPER::new(@_);
21
22     $self->parse_xml($self->{FILENAME})
23         if defined $self->{FILENAME};
24
25     my @renderer_classes = ( 'Spreadsheet::WriteExcel' );
26     if (exists $self->{BIG_FILE} && $self->{BIG_FILE})
27     {
28         unshift @renderer_classes, 'Spreadsheet::WriteExcel::Big';
29     }
30
31     if (exists $self->{XML} && $self->{XML})
32     {
33         unshift @renderer_classes, 'Spreadsheet::WriteExcelXML';
34     }
35
36     $self->{RENDERER} = undef;
37     foreach my $class (@renderer_classes)
38     {
39         (my $filename = $class) =~ s!::!/!g;
40         eval {
41             require "$filename.pm";
42             $class->import;
43         };
44         if ($@) {
45             warn "Could not find or compile '$class'\n" if $^W;
46         } else {
47             $self->{RENDERER} = $class;
48             last;
49         }
50     }
51
52     defined $self->{RENDERER} ||
53         die "Could not find a renderer class. Tried:\n\t" .
54             join("\n\t", @renderer_classes) .
55             "\n";
56
57     $self->{USE_UNICODE} = ~~0
58         if $] >= 5.008;
59
60     return $self;
61 }
62
63 sub param
64 {
65     my $self = shift;
66
67     # Allow an arbitrary number of hashrefs, so long as they're the first things    # into param(). Put each one onto the end, de-referenced.
68     push @_, %{shift @_} while UNIVERSAL::isa($_[0], 'HASH');
69
70     (@_ % 2)
71         && die __PACKAGE__, "->param() : Odd number of parameters to param()\n";
72
73     my %params = @_;
74     $params{uc $_} = delete $params{$_} for keys %params;
75     @{$self->{PARAM_MAP}}{keys %params} = @params{keys %params};
76
77     return ~~1;
78 }
79
80 sub write_file
81 {
82     my $self = shift;
83     my ($filename) = @_;
84
85     my $xls = $self->{RENDERER}->new($filename)
86         || die "Cannot create XLS in '$filename': $!\n";
87
88     $self->_prepare_output($xls);
89
90     $xls->close;
91
92     return ~~1;
93 }
94
95 sub output
96 {
97     my $self = shift;
98
99     my $output;
100     tie *XLS, 'IO::Scalar', \$output;
101
102     $self->write_file(\*XLS);
103
104     return $output;
105 }
106
107 sub parse_xml
108 {
109     my $self = shift;
110     my ($fname) = @_;
111
112     my ($filename, $dirname) = fileparse($fname);
113  
114     my @stack;
115     my $parser = XML::Parser->new(
116         Base => $dirname,
117         Handlers => {
118             Start => sub {
119                 shift;
120
121                 my $name = uc shift;
122
123                 my $node = Excel::Template::Factory->create_node($name, @_);
124                 die "'$name' (@_) didn't make a node!\n" unless defined $node;
125
126                 if ( $node->isa( 'WORKBOOK' ) )
127                 {
128                     push @{$self->{WORKBOOKS}}, $node;
129                 }
130                 elsif ( $node->is_embedded )
131                 {
132                     return unless @stack;
133                                                                                 
134                     if (exists $stack[-1]{TXTOBJ} &&
135                         $stack[-1]{TXTOBJ}->isa('TEXTOBJECT'))
136                     {
137                         push @{$stack[-1]{TXTOBJ}{STACK}}, $node;
138                     }
139  
140                 }
141                 else
142                 {
143                     push @{$stack[-1]{ELEMENTS}}, $node
144                         if @stack;
145                 }
146                 push @stack, $node;
147             },
148             Char => sub {
149                 shift;
150                 return unless @stack;
151
152                 my $parent = $stack[-1];
153
154                 if (
155                     exists $parent->{TXTOBJ}
156                         &&
157                     $parent->{TXTOBJ}->isa('TEXTOBJECT')
158                 ) {
159                     push @{$parent->{TXTOBJ}{STACK}}, @_;
160                 }
161             },
162             End => sub {
163                 shift;
164                 return unless @stack;
165
166                 pop @stack if $stack[-1]->isa(uc $_[0]);
167             },
168         },
169     );
170
171     {
172         open( INFILE, "<$fname" )
173             || die "Cannot open '$fname' for reading: $!\n";
174
175         $parser->parse(do { local $/ = undef; <INFILE> });
176
177         close INFILE;
178     }
179
180     return ~~1;
181 }
182 *parse = \&parse_xml;
183
184 sub _prepare_output
185 {
186     my $self = shift;
187     my ($xls) = @_;
188
189     my $context = Excel::Template::Factory->create(
190         'CONTEXT',
191
192         XLS       => $xls,
193         PARAM_MAP => [ $self->{PARAM_MAP} ],
194         UNICODE   => $self->{UNICODE},
195     );
196
197     $_->render($context) for @{$self->{WORKBOOKS}};
198
199     return ~~1;
200 }
201
202 sub register { shift; Excel::Template::Factory::register(@_) }
203
204 1;
205 __END__
206
207 =head1 NAME
208
209 Excel::Template - Excel::Template
210
211 =head1 SYNOPSIS
212
213 First, make a template. This is an XML file, describing the layout of the
214 spreadsheet.
215
216 For example, test.xml:
217
218   <workbook>
219       <worksheet name="tester">
220           <cell text="$HOME"/>
221           <cell text="$PATH"/>
222       </worksheet>
223   </workbook>
224
225 Now, create a small program to use it:
226
227   #!/usr/bin/perl -w
228   use Excel::Template;
229
230   # Create the Excel template
231   my $template = Excel::Template->new(
232       filename => 'test.xml',
233   );
234
235   # Add a few parameters
236   $template->param(
237       HOME => $ENV{HOME},
238       PATH => $ENV{PATH},
239   );
240
241   $template->write_file('test.xls');
242
243 If everything worked, then you should have a spreadsheet in your work directory
244 that looks something like:
245
246              A                B                C
247     +----------------+----------------+----------------
248   1 | /home/me       | /bin:/usr/bin  |
249     +----------------+----------------+----------------
250   2 |                |                |
251     +----------------+----------------+----------------
252   3 |                |                |
253
254 =head1 DESCRIPTION
255
256 This is a module used for templating Excel files. Its genesis came from the
257 need to use the same datastructure as HTML::Template, but provide Excel files
258 instead. The existing modules don't do the trick, as they require replication
259 of logic that's already been done within HTML::Template.
260
261 =head1 MOTIVATION
262
263 I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been,
264 I've been asked for HTML, PDF, and Excel. HTML::Template provides the first, and
265 PDF::Template does the second pretty well. But, generating Excel was the
266 sticking point. I already had the data structure for the other templating
267 modules, but I just didn't have an easy mechanism to get that data structure
268 into an XLS file.
269
270 =head1 USAGE
271
272 =head2 new()
273
274 This creates a Excel::Template object. If passed a FILENAME parameter, it will
275 parse the template in the given file. (You can also use the parse() method,
276 described below.)
277
278 new() accepts an optional BIG_FILE parameter. This will attempt to change the
279 renderer from L<Spreadsheet::WriteExcel> to L<Spreadsheet::WriteExcel::Big>. You
280 must already have L<OLE::Storage_Lite> (required by Spreadsheet::WriteExcel::Big) installed on your system.
281
282 new() also accepts an optional USE_UNICODE parameter. This will use
283 L<Unicode::String> to represent strings instead of Perl's internal string
284 handling. You must already have L<Unicode::String> installed on your system.
285
286 The USE_UNICODE parameter will be ignored if you are using Perl 5.8 or higher as
287 Perl's internal string handling is unicode-aware.
288
289 NOTE: Certain older versions of L<OLE::Storage_Lite> and mod_perl clash for some
290 reason. Upgrading to the latest version of L<OLE::Storage_Lite> should fix the
291 problem.
292
293 =head2 param()
294
295 This method is exactly like L<HTML::Template>'s param() method.
296
297 =head2 parse() / parse_xml()
298
299 This method actually parses the template file. It can either be called
300 separately or through the new() call. It will die() if it runs into a situation
301 it cannot handle.
302
303 =head2 write_file()
304
305 Create the Excel file and write it to the specified filename, if possible. (This
306 is when the actual merging of the template and the parameters occurs.)
307
308 =head2 output()
309
310 It will act just like HTML::Template's output() method, returning the resultant
311 file as a stream, usually for output to the web. (This is when the actual
312 merging of the template and the parameters occurs.)
313
314 =head1 SUPPORTED NODES
315
316 This is a partial list of nodes. See the other classes in this distro for more
317 details on specific parameters and the like.
318
319 Every node can set the ROW and COL parameters. These are the actual ROW/COL
320 values that the next CELL-type tag will write into.
321
322 =over 4
323
324 =item * L<WORKBOOK|Excel::Template::Container::Workbook>
325
326 This is the node representing the workbook. It is the parent for all other
327 nodes.
328
329 =item * L<WORKSHEET|Excel::Template::Container::Worksheet>
330
331 This is the node representing a given worksheet.
332
333 =item * L<IF|Excel::Template::Container::Conditional>
334
335 This node represents a conditional expression. Its children may or may not be
336 rendered. It behaves just like L<HTML::Template>'s TMPL_IF.
337
338 =item * L<LOOP|Excel::Template::Container::Loop>
339
340 This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP.
341
342 =item * L<ROW|Excel::Template::Container::Row>
343
344 This node represents a row of data. This is the A in A1.
345
346 =item * L<FORMAT|Excel::Template::Container::Format>
347
348 This node varies the format for its children. All formatting options supported
349 in L<Spreadsheet::WriteExcel> are supported here. There are also a number of
350 formatting shortcuts, such as L<BOLD|Excel::Template::Container::Bold> and
351 L<ITALIC|Excel::Template::Container::Italic>.
352
353 =item * L<BACKREF|Excel::Template::Element::Backref>
354
355 This refers back to a cell previously named.
356
357 =item * L<CELL|Excel::Template::Element::Cell>
358
359 This is the actual cell in a spreadsheet.
360
361 =item * L<FORMULA|Excel::Template::Element::Formula>
362
363 This is a formula in a spreadsheet.
364
365 =item * L<RANGE|Excel::Template::Element::Range>
366
367 This is a BACKREF for a number of identically-named cells.
368
369 =item * L<VAR|Excel::Template::Element::Var>
370
371 This is a variable. It is generally used when the 'text' attribute isn't
372 sufficient.
373
374 =back 4
375
376 =head1 BUGS
377
378 None, that I know of.
379
380 =head1 SUPPORT
381
382 This is production quality software, used in several production web
383 applications.
384
385 =head1 AUTHOR
386
387     Rob Kinyon (rob.kinyon@gmail.com)
388
389 =head1 CONTRIBUTORS
390
391 There is a mailing list at http://groups.google.com/group/ExcelTemplate
392
393 Robert Graff -
394
395 =over 4
396
397 =item * Finishing formats
398
399 =item * Fixing several bugs in worksheet naming
400
401 =back 4
402
403 =head1 COPYRIGHT
404
405 This program is free software; you can redistribute
406 it and/or modify it under the same terms as Perl itself.
407
408 The full text of the license can be found in the
409 LICENSE file included with this module.
410
411 =head1 SEE ALSO
412
413 perl(1), HTML::Template, Spreadsheet::WriteExcel.
414
415 =cut