- Fixed Makefile.PL so that it uses PM_FILTER instead of rolling its own
[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.16';
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";
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     return $self;
53 }
54
55 sub param
56 {
57     my $self = shift;
58
59     # Allow an arbitrary number of hashrefs, so long as they're the first things    # into param(). Put each one onto the end, de-referenced.
60     push @_, %{shift @_} while UNIVERSAL::isa($_[0], 'HASH');
61
62     (@_ % 2)
63         && die __PACKAGE__, "->param() : Odd number of parameters to param()\n";
64
65     my %params = @_;
66     $params{uc $_} = delete $params{$_} for keys %params;
67     @{$self->{PARAM_MAP}}{keys %params} = @params{keys %params};
68
69     return !!1;
70 }
71
72 sub write_file
73 {
74     my $self = shift;
75     my ($filename) = @_;
76
77     my $xls = $self->{RENDERER}->new($filename)
78         || die "Cannot create XLS in '$filename': $!\n";
79
80     $self->_prepare_output($xls);
81
82     $xls->close;
83
84     return !!1;
85 }
86
87 sub output
88 {
89     my $self = shift;
90
91     my $output;
92     tie *XLS, 'IO::Scalar', \$output;
93
94     $self->write_file(\*XLS);
95
96     return $output;
97 }
98
99 sub parse_xml
100 {
101     my $self = shift;
102     my ($fname) = @_;
103
104     my ($filename, $dirname) = fileparse($fname);
105  
106     my @stack;
107     my $parser = XML::Parser->new(
108         Base => $dirname,
109         Handlers => {
110             Start => sub {
111                 shift;
112
113                 my $name = uc shift;
114
115                 my $node = Excel::Template::Factory->create_node($name, @_);
116                 die "'$name' (@_) didn't make a node!\n" unless defined $node;
117
118                 if ( $node->isa( 'WORKBOOK' ) )
119                 {
120                     push @{$self->{WORKBOOKS}}, $node;
121                 }
122                 elsif ( $node->is_embedded )
123                 {
124                     return unless @stack;
125                                                                                 
126                     if (exists $stack[-1]{TXTOBJ} &&
127                         $stack[-1]{TXTOBJ}->isa('TEXTOBJECT'))
128                     {
129                         push @{$stack[-1]{TXTOBJ}{STACK}}, $node;
130                     }
131  
132                 }
133                 else
134                 {
135                     push @{$stack[-1]{ELEMENTS}}, $node
136                         if @stack;
137                 }
138                 push @stack, $node;
139             },
140             Char => sub {
141                 shift;
142                 return unless @stack;
143
144                 my $parent = $stack[-1];
145
146                 if (
147                     exists $parent->{TXTOBJ}
148                         &&
149                     $parent->{TXTOBJ}->isa('TEXTOBJECT')
150                 ) {
151                     push @{$parent->{TXTOBJ}{STACK}}, @_;
152                 }
153             },
154             End => sub {
155                 shift;
156                 return unless @stack;
157
158                 pop @stack if $stack[-1]->isa(uc $_[0]);
159             },
160         },
161     );
162
163     {
164         open( INFILE, "<$fname" )
165             || die "Cannot open '$fname' for reading: $!\n";
166
167         $parser->parse(do { local $/ = undef; <INFILE> });
168
169         close INFILE;
170     }
171
172     return !!1;
173 }
174 *parse = \&parse_xml;
175
176 sub _prepare_output
177 {
178     my $self = shift;
179     my ($xls) = @_;
180
181     my $context = Excel::Template::Factory->create(
182         'CONTEXT',
183
184         XLS       => $xls,
185         PARAM_MAP => [ $self->{PARAM_MAP} ],
186     );
187
188     $_->render($context) for @{$self->{WORKBOOKS}};
189
190     return !!1;
191 }
192
193 sub register { shift; Excel::Template::Factory::register(@_) }
194
195 1;
196 __END__
197
198 =head1 NAME
199
200 Excel::Template - Excel::Template
201
202 =head1 SYNOPSIS
203
204 First, make a template. This is an XML file, describing the layout of the
205 spreadsheet.
206
207 For example, test.xml:
208
209   <workbook>
210       <worksheet name="tester">
211           <cell text="$HOME"/>
212           <cell text="$PATH"/>
213       </worksheet>
214   </workbook>
215
216 Now, create a small program to use it:
217
218   #!/usr/bin/perl -w
219   use Excel::Template
220
221   # Create the Excel template
222   my $template = Excel::Template->new(
223       filename => 'test.xml',
224   );
225
226   # Add a few parameters
227   $template->param(
228       HOME => $ENV{HOME},
229       PATH => $ENV{PATH},
230   );
231
232   $template->write_file('test.xls');
233
234 If everything worked, then you should have a spreadsheet in your work directory
235 that looks something like:
236
237              A                B                C
238     +----------------+----------------+----------------
239   1 | /home/me       | /bin:/usr/bin  |
240     +----------------+----------------+----------------
241   2 |                |                |
242     +----------------+----------------+----------------
243   3 |                |                |
244
245 =head1 DESCRIPTION
246
247 This is a module used for templating Excel files. Its genesis came from the
248 need to use the same datastructure as HTML::Template, but provide Excel files
249 instead. The existing modules don't do the trick, as they require replication
250 of logic that's already been done within HTML::Template.
251
252 Currently, only a small subset of the planned features are supported. This is
253 meant to be a test of the waters, to see what features people actually want.
254
255 =head1 MOTIVATION
256
257 I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been,
258 I've been asked for HTML, PDF, and Excel. HTML::Template provides the first, and
259 PDF::Template does the second pretty well. But, generating Excel was the
260 sticking point. I already had the data structure for the other templating
261 modules, but I just didn't have an easy mechanism to get that data structure
262 into an XLS file.
263
264 =head1 USAGE
265
266 =head2 new()
267
268 This creates a Excel::Template object. If passed a filename parameter, it will
269 parse the template in the given file. (You can also use the parse() method,
270 described below.)
271
272 =head2 param()
273
274 This method is exactly like HTML::Template's param() method. Although I will
275 be adding more to this section later, please see HTML::Template's description
276 for info right now.
277
278 =head2 parse() / parse_xml()
279
280 This method actually parses the template file. It can either be called
281 separately or through the new() call. It will die() if it runs into a situation
282 it cannot handle.
283
284 =head2 write_file()
285
286 Create the Excel file and write it to the specified filename, if possible. (This
287 is when the actual merging of the template and the parameters occurs.)
288
289 =head2 output()
290
291 It will act just like HTML::Template's output() method, returning the resultant
292 file as a stream, usually for output to the web. (This is when the actual
293 merging of the template and the parameters occurs.)
294
295 =head1 SUPPORTED NODES
296
297 This is just a list of nodes. See the other classes in this distro for more
298 details on specific parameters and the like.
299
300 Every node can set the ROW and COL parameters. These are the actual ROW/COL
301 values that the next CELL tag will write into.
302
303 =over 4
304
305 =item * WORKBOOK
306
307 =item * WORKSHEET
308
309 =item * IF
310
311 =item * LOOP
312
313 =item * ROW
314
315 =item * CELL
316
317 =item * FORMULA
318
319 =item * BOLD
320
321 =item * ITALIC
322
323 =back 4
324
325 =head1 BUGS
326
327 None, that I know of.
328
329 =head1 SUPPORT
330
331 This is currently beta-quality software. The featureset is extremely limited,
332 but I expect to be adding on to it very soon.
333
334 =head1 AUTHOR
335
336     Rob Kinyon
337     rob.kinyon@gmail.com
338
339 =head1 CONTRIBUTORS
340
341 There is a mailing list at http://groups-beta.google.com/group/ExcelTemplate
342
343 Robert Graff -
344
345 =over 4
346
347 =item * Finishing formats
348
349 =item * Fixing several bugs in worksheet naming
350
351 =back 4
352
353 =head1 COPYRIGHT
354
355 This program is free software; you can redistribute
356 it and/or modify it under the same terms as Perl itself.
357
358 The full text of the license can be found in the
359 LICENSE file included with this module.
360
361 =head1 SEE ALSO
362
363 perl(1), HTML::Template, Spreadsheet::WriteExcel.
364
365 =cut