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 | |
dba6a68e |
9 | $VERSION = '0.17'; |
d0eafc11 |
10 | @ISA = qw( Excel::Template::Base ); |
11 | } |
12 | |
13 | use File::Basename; |
14 | use XML::Parser; |
d0eafc11 |
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 | |
b6bc5a5d |
69 | return !!1; |
d0eafc11 |
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 | |
b6bc5a5d |
84 | return !!1; |
d0eafc11 |
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 | |
d0eafc11 |
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 | |
37513eae |
118 | if ( $node->isa( 'WORKBOOK' ) ) |
d0eafc11 |
119 | { |
120 | push @{$self->{WORKBOOKS}}, $node; |
121 | } |
37513eae |
122 | elsif ( $node->is_embedded ) |
d0eafc11 |
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 | { |
b6bc5a5d |
164 | open( INFILE, "<$fname" ) |
d0eafc11 |
165 | || die "Cannot open '$fname' for reading: $!\n"; |
166 | |
b6bc5a5d |
167 | $parser->parse(do { local $/ = undef; <INFILE> }); |
d0eafc11 |
168 | |
b6bc5a5d |
169 | close INFILE; |
d0eafc11 |
170 | } |
171 | |
b6bc5a5d |
172 | return !!1; |
d0eafc11 |
173 | } |
b6bc5a5d |
174 | *parse = \&parse_xml; |
d0eafc11 |
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 | |
b6bc5a5d |
190 | return !!1; |
d0eafc11 |
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 |
a8441e01 |
337 | rob.kinyon@gmail.com |
338 | |
339 | =head1 CONTRIBUTORS |
340 | |
341 | There is a mailing list at http://groups-beta.google.com/group/ExcelTemplate |
d0eafc11 |
342 | |
37513eae |
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 | |
d0eafc11 |
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 |