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