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 | |
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 |
a8441e01 |
344 | rob.kinyon@gmail.com |
345 | |
346 | =head1 CONTRIBUTORS |
347 | |
348 | There is a mailing list at http://groups-beta.google.com/group/ExcelTemplate |
d0eafc11 |
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 |