- Converted to use Unicode logic from PDF::Template
[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.12';
10     @ISA      = qw( Excel::Template::Base );
11 }
12
13 use File::Basename;
14 use XML::Parser;
15 use IO::File;
16 use IO::Scalar;
17
18 sub new
19 {
20     my $class = shift;
21     my $self = $class->SUPER::new(@_);
22
23     $self->parse_xml($self->{FILENAME})
24         if defined $self->{FILENAME};
25
26     my @renderer_classes = ( 'Spreadsheet::WriteExcel' );
27     if (exists $self->{BIG_FILE} && $self->{BIG_FILE})
28     {
29         unshift @renderer_classes, 'Spreadsheet::WriteExcel::Big';
30     }
31
32     $self->{RENDERER} = undef;
33     foreach my $class (@renderer_classes)
34     {
35         (my $filename = $class) =~ s!::!/!g;
36         eval {
37             require "$filename.pm";
38             $class->import;
39         };
40         if ($@) {
41             warn "Could not find or compile '$class'\n";
42         } else {
43             $self->{RENDERER} = $class;
44             last;
45         }
46     }
47
48     defined $self->{RENDERER} ||
49         die "Could not find a renderer class. Tried:\n\t" .
50             join("\n\t", @renderer_classes) .
51             "\n";
52
53     return $self;
54 }
55
56 sub param
57 {
58     my $self = shift;
59
60     # Allow an arbitrary number of hashrefs, so long as they're the first things    # into param(). Put each one onto the end, de-referenced.
61     push @_, %{shift @_} while UNIVERSAL::isa($_[0], 'HASH');
62
63     (@_ % 2)
64         && die __PACKAGE__, "->param() : Odd number of parameters to param()\n";
65
66     my %params = @_;
67     $params{uc $_} = delete $params{$_} for keys %params;
68     @{$self->{PARAM_MAP}}{keys %params} = @params{keys %params};
69
70     return 1;
71 }
72
73 sub write_file
74 {
75     my $self = shift;
76     my ($filename) = @_;
77
78     my $xls = $self->{RENDERER}->new($filename)
79         || die "Cannot create XLS in '$filename': $!\n";
80
81     $self->_prepare_output($xls);
82
83     $xls->close;
84
85     return 1;
86 }
87
88 sub output
89 {
90     my $self = shift;
91
92     my $output;
93     tie *XLS, 'IO::Scalar', \$output;
94
95     $self->write_file(\*XLS);
96
97     return $output;
98 }
99
100 sub parse
101 {
102     my $self = shift;
103
104     $self->parse_xml(@_);
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 ($name eq 'WORKBOOK')
127                 {
128                     push @{$self->{WORKBOOKS}}, $node;
129                 }
130                 elsif ($name eq 'VAR')
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         my $fh = IO::File->new($fname)
173             || die "Cannot open '$fname' for reading: $!\n";
174
175         $parser->parse(do { local $/ = undef; <$fh> });
176
177         $fh->close;
178     }
179
180     return 1;
181 }
182
183 sub _prepare_output
184 {
185     my $self = shift;
186     my ($xls) = @_;
187
188     my $context = Excel::Template::Factory->create(
189         'CONTEXT',
190
191         XLS       => $xls,
192         PARAM_MAP => [ $self->{PARAM_MAP} ],
193     );
194
195     $_->render($context) for @{$self->{WORKBOOKS}};
196
197     return 1;
198 }
199
200 sub register { shift; Excel::Template::Factory::register(@_) }
201
202 1;
203 __END__
204
205 =head1 NAME
206
207 Excel::Template - Excel::Template
208
209 =head1 SYNOPSIS
210
211 First, make a template. This is an XML file, describing the layout of the
212 spreadsheet.
213
214 For example, test.xml:
215
216   <workbook>
217       <worksheet name="tester">
218           <cell text="$HOME"/>
219           <cell text="$PATH"/>
220       </worksheet>
221   </workbook>
222
223 Now, create a small program to use it:
224
225   #!/usr/bin/perl -w
226   use Excel::Template
227
228   # Create the Excel template
229   my $template = Excel::Template->new(
230       filename => 'test.xml',
231   );
232
233   # Add a few parameters
234   $template->param(
235       HOME => $ENV{HOME},
236       PATH => $ENV{PATH},
237   );
238
239   $template->write_file('test.xls');
240
241 If everything worked, then you should have a spreadsheet in your work directory
242 that looks something like:
243
244              A                B                C
245     +----------------+----------------+----------------
246   1 | /home/me       | /bin:/usr/bin  |
247     +----------------+----------------+----------------
248   2 |                |                |
249     +----------------+----------------+----------------
250   3 |                |                |
251
252 =head1 DESCRIPTION
253
254 This is a module used for templating Excel files. Its genesis came from the
255 need to use the same datastructure as HTML::Template, but provide Excel files
256 instead. The existing modules don't do the trick, as they require replication
257 of logic that's already been done within HTML::Template.
258
259 Currently, only a small subset of the planned features are supported. This is
260 meant to be a test of the waters, to see what features people actually want.
261
262 =head1 MOTIVATION
263
264 I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been,
265 I've been asked for HTML, PDF, and Excel. HTML::Template provides the first, and
266 PDF::Template does the second pretty well. But, generating Excel was the
267 sticking point. I already had the data structure for the other templating
268 modules, but I just didn't have an easy mechanism to get that data structure
269 into an XLS file.
270
271 =head1 USAGE
272
273 =head2 new()
274
275 This creates a Excel::Template object. If passed a filename parameter, it will
276 parse the template in the given file. (You can also use the parse() method,
277 described below.)
278
279 =head2 param()
280
281 This method is exactly like HTML::Template's param() method. Although I will
282 be adding more to this section later, please see HTML::Template's description
283 for info right now.
284
285 =head2 parse() / parse_xml()
286
287 This method actually parses the template file. It can either be called
288 separately or through the new() call. It will die() if it runs into a situation
289 it cannot handle.
290
291 =head2 write_file()
292
293 Create the Excel file and write it to the specified filename, if possible. (This
294 is when the actual merging of the template and the parameters occurs.)
295
296 =head2 output()
297
298 It will act just like HTML::Template's output() method, returning the resultant
299 file as a stream, usually for output to the web. (This is when the actual
300 merging of the template and the parameters occurs.)
301
302 =head1 SUPPORTED NODES
303
304 This is just a list of nodes. See the other classes in this distro for more
305 details on specific parameters and the like.
306
307 Every node can set the ROW and COL parameters. These are the actual ROW/COL
308 values that the next CELL tag will write into.
309
310 =over 4
311
312 =item * WORKBOOK
313
314 =item * WORKSHEET
315
316 =item * IF
317
318 =item * LOOP
319
320 =item * ROW
321
322 =item * CELL
323
324 =item * FORMULA
325
326 =item * BOLD
327
328 =item * ITALIC
329
330 =back 4
331
332 =head1 BUGS
333
334 None, that I know of.
335
336 =head1 SUPPORT
337
338 This is currently beta-quality software. The featureset is extremely limited,
339 but I expect to be adding on to it very soon.
340
341 =head1 AUTHOR
342
343     Rob Kinyon
344     rob.kinyon@gmail.com
345
346 =head1 CONTRIBUTORS
347
348 There is a mailing list at http://groups-beta.google.com/group/ExcelTemplate
349
350 =head1 COPYRIGHT
351
352 This program is free software; you can redistribute
353 it and/or modify it under the same terms as Perl itself.
354
355 The full text of the license can be found in the
356 LICENSE file included with this module.
357
358 =head1 SEE ALSO
359
360 perl(1), HTML::Template, Spreadsheet::WriteExcel.
361
362 =cut