Commit | Line | Data |
d0eafc11 |
1 | package Excel::Template; |
2 | |
3 | use strict; |
4 | |
5 | BEGIN { |
6 | use Excel::Template::Base; |
7 | use vars qw ($VERSION @ISA); |
8 | |
a084e413 |
9 | $VERSION = '0.22'; |
d0eafc11 |
10 | @ISA = qw( Excel::Template::Base ); |
11 | } |
12 | |
13 | use File::Basename; |
14 | use XML::Parser; |
d0eafc11 |
15 | use IO::Scalar; |
16 | |
6dd4c89d |
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 | |
d0eafc11 |
27 | sub 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 | |
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 | |
8c63e224 |
98 | return ~~1; |
d0eafc11 |
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 | |
8c63e224 |
113 | return ~~1; |
d0eafc11 |
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 | |
d0eafc11 |
128 | sub parse_xml |
129 | { |
130 | my $self = shift; |
a084e413 |
131 | my ($file) = @_; |
d0eafc11 |
132 | |
d0eafc11 |
133 | my @stack; |
a084e413 |
134 | my @parms = ( |
d0eafc11 |
135 | Handlers => { |
136 | Start => sub { |
137 | shift; |
138 | |
139 | my $name = uc shift; |
140 | |
6dd4c89d |
141 | my $node = Excel::Template::Factory->_create_node($name, @_); |
d0eafc11 |
142 | die "'$name' (@_) didn't make a node!\n" unless defined $node; |
143 | |
37513eae |
144 | if ( $node->isa( 'WORKBOOK' ) ) |
d0eafc11 |
145 | { |
146 | push @{$self->{WORKBOOKS}}, $node; |
147 | } |
37513eae |
148 | elsif ( $node->is_embedded ) |
d0eafc11 |
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 | |
a084e413 |
189 | if ( ref $file ) |
190 | { |
191 | *INFILE = $file; |
192 | } |
193 | else |
d0eafc11 |
194 | { |
a084e413 |
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 | } |
d0eafc11 |
203 | |
a084e413 |
204 | my $parser = XML::Parser->new( @parms ); |
205 | $parser->parse(do { local $/ = undef; <INFILE> }); |
d0eafc11 |
206 | |
a084e413 |
207 | unless ( ref $file ) |
208 | { |
b6bc5a5d |
209 | close INFILE; |
d0eafc11 |
210 | } |
211 | |
8ca5e726 |
212 | return ~~1; |
d0eafc11 |
213 | } |
b6bc5a5d |
214 | *parse = \&parse_xml; |
d0eafc11 |
215 | |
216 | sub _prepare_output |
217 | { |
218 | my $self = shift; |
219 | my ($xls) = @_; |
220 | |
6dd4c89d |
221 | my $context = Excel::Template::Factory->_create( |
d0eafc11 |
222 | 'CONTEXT', |
223 | |
224 | XLS => $xls, |
225 | PARAM_MAP => [ $self->{PARAM_MAP} ], |
8c63e224 |
226 | UNICODE => $self->{UNICODE}, |
d0eafc11 |
227 | ); |
228 | |
a084e413 |
229 | # print "@{$self->{WORKBOOKS}}\n"; |
d0eafc11 |
230 | $_->render($context) for @{$self->{WORKBOOKS}}; |
231 | |
8ca5e726 |
232 | return ~~1; |
d0eafc11 |
233 | } |
234 | |
235 | sub register { shift; Excel::Template::Factory::register(@_) } |
236 | |
237 | 1; |
238 | __END__ |
239 | |
240 | =head1 NAME |
241 | |
242 | Excel::Template - Excel::Template |
243 | |
244 | =head1 SYNOPSIS |
245 | |
246 | First, make a template. This is an XML file, describing the layout of the |
247 | spreadsheet. |
248 | |
249 | For example, test.xml: |
250 | |
251 | <workbook> |
252 | <worksheet name="tester"> |
253 | <cell text="$HOME"/> |
254 | <cell text="$PATH"/> |
255 | </worksheet> |
256 | </workbook> |
257 | |
258 | Now, create a small program to use it: |
259 | |
260 | #!/usr/bin/perl -w |
9d172425 |
261 | use Excel::Template; |
d0eafc11 |
262 | |
263 | # Create the Excel template |
264 | my $template = Excel::Template->new( |
265 | filename => 'test.xml', |
266 | ); |
267 | |
268 | # Add a few parameters |
269 | $template->param( |
270 | HOME => $ENV{HOME}, |
271 | PATH => $ENV{PATH}, |
272 | ); |
273 | |
274 | $template->write_file('test.xls'); |
275 | |
276 | If everything worked, then you should have a spreadsheet in your work directory |
277 | that looks something like: |
278 | |
279 | A B C |
280 | +----------------+----------------+---------------- |
281 | 1 | /home/me | /bin:/usr/bin | |
282 | +----------------+----------------+---------------- |
283 | 2 | | | |
284 | +----------------+----------------+---------------- |
285 | 3 | | | |
286 | |
287 | =head1 DESCRIPTION |
288 | |
289 | This is a module used for templating Excel files. Its genesis came from the |
290 | need to use the same datastructure as HTML::Template, but provide Excel files |
291 | instead. The existing modules don't do the trick, as they require replication |
292 | of logic that's already been done within HTML::Template. |
293 | |
d0eafc11 |
294 | =head1 MOTIVATION |
295 | |
296 | I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been, |
297 | I've been asked for HTML, PDF, and Excel. HTML::Template provides the first, and |
298 | PDF::Template does the second pretty well. But, generating Excel was the |
299 | sticking point. I already had the data structure for the other templating |
300 | modules, but I just didn't have an easy mechanism to get that data structure |
301 | into an XLS file. |
302 | |
303 | =head1 USAGE |
304 | |
305 | =head2 new() |
306 | |
a084e413 |
307 | This creates a Excel::Template object. |
d0eafc11 |
308 | |
6dd4c89d |
309 | =head3 Parameters |
310 | |
311 | =over 4 |
312 | |
a084e413 |
313 | =item * FILE / FILENAME |
314 | |
315 | Excel::Template will parse the template in the given file or filehandle automatically. (You can also use the parse() method, described below.) |
316 | |
317 | If you want to use the __DATA__ section, you can do so by passing |
318 | |
319 | FILE => \*DATA |
320 | |
6dd4c89d |
321 | =item * RENDERER |
322 | |
323 | The default rendering engine is Spreadsheet::WriteExcel. You may, if you choose, change that to another choice. The legal values are: |
324 | |
325 | =over 4 |
326 | |
327 | =item * Excel::Template->RENDER_NML |
328 | |
329 | This is the default of Spreadsheet::WriteExcel. |
8c63e224 |
330 | |
6dd4c89d |
331 | =item * Excel::Template->RENDER_BIG |
332 | |
333 | This attempts to load Spreadsheet::WriteExcel::Big. |
334 | |
335 | =item * Excel::Template->RENDER_XML |
336 | |
337 | This attempts to load Spreadsheet::WriteExcelXML. |
338 | |
339 | =back |
340 | |
341 | =item * USE_UNICODE |
342 | |
343 | 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. |
8c63e224 |
344 | |
345 | The USE_UNICODE parameter will be ignored if you are using Perl 5.8 or higher as |
346 | Perl's internal string handling is unicode-aware. |
8ca5e726 |
347 | |
9d172425 |
348 | NOTE: Certain older versions of L<OLE::Storage_Lite> and mod_perl clash for some |
349 | reason. Upgrading to the latest version of L<OLE::Storage_Lite> should fix the |
350 | problem. |
8ca5e726 |
351 | |
6dd4c89d |
352 | =back |
353 | |
354 | =head3 Deprecated |
355 | |
356 | =over 4 |
357 | |
358 | =item * BIG_FILE |
359 | |
360 | Instead, use RENDERER => Excel::Template->RENDER_BIG |
361 | |
362 | =back |
363 | |
d0eafc11 |
364 | =head2 param() |
365 | |
8ca5e726 |
366 | This method is exactly like L<HTML::Template>'s param() method. |
d0eafc11 |
367 | |
368 | =head2 parse() / parse_xml() |
369 | |
a084e413 |
370 | 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. |
371 | |
372 | If a filename is passed in (vs. a filehandle), the directory name will be passed in to XML::Parser as the I<Base> parameter. This will allow for XML directives to work as expected. |
d0eafc11 |
373 | |
374 | =head2 write_file() |
375 | |
376 | Create the Excel file and write it to the specified filename, if possible. (This |
377 | is when the actual merging of the template and the parameters occurs.) |
378 | |
379 | =head2 output() |
380 | |
381 | It will act just like HTML::Template's output() method, returning the resultant |
382 | file as a stream, usually for output to the web. (This is when the actual |
383 | merging of the template and the parameters occurs.) |
384 | |
6dd4c89d |
385 | =head2 register() |
386 | |
387 | This allows you to register a class as handling a node. q.v. L<Excel::Template::Factory> for more info. |
388 | |
d0eafc11 |
389 | =head1 SUPPORTED NODES |
390 | |
8ca5e726 |
391 | This is a partial list of nodes. See the other classes in this distro for more |
d0eafc11 |
392 | details on specific parameters and the like. |
393 | |
394 | Every node can set the ROW and COL parameters. These are the actual ROW/COL |
8ca5e726 |
395 | values that the next CELL-type tag will write into. |
d0eafc11 |
396 | |
397 | =over 4 |
398 | |
8ca5e726 |
399 | =item * L<WORKBOOK|Excel::Template::Container::Workbook> |
400 | |
401 | This is the node representing the workbook. It is the parent for all other |
402 | nodes. |
403 | |
404 | =item * L<WORKSHEET|Excel::Template::Container::Worksheet> |
405 | |
406 | This is the node representing a given worksheet. |
407 | |
408 | =item * L<IF|Excel::Template::Container::Conditional> |
409 | |
410 | This node represents a conditional expression. Its children may or may not be |
411 | rendered. It behaves just like L<HTML::Template>'s TMPL_IF. |
412 | |
413 | =item * L<LOOP|Excel::Template::Container::Loop> |
414 | |
415 | This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP. |
416 | |
417 | =item * L<ROW|Excel::Template::Container::Row> |
418 | |
419 | This node represents a row of data. This is the A in A1. |
420 | |
421 | =item * L<FORMAT|Excel::Template::Container::Format> |
422 | |
423 | This node varies the format for its children. All formatting options supported |
424 | in L<Spreadsheet::WriteExcel> are supported here. There are also a number of |
425 | formatting shortcuts, such as L<BOLD|Excel::Template::Container::Bold> and |
426 | L<ITALIC|Excel::Template::Container::Italic>. |
427 | |
428 | =item * L<BACKREF|Excel::Template::Element::Backref> |
429 | |
430 | This refers back to a cell previously named. |
d0eafc11 |
431 | |
8ca5e726 |
432 | =item * L<CELL|Excel::Template::Element::Cell> |
d0eafc11 |
433 | |
8ca5e726 |
434 | This is the actual cell in a spreadsheet. |
d0eafc11 |
435 | |
8ca5e726 |
436 | =item * L<FORMULA|Excel::Template::Element::Formula> |
d0eafc11 |
437 | |
8ca5e726 |
438 | This is a formula in a spreadsheet. |
d0eafc11 |
439 | |
8ca5e726 |
440 | =item * L<RANGE|Excel::Template::Element::Range> |
d0eafc11 |
441 | |
8ca5e726 |
442 | This is a BACKREF for a number of identically-named cells. |
d0eafc11 |
443 | |
8ca5e726 |
444 | =item * L<VAR|Excel::Template::Element::Var> |
d0eafc11 |
445 | |
8ca5e726 |
446 | This is a variable. It is generally used when the 'text' attribute isn't |
447 | sufficient. |
d0eafc11 |
448 | |
6dd4c89d |
449 | =back |
d0eafc11 |
450 | |
451 | =head1 BUGS |
452 | |
453 | None, that I know of. |
454 | |
455 | =head1 SUPPORT |
456 | |
8ca5e726 |
457 | This is production quality software, used in several production web |
458 | applications. |
d0eafc11 |
459 | |
460 | =head1 AUTHOR |
461 | |
8ca5e726 |
462 | Rob Kinyon (rob.kinyon@gmail.com) |
a8441e01 |
463 | |
464 | =head1 CONTRIBUTORS |
465 | |
c11fa570 |
466 | There is a mailing list at http://groups.google.com/group/ExcelTemplate or exceltemplate@googlegroups.com |
d0eafc11 |
467 | |
c11fa570 |
468 | =head2 Robert Graff |
37513eae |
469 | |
470 | =over 4 |
471 | |
472 | =item * Finishing formats |
473 | |
474 | =item * Fixing several bugs in worksheet naming |
475 | |
6dd4c89d |
476 | =back |
37513eae |
477 | |
c11fa570 |
478 | =head1 TEST COVERAGE |
479 | |
480 | I used Devel::Cover to test the coverage of my tests. Every release, I intend to improve these numbers. |
481 | |
482 | 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. |
483 | |
484 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
485 | File stmt branch cond sub pod time total |
486 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
a084e413 |
487 | blib/lib/Excel/Template.pm 90.4 62.5 58.8 90.5 100.0 30.2 82.0 |
488 | ...ib/Excel/Template/Base.pm 83.3 50.0 66.7 75.0 88.9 8.2 80.0 |
c11fa570 |
489 | ...cel/Template/Container.pm 46.3 20.0 33.3 58.3 85.7 4.6 47.7 |
a084e413 |
490 | ...emplate/Container/Bold.pm 100.0 n/a n/a 100.0 0.0 0.5 95.0 |
491 | .../Container/Conditional.pm 58.5 52.3 66.7 75.0 66.7 0.8 58.4 |
492 | ...plate/Container/Format.pm 100.0 n/a n/a 100.0 0.0 0.7 96.6 |
c11fa570 |
493 | ...plate/Container/Hidden.pm 100.0 n/a n/a 100.0 0.0 0.2 95.0 |
a084e413 |
494 | ...plate/Container/Italic.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0 |
c11fa570 |
495 | ...plate/Container/Locked.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0 |
496 | ...emplate/Container/Loop.pm 55.6 40.0 50.0 77.8 75.0 0.5 56.6 |
497 | ...late/Container/Outline.pm 71.4 n/a n/a 80.0 0.0 0.0 70.0 |
498 | ...Template/Container/Row.pm 100.0 75.0 n/a 100.0 50.0 0.3 93.8 |
499 | ...mplate/Container/Scope.pm 100.0 n/a n/a 100.0 n/a 0.1 100.0 |
500 | ...plate/Container/Shadow.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0 |
501 | ...te/Container/Strikeout.pm 100.0 n/a n/a 100.0 0.0 0.1 95.0 |
a084e413 |
502 | ...ate/Container/Workbook.pm 100.0 n/a n/a 100.0 n/a 1.0 100.0 |
c11fa570 |
503 | ...te/Container/Worksheet.pm 94.1 50.0 n/a 100.0 0.0 0.9 88.0 |
a084e413 |
504 | ...Excel/Template/Context.pm 83.1 53.4 54.2 95.0 92.9 19.1 75.2 |
505 | ...Excel/Template/Element.pm 100.0 n/a n/a 100.0 n/a 0.4 100.0 |
c11fa570 |
506 | ...mplate/Element/Backref.pm 100.0 50.0 33.3 100.0 0.0 0.1 87.1 |
507 | .../Template/Element/Cell.pm 95.8 65.0 80.0 100.0 66.7 3.6 86.9 |
508 | ...mplate/Element/Formula.pm 100.0 n/a n/a 100.0 0.0 0.3 94.1 |
509 | ...Template/Element/Range.pm 100.0 66.7 n/a 100.0 66.7 0.2 93.3 |
a084e413 |
510 | ...l/Template/Element/Var.pm 100.0 n/a n/a 100.0 0.0 0.1 94.1 |
511 | ...Excel/Template/Factory.pm 57.1 34.6 n/a 88.9 100.0 14.3 55.2 |
512 | .../Excel/Template/Format.pm 98.3 81.2 33.3 100.0 100.0 8.9 93.2 |
513 | ...xcel/Template/Iterator.pm 85.2 70.6 70.6 84.6 87.5 1.9 80.4 |
514 | ...el/Template/TextObject.pm 92.9 62.5 33.3 100.0 50.0 2.7 83.0 |
515 | Total 83.1 56.6 58.3 91.1 98.7 100.0 78.8 |
c11fa570 |
516 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
517 | |
d0eafc11 |
518 | =head1 COPYRIGHT |
519 | |
520 | This program is free software; you can redistribute |
521 | it and/or modify it under the same terms as Perl itself. |
522 | |
523 | The full text of the license can be found in the |
524 | LICENSE file included with this module. |
525 | |
526 | =head1 SEE ALSO |
527 | |
528 | perl(1), HTML::Template, Spreadsheet::WriteExcel. |
529 | |
530 | =cut |