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 | |
6dd4c89d |
9 | $VERSION = '0.21'; |
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 | |
32 | $self->parse_xml($self->{FILENAME}) |
33 | if defined $self->{FILENAME}; |
34 | |
35 | my @renderer_classes = ( 'Spreadsheet::WriteExcel' ); |
6dd4c89d |
36 | |
37 | if (exists $self->{RENDERER} && $self->{RENDERER}) |
d0eafc11 |
38 | { |
6dd4c89d |
39 | if (exists $renderers{ lc $self->{RENDERER} }) |
40 | { |
41 | unshift @renderer_classes, $renderers{ lc $self->{RENDERER} }; |
42 | } |
43 | elsif ($^W) |
44 | { |
45 | warn "'$self->{RENDERER}' is not recognized\n"; |
46 | } |
d0eafc11 |
47 | } |
6dd4c89d |
48 | elsif (exists $self->{BIG_FILE} && $self->{BIG_FILE}) |
9d172425 |
49 | { |
6dd4c89d |
50 | warn "Use of BIG_FILE is deprecated.\n"; |
51 | unshift @renderer_classes, 'Spreadsheet::WriteExcel::Big'; |
9d172425 |
52 | } |
53 | |
d0eafc11 |
54 | $self->{RENDERER} = undef; |
55 | foreach my $class (@renderer_classes) |
56 | { |
57 | (my $filename = $class) =~ s!::!/!g; |
58 | eval { |
59 | require "$filename.pm"; |
60 | $class->import; |
61 | }; |
62 | if ($@) { |
dee1f239 |
63 | warn "Could not find or compile '$class'\n" if $^W; |
d0eafc11 |
64 | } else { |
65 | $self->{RENDERER} = $class; |
66 | last; |
67 | } |
68 | } |
69 | |
70 | defined $self->{RENDERER} || |
71 | die "Could not find a renderer class. Tried:\n\t" . |
72 | join("\n\t", @renderer_classes) . |
73 | "\n"; |
74 | |
8c63e224 |
75 | $self->{USE_UNICODE} = ~~0 |
76 | if $] >= 5.008; |
77 | |
d0eafc11 |
78 | return $self; |
79 | } |
80 | |
81 | sub param |
82 | { |
83 | my $self = shift; |
84 | |
85 | # Allow an arbitrary number of hashrefs, so long as they're the first things # into param(). Put each one onto the end, de-referenced. |
86 | push @_, %{shift @_} while UNIVERSAL::isa($_[0], 'HASH'); |
87 | |
88 | (@_ % 2) |
89 | && die __PACKAGE__, "->param() : Odd number of parameters to param()\n"; |
90 | |
91 | my %params = @_; |
92 | $params{uc $_} = delete $params{$_} for keys %params; |
93 | @{$self->{PARAM_MAP}}{keys %params} = @params{keys %params}; |
94 | |
8c63e224 |
95 | return ~~1; |
d0eafc11 |
96 | } |
97 | |
98 | sub write_file |
99 | { |
100 | my $self = shift; |
101 | my ($filename) = @_; |
102 | |
103 | my $xls = $self->{RENDERER}->new($filename) |
104 | || die "Cannot create XLS in '$filename': $!\n"; |
105 | |
106 | $self->_prepare_output($xls); |
107 | |
108 | $xls->close; |
109 | |
8c63e224 |
110 | return ~~1; |
d0eafc11 |
111 | } |
112 | |
113 | sub output |
114 | { |
115 | my $self = shift; |
116 | |
117 | my $output; |
118 | tie *XLS, 'IO::Scalar', \$output; |
119 | |
120 | $self->write_file(\*XLS); |
121 | |
122 | return $output; |
123 | } |
124 | |
d0eafc11 |
125 | sub parse_xml |
126 | { |
127 | my $self = shift; |
128 | my ($fname) = @_; |
129 | |
130 | my ($filename, $dirname) = fileparse($fname); |
131 | |
132 | my @stack; |
133 | my $parser = XML::Parser->new( |
134 | Base => $dirname, |
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 | |
189 | { |
b6bc5a5d |
190 | open( INFILE, "<$fname" ) |
d0eafc11 |
191 | || die "Cannot open '$fname' for reading: $!\n"; |
192 | |
b6bc5a5d |
193 | $parser->parse(do { local $/ = undef; <INFILE> }); |
d0eafc11 |
194 | |
b6bc5a5d |
195 | close INFILE; |
d0eafc11 |
196 | } |
197 | |
8ca5e726 |
198 | return ~~1; |
d0eafc11 |
199 | } |
b6bc5a5d |
200 | *parse = \&parse_xml; |
d0eafc11 |
201 | |
202 | sub _prepare_output |
203 | { |
204 | my $self = shift; |
205 | my ($xls) = @_; |
206 | |
6dd4c89d |
207 | my $context = Excel::Template::Factory->_create( |
d0eafc11 |
208 | 'CONTEXT', |
209 | |
210 | XLS => $xls, |
211 | PARAM_MAP => [ $self->{PARAM_MAP} ], |
8c63e224 |
212 | UNICODE => $self->{UNICODE}, |
d0eafc11 |
213 | ); |
214 | |
215 | $_->render($context) for @{$self->{WORKBOOKS}}; |
216 | |
8ca5e726 |
217 | return ~~1; |
d0eafc11 |
218 | } |
219 | |
220 | sub register { shift; Excel::Template::Factory::register(@_) } |
221 | |
222 | 1; |
223 | __END__ |
224 | |
225 | =head1 NAME |
226 | |
227 | Excel::Template - Excel::Template |
228 | |
229 | =head1 SYNOPSIS |
230 | |
231 | First, make a template. This is an XML file, describing the layout of the |
232 | spreadsheet. |
233 | |
234 | For example, test.xml: |
235 | |
236 | <workbook> |
237 | <worksheet name="tester"> |
238 | <cell text="$HOME"/> |
239 | <cell text="$PATH"/> |
240 | </worksheet> |
241 | </workbook> |
242 | |
243 | Now, create a small program to use it: |
244 | |
245 | #!/usr/bin/perl -w |
9d172425 |
246 | use Excel::Template; |
d0eafc11 |
247 | |
248 | # Create the Excel template |
249 | my $template = Excel::Template->new( |
250 | filename => 'test.xml', |
251 | ); |
252 | |
253 | # Add a few parameters |
254 | $template->param( |
255 | HOME => $ENV{HOME}, |
256 | PATH => $ENV{PATH}, |
257 | ); |
258 | |
259 | $template->write_file('test.xls'); |
260 | |
261 | If everything worked, then you should have a spreadsheet in your work directory |
262 | that looks something like: |
263 | |
264 | A B C |
265 | +----------------+----------------+---------------- |
266 | 1 | /home/me | /bin:/usr/bin | |
267 | +----------------+----------------+---------------- |
268 | 2 | | | |
269 | +----------------+----------------+---------------- |
270 | 3 | | | |
271 | |
272 | =head1 DESCRIPTION |
273 | |
274 | This is a module used for templating Excel files. Its genesis came from the |
275 | need to use the same datastructure as HTML::Template, but provide Excel files |
276 | instead. The existing modules don't do the trick, as they require replication |
277 | of logic that's already been done within HTML::Template. |
278 | |
d0eafc11 |
279 | =head1 MOTIVATION |
280 | |
281 | I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been, |
282 | I've been asked for HTML, PDF, and Excel. HTML::Template provides the first, and |
283 | PDF::Template does the second pretty well. But, generating Excel was the |
284 | sticking point. I already had the data structure for the other templating |
285 | modules, but I just didn't have an easy mechanism to get that data structure |
286 | into an XLS file. |
287 | |
288 | =head1 USAGE |
289 | |
290 | =head2 new() |
291 | |
8ca5e726 |
292 | This creates a Excel::Template object. If passed a FILENAME parameter, it will |
d0eafc11 |
293 | parse the template in the given file. (You can also use the parse() method, |
294 | described below.) |
295 | |
6dd4c89d |
296 | =head3 Parameters |
297 | |
298 | =over 4 |
299 | |
300 | =item * RENDERER |
301 | |
302 | The default rendering engine is Spreadsheet::WriteExcel. You may, if you choose, change that to another choice. The legal values are: |
303 | |
304 | =over 4 |
305 | |
306 | =item * Excel::Template->RENDER_NML |
307 | |
308 | This is the default of Spreadsheet::WriteExcel. |
8c63e224 |
309 | |
6dd4c89d |
310 | =item * Excel::Template->RENDER_BIG |
311 | |
312 | This attempts to load Spreadsheet::WriteExcel::Big. |
313 | |
314 | =item * Excel::Template->RENDER_XML |
315 | |
316 | This attempts to load Spreadsheet::WriteExcelXML. |
317 | |
318 | =back |
319 | |
320 | =item * USE_UNICODE |
321 | |
322 | 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 |
323 | |
324 | The USE_UNICODE parameter will be ignored if you are using Perl 5.8 or higher as |
325 | Perl's internal string handling is unicode-aware. |
8ca5e726 |
326 | |
9d172425 |
327 | NOTE: Certain older versions of L<OLE::Storage_Lite> and mod_perl clash for some |
328 | reason. Upgrading to the latest version of L<OLE::Storage_Lite> should fix the |
329 | problem. |
8ca5e726 |
330 | |
6dd4c89d |
331 | =back |
332 | |
333 | =head3 Deprecated |
334 | |
335 | =over 4 |
336 | |
337 | =item * BIG_FILE |
338 | |
339 | Instead, use RENDERER => Excel::Template->RENDER_BIG |
340 | |
341 | =back |
342 | |
d0eafc11 |
343 | =head2 param() |
344 | |
8ca5e726 |
345 | This method is exactly like L<HTML::Template>'s param() method. |
d0eafc11 |
346 | |
347 | =head2 parse() / parse_xml() |
348 | |
349 | This method actually parses the template file. It can either be called |
350 | separately or through the new() call. It will die() if it runs into a situation |
351 | it cannot handle. |
352 | |
353 | =head2 write_file() |
354 | |
355 | Create the Excel file and write it to the specified filename, if possible. (This |
356 | is when the actual merging of the template and the parameters occurs.) |
357 | |
358 | =head2 output() |
359 | |
360 | It will act just like HTML::Template's output() method, returning the resultant |
361 | file as a stream, usually for output to the web. (This is when the actual |
362 | merging of the template and the parameters occurs.) |
363 | |
6dd4c89d |
364 | =head2 register() |
365 | |
366 | This allows you to register a class as handling a node. q.v. L<Excel::Template::Factory> for more info. |
367 | |
d0eafc11 |
368 | =head1 SUPPORTED NODES |
369 | |
8ca5e726 |
370 | This is a partial list of nodes. See the other classes in this distro for more |
d0eafc11 |
371 | details on specific parameters and the like. |
372 | |
373 | Every node can set the ROW and COL parameters. These are the actual ROW/COL |
8ca5e726 |
374 | values that the next CELL-type tag will write into. |
d0eafc11 |
375 | |
376 | =over 4 |
377 | |
8ca5e726 |
378 | =item * L<WORKBOOK|Excel::Template::Container::Workbook> |
379 | |
380 | This is the node representing the workbook. It is the parent for all other |
381 | nodes. |
382 | |
383 | =item * L<WORKSHEET|Excel::Template::Container::Worksheet> |
384 | |
385 | This is the node representing a given worksheet. |
386 | |
387 | =item * L<IF|Excel::Template::Container::Conditional> |
388 | |
389 | This node represents a conditional expression. Its children may or may not be |
390 | rendered. It behaves just like L<HTML::Template>'s TMPL_IF. |
391 | |
392 | =item * L<LOOP|Excel::Template::Container::Loop> |
393 | |
394 | This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP. |
395 | |
396 | =item * L<ROW|Excel::Template::Container::Row> |
397 | |
398 | This node represents a row of data. This is the A in A1. |
399 | |
400 | =item * L<FORMAT|Excel::Template::Container::Format> |
401 | |
402 | This node varies the format for its children. All formatting options supported |
403 | in L<Spreadsheet::WriteExcel> are supported here. There are also a number of |
404 | formatting shortcuts, such as L<BOLD|Excel::Template::Container::Bold> and |
405 | L<ITALIC|Excel::Template::Container::Italic>. |
406 | |
407 | =item * L<BACKREF|Excel::Template::Element::Backref> |
408 | |
409 | This refers back to a cell previously named. |
d0eafc11 |
410 | |
8ca5e726 |
411 | =item * L<CELL|Excel::Template::Element::Cell> |
d0eafc11 |
412 | |
8ca5e726 |
413 | This is the actual cell in a spreadsheet. |
d0eafc11 |
414 | |
8ca5e726 |
415 | =item * L<FORMULA|Excel::Template::Element::Formula> |
d0eafc11 |
416 | |
8ca5e726 |
417 | This is a formula in a spreadsheet. |
d0eafc11 |
418 | |
8ca5e726 |
419 | =item * L<RANGE|Excel::Template::Element::Range> |
d0eafc11 |
420 | |
8ca5e726 |
421 | This is a BACKREF for a number of identically-named cells. |
d0eafc11 |
422 | |
8ca5e726 |
423 | =item * L<VAR|Excel::Template::Element::Var> |
d0eafc11 |
424 | |
8ca5e726 |
425 | This is a variable. It is generally used when the 'text' attribute isn't |
426 | sufficient. |
d0eafc11 |
427 | |
6dd4c89d |
428 | =back |
d0eafc11 |
429 | |
430 | =head1 BUGS |
431 | |
432 | None, that I know of. |
433 | |
434 | =head1 SUPPORT |
435 | |
8ca5e726 |
436 | This is production quality software, used in several production web |
437 | applications. |
d0eafc11 |
438 | |
439 | =head1 AUTHOR |
440 | |
8ca5e726 |
441 | Rob Kinyon (rob.kinyon@gmail.com) |
a8441e01 |
442 | |
443 | =head1 CONTRIBUTORS |
444 | |
c11fa570 |
445 | There is a mailing list at http://groups.google.com/group/ExcelTemplate or exceltemplate@googlegroups.com |
d0eafc11 |
446 | |
c11fa570 |
447 | =head2 Robert Graff |
37513eae |
448 | |
449 | =over 4 |
450 | |
451 | =item * Finishing formats |
452 | |
453 | =item * Fixing several bugs in worksheet naming |
454 | |
6dd4c89d |
455 | =back |
37513eae |
456 | |
c11fa570 |
457 | =head1 TEST COVERAGE |
458 | |
459 | I used 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 branch cond sub pod time total |
465 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
466 | blib/lib/Excel/Template.pm 90.0 57.1 50.0 90.5 100.0 26.0 80.8 |
467 | ...ib/Excel/Template/Base.pm 83.3 50.0 66.7 75.0 88.9 8.8 80.0 |
468 | ...cel/Template/Container.pm 46.3 20.0 33.3 58.3 85.7 4.6 47.7 |
469 | ...emplate/Container/Bold.pm 100.0 n/a n/a 100.0 0.0 0.6 95.0 |
470 | .../Container/Conditional.pm 58.5 52.3 66.7 75.0 66.7 0.7 58.4 |
471 | ...plate/Container/Format.pm 100.0 n/a n/a 100.0 0.0 0.8 96.6 |
472 | ...plate/Container/Hidden.pm 100.0 n/a n/a 100.0 0.0 0.2 95.0 |
473 | ...plate/Container/Italic.pm 100.0 n/a n/a 100.0 0.0 0.2 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 55.6 40.0 50.0 77.8 75.0 0.5 56.6 |
476 | ...late/Container/Outline.pm 71.4 n/a n/a 80.0 0.0 0.0 70.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 1.1 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 83.1 53.4 54.2 95.0 92.9 19.2 75.2 |
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.6 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.2 94.1 |
490 | ...Excel/Template/Factory.pm 57.1 34.6 n/a 88.9 100.0 15.4 55.2 |
491 | .../Excel/Template/Format.pm 98.3 81.2 33.3 100.0 100.0 9.9 93.2 |
492 | ...xcel/Template/Iterator.pm 85.2 70.6 70.6 84.6 87.5 2.0 80.4 |
493 | ...el/Template/TextObject.pm 92.9 62.5 33.3 100.0 50.0 3.6 83.0 |
494 | Total 83.0 55.6 57.0 91.1 98.7 100.0 78.6 |
495 | ---------------------------- ------ ------ ------ ------ ------ ------ ------ |
496 | |
d0eafc11 |
497 | =head1 COPYRIGHT |
498 | |
499 | This program is free software; you can redistribute |
500 | it and/or modify it under the same terms as Perl itself. |
501 | |
502 | The full text of the license can be found in the |
503 | LICENSE file included with this module. |
504 | |
505 | =head1 SEE ALSO |
506 | |
507 | perl(1), HTML::Template, Spreadsheet::WriteExcel. |
508 | |
509 | =cut |