From: Rob Kinyon Date: Wed, 27 Oct 2004 12:59:22 +0000 (+0000) Subject: Initial Import X-Git-Tag: v0.13~4 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=d0eafc11b999be05367693c7d16879a83d3f2d19;p=p5sagit%2FExcel-Template.git Initial Import --- d0eafc11b999be05367693c7d16879a83d3f2d19 diff --git a/Changes b/Changes new file mode 100644 index 0000000..f1e84ad --- /dev/null +++ b/Changes @@ -0,0 +1,55 @@ +Revision history for Perl module Excel::Template + +0.12 Thu Apr 08 07:30:00 2004 + - Fixed bug regarding empty arrays as loop variables + +0.11 Wed Mar 17 16:00:00 2004 + - Fixed bug introduced in 0.10 (Loops were not case-insensitive) + +0.10 Wed Mar 17 16:00:00 2004 + - Parameters are now case-insensitive + +0.09 Mon Feb 02 16:00:00 2004 + - Fixed bug with multiple worksheets + +0.08 Fri Jan 30 14:00:00 2004 + - Added Base to the params for XML::Parser to allow for entity includes + +0.07 Fri Jan 23 08:30:00 2004 + - Fixed the MANIFEST to account for missing files + +0.06 Mon Jan 20 11:00:00 2004 + - Added formulas (no back-references yet) + - Improved POD a little + +0.05 Wed Jan 16 12:30:00 2004 + - Fixed a bug in formats + +0.04 Wed Jan 16 12:00:00 2004 + - Added BIG_FILES as an option, which will use + Spreadsheet::WriteExcel::Big as the renderer (yet unimplemented) + - Changed the output() method to use a tied IO::Scalar (which is + now a requirement. + - Firmed up the infrastructure in Excel::Template::Format + - Added the following tags + - FORMAT + - HIDDEN + - LOCKED + - OUTLINE + - SHADOW + - STRIKEOUT + +0.03 Wed Dec 03 20:30:00 2003 + - Added XML::Parser as a required pre-requisite module + - Added Italic format + - Removed $VERSION from Excel::Template::Base (Unneeded) + - No documentation or testing changes + +0.02 Sun Nov 30 17:00:00 2003 + - documentation improvements + - No actual functional changes + +0.01 Tue Nov 18 14:23:42 2003 + - original version; created by ExtUtils::ModuleMaker 0.32 + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d0305b --- /dev/null +++ b/LICENSE @@ -0,0 +1,383 @@ +Terms of Perl itself + +a) the GNU General Public License as published by the Free + Software Foundation; either version 1, or (at your option) any + later version, or +b) the "Artistic License" + +--------------------------------------------------------------------------- + +The General Public License (GPL) +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to most of +the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you wish), that +you receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs; and that you know you can do +these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a +fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer +you this license which gives you legal permission to copy, distribute and/or +modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced by +others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish +to avoid the danger that redistributors of a free program will individually obtain +patent licenses, in effect making the program proprietary. To prevent this, we +have made it clear that any patent must be licensed for everyone's free use or +not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +GNU GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND +MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is not +restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and appropriately +publish on each copy an appropriate copyright notice and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence of any +warranty; and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at +your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you also +meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that you +changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be licensed +as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you +must cause it, when started running for such interactive use in the most ordinary +way, to print or display an announcement including an appropriate copyright +notice and a notice that there is no warranty (or else, saying that you provide a +warranty) and that users may redistribute the program under these conditions, +and telling the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an announcement, +your work based on the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be on +the terms of this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to control +the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and 2 +above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any +third party, for a charge no more than your cost of physically performing source +distribution, a complete machine-readable copy of the corresponding source +code, to be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all the +source code for all modules it contains, plus any associated interface definition +files, plus the scripts used to control compilation and installation of the +executable. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary form) +with the major components (compiler, kernel, and so on) of the operating system +on which the executable runs, unless that component itself accompanies the +executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so long +as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not accept +this License. Therefore, by modifying or distributing the Program (or any work +based on the Program), you indicate your acceptance of this License to do so, +and all its terms and conditions for copying, distributing or modifying the +Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to copy, +distribute or modify the Program subject to these terms and conditions. You +may not impose any further restrictions on the recipients' exercise of the rights +granted herein. You are not responsible for enforcing compliance by third parties +to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed on +you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of this +License. If you cannot distribute so as to satisfy simultaneously your obligations +under this License and any other pertinent obligations, then as a consequence +you may not distribute the Program at all. For example, if a patent license would +not permit royalty-free redistribution of the Program by all those who receive +copies directly or indirectly through you, then the only way you could satisfy +both it and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under any particular +circumstance, the balance of the section is intended to apply and the section as +a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other +property right claims or to contest validity of any such claims; this section has +the sole purpose of protecting the integrity of the free software distribution +system, which is implemented by public license practices. Many people have +made generous contributions to the wide range of software distributed through +that system in reliance on consistent application of that system; it is up to the +author/donor to decide if he or she is willing to distribute software through any +other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries +either by patents or by copyrighted interfaces, the original copyright holder who +places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is permitted +only in or among countries not thus excluded. In such case, this License +incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems or +concerns. + +Each version is given a distinguishing version number. If the Program specifies a +version number of this License which applies to it and "any later version", you +have the option of following the terms and conditions either of that version or of +any later version published by the Free Software Foundation. If the Program does +not specify a version number of this License, you may choose any version ever +published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of all +derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS +NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE +COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM +"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED +TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY +WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM +(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY +OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + + +--------------------------------------------------------------------------- + +The Artistic License + +Preamble + +The intent of this document is to state the conditions under which a Package +may be copied, such that the Copyright Holder maintains some semblance of +artistic control over the development of the package, while giving the users of the +package the right to use and distribute the Package in a more-or-less customary +fashion, plus the right to make reasonable modifications. + +Definitions: + +- "Package" refers to the collection of files distributed by the Copyright + Holder, and derivatives of that collection of files created through textual + modification. +- "Standard Version" refers to such a Package if it has not been modified, + or has been modified in accordance with the wishes of the Copyright + Holder. +- "Copyright Holder" is whoever is named in the copyright or copyrights for + the package. +- "You" is you, if you're thinking about copying or distributing this Package. +- "Reasonable copying fee" is whatever you can justify on the basis of + media cost, duplication charges, time of people involved, and so on. (You + will not be required to justify it to the Copyright Holder, but only to the + computing community at large as a market that must bear the fee.) +- "Freely Available" means that no fee is charged for the item itself, though + there may be fees involved in handling the item. It also means that + recipients of the item may redistribute it under the same conditions they + received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you duplicate +all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications derived from +the Public Domain or from the Copyright Holder. A Package modified in such a +way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and when +you changed that file, and provided that you do at least ONE of the following: + + a) place your modifications in the Public Domain or otherwise + make them Freely Available, such as by posting said modifications + to Usenet or an equivalent medium, or placing the modifications on + a major archive site such as ftp.uu.net, or by allowing the + Copyright Holder to include your modifications in the Standard + Version of the Package. + + b) use the modified Package only within your corporation or + organization. + + c) rename any non-standard executables so the names do not + conflict with standard executables, which must also be provided, + and provide a separate manual page for each non-standard + executable that clearly documents how it differs from the Standard + Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or executable +form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library + files, together with instructions (in the manual page or equivalent) + on where to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) accompany any non-standard executables with their + corresponding Standard Version executables, giving the + non-standard executables non-standard names, and clearly + documenting the differences in manual pages (or equivalent), + together with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this Package. +You may charge any fee you choose for support of this Package. You may not +charge a fee for this Package itself. However, you may distribute this Package in +aggregate with other (possibly commercial) programs as part of a larger +(possibly commercial) software distribution provided that you do not advertise +this Package as a product of your own. + +6. The scripts and library files supplied as input to or produced as output from +the programs of this Package do not automatically fall under the copyright of this +Package, but belong to whomever generated them, and may be sold +commercially, and may be aggregated with this Package. + +7. C or perl subroutines supplied by you and linked into this Package shall not +be considered part of this Package. + +8. Aggregation of this Package with a commercial distribution is always permitted +provided that the use of this Package is embedded; that is, when no overt attempt +is made to make this Package's interfaces visible to the end user of the +commercial distribution. Such use shall not be construed as a distribution of +this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +The End + + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..0ff82b1 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,33 @@ +MANIFEST +LICENSE +README +Todo +Changes +lib/Excel/Template.pm +lib/Excel/Template/Base.pm +lib/Excel/Template/Container.pm +lib/Excel/Template/Context.pm +lib/Excel/Template/Element.pm +lib/Excel/Template/Factory.pm +lib/Excel/Template/Format.pm +lib/Excel/Template/Iterator.pm +lib/Excel/Template/TextObject.pm +lib/Excel/Template/Container/Bold.pm +lib/Excel/Template/Container/Conditional.pm +lib/Excel/Template/Container/Format.pm +lib/Excel/Template/Container/Hidden.pm +lib/Excel/Template/Container/Italic.pm +lib/Excel/Template/Container/Loop.pm +lib/Excel/Template/Container/Locked.pm +lib/Excel/Template/Container/Outline.pm +lib/Excel/Template/Container/Row.pm +lib/Excel/Template/Container/Scope.pm +lib/Excel/Template/Container/Strikeout.pm +lib/Excel/Template/Container/Shadow.pm +lib/Excel/Template/Container/Workbook.pm +lib/Excel/Template/Container/Worksheet.pm +lib/Excel/Template/Element/Cell.pm +lib/Excel/Template/Element/Formula.pm +lib/Excel/Template/Element/Var.pm +t/001_load.t +Makefile.PL diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..89504ef --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,16 @@ +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. +WriteMakefile( + NAME => 'Excel::Template', + VERSION_FROM => 'lib/Excel/Template.pm', # finds $VERSION + AUTHOR => 'Rob Kinyon (rkinyon@columbus.rr.com', + ABSTRACT => 'Excel::Template', + PREREQ_PM => { + 'Test::Simple' => 0.44, + 'Spreadsheet::WriteExcel' => 0.42, + 'XML::Parser' => 0.01, + 'IO::Scalar' => 0.01, + 'IO::File' => 0.01, + }, +); diff --git a/README b/README new file mode 100644 index 0000000..b5fedff --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +Excel::Template is a layout system to use the data structure from HTML::Template +and create a Microsoft Excel file. A few caveats: +1) All limitations stated in Spreadsheet::WriteExcel are in force, as that is + the module used for rendering. +2) The output() method is not extremely well-tested. +3) Not all the methods have been tested in all situations. I fully expect bugs + to come back and have to be fixed. + +Barring all of that, and a slight lack of features, it's a good module. Enjoy! + +Rob diff --git a/Todo b/Todo new file mode 100644 index 0000000..297e3fd --- /dev/null +++ b/Todo @@ -0,0 +1,9 @@ +TODO list for Perl module Excel::Template + +- Add more formatting options, such as italic +- Add fonts +- Add numeric/string formats +- Figure out how to add support for formulas, especially in s +- Figure out if and how to add support for charts +- Anything else people suggest + diff --git a/lib/Excel/Template.pm b/lib/Excel/Template.pm new file mode 100644 index 0000000..665405b --- /dev/null +++ b/lib/Excel/Template.pm @@ -0,0 +1,358 @@ +package Excel::Template; + +use strict; + +BEGIN { + use Excel::Template::Base; + use vars qw ($VERSION @ISA); + + $VERSION = '0.12'; + @ISA = qw( Excel::Template::Base ); +} + +use File::Basename; +use XML::Parser; +use IO::File; +use IO::Scalar; + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->parse_xml($self->{FILENAME}) + if defined $self->{FILENAME}; + + my @renderer_classes = ( 'Spreadsheet::WriteExcel' ); + if (exists $self->{BIG_FILE} && $self->{BIG_FILE}) + { + unshift @renderer_classes, 'Spreadsheet::WriteExcel::Big'; + } + + $self->{RENDERER} = undef; + foreach my $class (@renderer_classes) + { + (my $filename = $class) =~ s!::!/!g; + eval { + require "$filename.pm"; + $class->import; + }; + if ($@) { + warn "Could not find or compile '$class'\n"; + } else { + $self->{RENDERER} = $class; + last; + } + } + + defined $self->{RENDERER} || + die "Could not find a renderer class. Tried:\n\t" . + join("\n\t", @renderer_classes) . + "\n"; + + return $self; +} + +sub param +{ + my $self = shift; + + # Allow an arbitrary number of hashrefs, so long as they're the first things # into param(). Put each one onto the end, de-referenced. + push @_, %{shift @_} while UNIVERSAL::isa($_[0], 'HASH'); + + (@_ % 2) + && die __PACKAGE__, "->param() : Odd number of parameters to param()\n"; + + my %params = @_; + $params{uc $_} = delete $params{$_} for keys %params; + @{$self->{PARAM_MAP}}{keys %params} = @params{keys %params}; + + return 1; +} + +sub write_file +{ + my $self = shift; + my ($filename) = @_; + + my $xls = $self->{RENDERER}->new($filename) + || die "Cannot create XLS in '$filename': $!\n"; + + $self->_prepare_output($xls); + + $xls->close; + + return 1; +} + +sub output +{ + my $self = shift; + + my $output; + tie *XLS, 'IO::Scalar', \$output; + + $self->write_file(\*XLS); + + return $output; +} + +sub parse +{ + my $self = shift; + + $self->parse_xml(@_); +} + +sub parse_xml +{ + my $self = shift; + my ($fname) = @_; + + my ($filename, $dirname) = fileparse($fname); + + my @stack; + my $parser = XML::Parser->new( + Base => $dirname, + Handlers => { + Start => sub { + shift; + + my $name = uc shift; + + my $node = Excel::Template::Factory->create_node($name, @_); + die "'$name' (@_) didn't make a node!\n" unless defined $node; + + if ($name eq 'WORKBOOK') + { + push @{$self->{WORKBOOKS}}, $node; + } + elsif ($name eq 'VAR') + { + return unless @stack; + + if (exists $stack[-1]{TXTOBJ} && + $stack[-1]{TXTOBJ}->isa('TEXTOBJECT')) + { + push @{$stack[-1]{TXTOBJ}{STACK}}, $node; + } + + } + else + { + push @{$stack[-1]{ELEMENTS}}, $node + if @stack; + } + push @stack, $node; + }, + Char => sub { + shift; + return unless @stack; + + my $parent = $stack[-1]; + + if ( + exists $parent->{TXTOBJ} + && + $parent->{TXTOBJ}->isa('TEXTOBJECT') + ) { + push @{$parent->{TXTOBJ}{STACK}}, @_; + } + }, + End => sub { + shift; + return unless @stack; + + pop @stack if $stack[-1]->isa(uc $_[0]); + }, + }, + ); + + { + my $fh = IO::File->new($fname) + || die "Cannot open '$fname' for reading: $!\n"; + + $parser->parse(do { local $/ = undef; <$fh> }); + + $fh->close; + } + + return 1; +} + +sub _prepare_output +{ + my $self = shift; + my ($xls) = @_; + + my $context = Excel::Template::Factory->create( + 'CONTEXT', + + XLS => $xls, + PARAM_MAP => [ $self->{PARAM_MAP} ], + ); + + $_->render($context) for @{$self->{WORKBOOKS}}; + + return 1; +} + +sub register { shift; Excel::Template::Factory::register(@_) } + +1; +__END__ + +=head1 NAME + +Excel::Template - Excel::Template + +=head1 SYNOPSIS + +First, make a template. This is an XML file, describing the layout of the +spreadsheet. + +For example, test.xml: + + + + + + + + +Now, create a small program to use it: + + #!/usr/bin/perl -w + use Excel::Template + + # Create the Excel template + my $template = Excel::Template->new( + filename => 'test.xml', + ); + + # Add a few parameters + $template->param( + HOME => $ENV{HOME}, + PATH => $ENV{PATH}, + ); + + $template->write_file('test.xls'); + +If everything worked, then you should have a spreadsheet in your work directory +that looks something like: + + A B C + +----------------+----------------+---------------- + 1 | /home/me | /bin:/usr/bin | + +----------------+----------------+---------------- + 2 | | | + +----------------+----------------+---------------- + 3 | | | + +=head1 DESCRIPTION + +This is a module used for templating Excel files. Its genesis came from the +need to use the same datastructure as HTML::Template, but provide Excel files +instead. The existing modules don't do the trick, as they require replication +of logic that's already been done within HTML::Template. + +Currently, only a small subset of the planned features are supported. This is +meant to be a test of the waters, to see what features people actually want. + +=head1 MOTIVATION + +I do a lot of Perl/CGI for reporting purposes. In nearly every place I've been, +I've been asked for HTML, PDF, and Excel. HTML::Template provides the first, and +PDF::Template does the second pretty well. But, generating Excel was the +sticking point. I already had the data structure for the other templating +modules, but I just didn't have an easy mechanism to get that data structure +into an XLS file. + +=head1 USAGE + +=head2 new() + +This creates a Excel::Template object. If passed a filename parameter, it will +parse the template in the given file. (You can also use the parse() method, +described below.) + +=head2 param() + +This method is exactly like HTML::Template's param() method. Although I will +be adding more to this section later, please see HTML::Template's description +for info right now. + +=head2 parse() / parse_xml() + +This method actually parses the template file. It can either be called +separately or through the new() call. It will die() if it runs into a situation +it cannot handle. + +=head2 write_file() + +Create the Excel file and write it to the specified filename, if possible. (This +is when the actual merging of the template and the parameters occurs.) + +=head2 output() + +It will act just like HTML::Template's output() method, returning the resultant +file as a stream, usually for output to the web. (This is when the actual +merging of the template and the parameters occurs.) + +=head1 SUPPORTED NODES + +This is just a list of nodes. See the other classes in this distro for more +details on specific parameters and the like. + +Every node can set the ROW and COL parameters. These are the actual ROW/COL +values that the next CELL tag will write into. + +=over 4 + +=item * WORKBOOK + +=item * WORKSHEET + +=item * IF + +=item * LOOP + +=item * ROW + +=item * CELL + +=item * FORMULA + +=item * BOLD + +=item * ITALIC + +=back 4 + +=head1 BUGS + +None, that I know of. + +=head1 SUPPORT + +This is currently beta-quality software. The featureset is extremely limited, +but I expect to be adding on to it very soon. + +=head1 AUTHOR + + Rob Kinyon + rkinyon@columbus.rr.com + +=head1 COPYRIGHT + +This program is free software; you can redistribute +it and/or modify it under the same terms as Perl itself. + +The full text of the license can be found in the +LICENSE file included with this module. + +=head1 SEE ALSO + +perl(1), HTML::Template, Spreadsheet::WriteExcel. + +=cut diff --git a/lib/Excel/Template/Base.pm b/lib/Excel/Template/Base.pm new file mode 100644 index 0000000..9788c11 --- /dev/null +++ b/lib/Excel/Template/Base.pm @@ -0,0 +1,78 @@ +package Excel::Template::Base; + +use strict; + +BEGIN { +} + +use Excel::Template::Factory; + +sub new +{ + my $class = shift; + + push @_, %{shift @_} while UNIVERSAL::isa($_[0], 'HASH'); + (@_ % 2) + and die "$class->new() called with odd number of option parameters\n"; + + my %x = @_; + + # Do not use a hashref-slice here because of the uppercase'ing + my $self = {}; + $self->{uc $_} = $x{$_} for keys %x; + + bless $self, $class; +} + +sub isa { Excel::Template::Factory::isa(@_) } + +sub calculate { ($_[1])->get(@_[0,2]) } +#{ +# my $self = shift; +# my ($context, $attr) = @_; +# +# return $context->get($self, $attr); +#} + +sub enter_scope { ($_[1])->enter_scope($_[0]) } +#{ +# my $self = shift; +# my ($context) = @_; +# +# return $context->enter_scope($self); +#} + +sub exit_scope { ($_[1])->exit_scope(@_[0, 2]) } +#{ +# my $self = shift; +# my ($context, $no_delta) = @_; +# +# return $context->exit_scope($self, $no_delta); +#} + +sub deltas +{ +# my $self = shift; +# my ($context) = @_; + + return {}; +} + +sub resolve +{ +# my $self = shift; +# my ($context) = @_; + + ''; +} + +sub render +{ +# my $self = shift; +# my ($context) = @_; + + 1; +} + +1; +__END__ diff --git a/lib/Excel/Template/Container.pm b/lib/Excel/Template/Container.pm new file mode 100644 index 0000000..97d6972 --- /dev/null +++ b/lib/Excel/Template/Container.pm @@ -0,0 +1,147 @@ +package Excel::Template::Container; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Base); + + use Excel::Template::Base; +} + +# Containers are objects that can contain arbitrary elements, such as +# PageDefs or Loops. + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{ELEMENTS} = [] unless UNIVERSAL::isa($self->{ELEMENTS}, 'ARRAY'); + + return $self; +} + +sub _do_page +{ + my $self = shift; + my ($method, $context) = @_; + + for my $e (@{$self->{ELEMENTS}}) + { + $e->enter_scope($context); + $e->$method($context); + $e->exit_scope($context, 1); + } + + return 1; +} + +sub begin_page { _do_page 'begin_page', @_ } +sub end_page { _do_page 'end_page', @_ } + +sub iterate_over_children +{ + my $self = shift; + my ($context) = @_; + + my $continue = 1; + + for my $e ( + @{$self->{ELEMENTS}}) + { + $e->enter_scope($context); + + my $rc = $e->render($context); + $continue = $rc if $continue; + + $e->exit_scope($context); + } + + return $continue; +} + +sub render { $_[0]->iterate_over_children($_[1]) } +#{ +# my $self = shift; +# my ($context) = @_; +# +# return $self->iterate_over_children($context); +#} + +sub max_of +{ + my $self = shift; + my ($context, $attr) = @_; + + my $max = $context->get($self, $attr); + + ELEMENT: + foreach my $e (@{$self->{ELEMENTS}}) + { + $e->enter_scope($context); + + my $v = $e->isa('CONTAINER') + ? $e->max_of($context, $attr) + : $e->calculate($context, $attr); + + $max = $v if $max < $v; + + $e->exit_scope($context, 1); + } + + return $max; +} + +sub total_of +{ + my $self = shift; + my ($context, $attr) = @_; + + my $total = 0; + + ELEMENT: + foreach my $e (@{$self->{ELEMENTS}}) + { + $e->enter_scope($context); + + $total += $e->isa('CONTAINER') + ? $e->total_of($context, $attr) + : $e->calculate($context, $attr); + + $e->exit_scope($context, 1); + } + + return $total; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container + +=head1 PURPOSE + +=head1 NODE NAME + +=head1 INHERITANCE + +=head1 ATTRIBUTES + +=head1 CHILDREN + +=head1 AFFECTS + +=head1 DEPENDENCIES + +=head1 USAGE + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/Container/Bold.pm b/lib/Excel/Template/Container/Bold.pm new file mode 100644 index 0000000..f4b345f --- /dev/null +++ b/lib/Excel/Template/Container/Bold.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Bold; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{BOLD} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Bold - Excel::Template::Container::Bold + +=head1 PURPOSE + +To format all children in bold + +=head1 NODE NAME + +BOLD + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a bold format. All other formatting will remain the same and the +"bold"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Conditional.pm b/lib/Excel/Template/Container/Conditional.pm new file mode 100644 index 0000000..cd7ec75 --- /dev/null +++ b/lib/Excel/Template/Container/Conditional.pm @@ -0,0 +1,189 @@ +package Excel::Template::Container::Conditional; + +#GGG Convert to be a special case of ? + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Container); + + use Excel::Template::Container; +} + +my %isOp = ( + '=' => '==', + (map { $_ => $_ } ( '>', '<', '==', '!=', '>=', '<=' )), + (map { $_ => $_ } ( 'gt', 'lt', 'eq', 'ne', 'ge', 'le' )), +); + +sub conditional_passes +{ + my $self = shift; + my ($context) = @_; + + my $name = $context->get($self, 'NAME'); + return 0 unless $name =~ /\S/; + + my $val = $context->param($name); + $val = @{$val} while UNIVERSAL::isa($val, 'ARRAY'); + $val = ${$val} while UNIVERSAL::isa($val, 'SCALAR'); + + my $value = $context->get($self, 'VALUE'); + if (defined $value) + { + my $op = $context->get($self, 'OP'); + $op = defined $op && exists $isOp{$op} + ? $isOp{$op} + : '=='; + + # Force numerical context on both values; + $value *= 1; + $val *= 1; + + my $res; + for ($op) + { + /^>$/ && do { $res = ($val > $value); last }; + /^<$/ && do { $res = ($val < $value); last }; + /^==$/ && do { $res = ($val == $value); last }; + /^!=$/ && do { $res = ($val != $value); last }; + /^>=$/ && do { $res = ($val >= $value); last }; + /^<=$/ && do { $res = ($val <= $value); last }; + /^gt$/ && do { $res = ($val gt $value); last }; + /^lt$/ && do { $res = ($val lt $value); last }; + /^eq$/ && do { $res = ($val eq $value); last }; + /^ne$/ && do { $res = ($val ne $value); last }; + /^ge$/ && do { $res = ($val ge $value); last }; + /^le$/ && do { $res = ($val le $value); last }; + + die "Unknown operator in conditional resolve", $/; + } + + return 0 unless $res; + } + elsif (my $is = uc $context->get($self, 'IS')) + { + my $istrue = $val && 1; + if ($is eq 'TRUE') + { + return 0 unless $istrue; + } + else + { + warn "Conditional 'is' value was [$is], defaulting to 'FALSE'" . $/ + if $is ne 'FALSE'; + + return 0 if $istrue; + } + } + + return 1; +} + +sub render +{ + my $self = shift; + my ($context) = @_; + + return 1 unless $self->conditional_passes($context); + + return $self->iterate_over_children($context); +} + +sub max_of +{ + my $self = shift; + my ($context, $attr) = @_; + + return 0 unless $self->conditional_passes($context); + + return $self->SUPER::max_of($context, $attr); +} + +sub total_of +{ + my $self = shift; + my ($context, $attr) = @_; + + return 0 unless $self->conditional_passes($context); + + return $self->SUPER::total_of($context, $attr); +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Conditional - Excel::Template::Container::Conditional + +=head1 PURPOSE + +To provide conditional execution of children nodes + +=head1 NODE NAME + +IF + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +=over 4 + +=item * NAME + +This is the name of the parameter to be testing. It is resolved like any other +parameter. + +=item * VALUE + +If VALUE is set, then a comparison operation is done. The value of NAME is +compared to VALUE using the value of OP. + +=item * OP + +If VALUE is set, then this is checked. If it isn't present, then '==' (numeric +equality) is assumed. OP must be one of the numeric comparison operators or the +string comparison operators. All 6 of each kind is supported. + +=item * IS + +If VALUE is not set, then IS is checked. IS is allowed to be either "TRUE" or +"FALSE". The boolean value of NAME is checked against IS. + +=back 4 + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be executed if the value of __ODD__ +(which is set by the LOOP node) is false. So, for all even iterations. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +LOOP + +=cut diff --git a/lib/Excel/Template/Container/Format.pm b/lib/Excel/Template/Container/Format.pm new file mode 100644 index 0000000..3574362 --- /dev/null +++ b/lib/Excel/Template/Container/Format.pm @@ -0,0 +1,116 @@ +package Excel::Template::Container::Format; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container ); + + use Excel::Template::Container; +} + +use Excel::Template::Format; + +sub render +{ + my $self = shift; + my ($context) = @_; + + my $old_format = $context->active_format; + my $format = Excel::Template::Format->copy( + $context, $old_format, + + %{$self}, + ); + $context->active_format($format); + + my $child_success = $self->iterate_over_children($context); + + $context->active_format($old_format); +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Format - Excel::Template::Container::Format + +=head1 PURPOSE + +To format all children according to the parameters + +=head1 NODE NAME + +FORMAT + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +=over 4 + +=item * bold + +This will set bold to on or off, depending on the boolean value. + +=item * hidden + +This will set whether the cell is hidden to on or off, depending on the boolean +value. (q.v. BOLD tag) + +=item * italic + +This will set italic to on or off, depending on the boolean value. (q.v. ITALIC +tag) + +=item * font_outline + +This will set font_outline to on or off, depending on the boolean value. (q.v. +OUTLINE tag) + +=item * font_shadow + +This will set font_shadow to on or off, depending on the boolean value. (q.v. +SHADOW tag) + +=item * font_strikeout + +This will set font_strikeout to on or off, depending on the boolean value. (q.v. +STRIKEOUT tag) + +=back 4 + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a bold format. All other formatting will remain the same and the +"bold"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +BOLD, HIDDEN, ITALIC, OUTLINE, SHADOW, STRIKEOUT + +=cut diff --git a/lib/Excel/Template/Container/Hidden.pm b/lib/Excel/Template/Container/Hidden.pm new file mode 100644 index 0000000..97f8f08 --- /dev/null +++ b/lib/Excel/Template/Container/Hidden.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Hidden; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{HIDDEN} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Hidden - Excel::Template::Container::Hidden + +=head1 PURPOSE + +To format all children in hidden + +=head1 NODE NAME + +HIDDEN + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a hidden format. All other formatting will remain the same and the +"hidden"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Italic.pm b/lib/Excel/Template/Container/Italic.pm new file mode 100644 index 0000000..f4f4e00 --- /dev/null +++ b/lib/Excel/Template/Container/Italic.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Italic; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{ITALIC} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Italic - Excel::Template::Container::Italic + +=head1 PURPOSE + +To format all children in italic + +=head1 NODE NAME + +ITALIC + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a italic format. All other formatting will remain the same and the +"italic"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Locked.pm b/lib/Excel/Template/Container/Locked.pm new file mode 100644 index 0000000..55cab43 --- /dev/null +++ b/lib/Excel/Template/Container/Locked.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Locked; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{LOCKED} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Locked - Excel::Template::Container::Locked + +=head1 PURPOSE + +To format all children in locked + +=head1 NODE NAME + +LOCKED + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a locked format. All other formatting will remain the same and the +"locked"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Loop.pm b/lib/Excel/Template/Container/Loop.pm new file mode 100644 index 0000000..574ca5f --- /dev/null +++ b/lib/Excel/Template/Container/Loop.pm @@ -0,0 +1,179 @@ +package Excel::Template::Container::Loop; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Container); + + use Excel::Template::Container; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + if (exists $self->{MAXITERS} && $self->{MAXITERS} < 1) + { + die " MAXITERS must be greater than or equal to 1", $/; + } + else + { + $self->{MAXITERS} = 0; + } + + return $self; +} + +sub make_iterator +{ + my $self = shift; + my ($context) = @_; + + return Excel::Template::Factory->create('ITERATOR', + NAME => $context->get($self, 'NAME'), + MAXITERS => $context->get($self, 'MAXITERS'), + CONTEXT => $context, + ); +} + +sub render +{ + my $self = shift; + my ($context) = @_; + + unless ($self->{ITERATOR} && $self->{ITERATOR}->more_params) + { + $self->{ITERATOR} = $self->make_iterator($context); + } + my $iterator = $self->{ITERATOR}; + + $iterator->enter_scope; + + while ($iterator->can_continue) + { + $iterator->next; + + unless ($self->iterate_over_children($context)) + { + $iterator->back_up; + last; + } + } + + $iterator->exit_scope; + + return 0 if $iterator->more_params; + + return 1; +} + +sub total_of +{ + my $self = shift; + my ($context, $attr) = @_; + + my $iterator = $self->make_iterator($context); + + my $total = 0; + + $iterator->enter_scope; + while ($iterator->can_continue) + { + $iterator->next; + $total += $self->SUPER::total_of($context, $attr); + } + $iterator->exit_scope; + + return $total; +} + +sub max_of +{ + my $self = shift; + my ($context, $attr) = @_; + + my $iterator = $self->make_iterator($context); + + my $max = $context->get($self, $attr); + + $iterator->enter_scope; + while ($iterator->can_continue) + { + $iterator->next; + my $v = $self->SUPER::max_of($context, $attr); + + $max = $v if $max < $v; + } + $iterator->exit_scope; + + return $max; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Loop + +=head1 PURPOSE + +To provide looping + +=head1 NODE NAME + +LOOP + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +=over 4 + +=item * NAME + +This is the name of the loop. It's used to identify within the parameter set +what variables to expose to the children nodes each iteration. + +=back 4 + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here ... + + +In the above example, the children nodes would have access to the LOOPY array +of hashes as parameters. Each iteration through the array would expose a +different hash of parameters to the children. + +These loops work just like HTML::Template's loops. (I promise I'll give more +info here!) + +There is one difference - I prefer using Perl-like scoping, so accessing of +variables outside the LOOP scope from within is perfectly acceptable. You can +also hide outside variables with inner values, if you desire, just like Perl. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/Container/Outline.pm b/lib/Excel/Template/Container/Outline.pm new file mode 100644 index 0000000..a13447b --- /dev/null +++ b/lib/Excel/Template/Container/Outline.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Outline; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{FONT_OUTLINE} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Outline - Excel::Template::Container::Outline + +=head1 PURPOSE + +To format all children in outline + +=head1 NODE NAME + +OUTLINE + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a outline format. All other formatting will remain the same and the +"outline"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Row.pm b/lib/Excel/Template/Container/Row.pm new file mode 100644 index 0000000..cc5c3d1 --- /dev/null +++ b/lib/Excel/Template/Container/Row.pm @@ -0,0 +1,81 @@ +package Excel::Template::Container::Row; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Container); + + use Excel::Template::Container; +} + +sub render +{ + my $self = shift; + my ($context) = @_; + + $context->{COL} = 0; + + return $self->SUPER::render($context); +} + +sub deltas +{ + return { + ROW => +1, + }; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Row - Excel::Template::Container::Row + +=head1 PURPOSE + +To provide a row context for CELL tags + +=head1 NODE NAME + +ROW + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +Each ROW tag will consume one row of the workbook. When the ROW tag starts, it +will set the COL value to 0. + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +Generally, you will have CELL and/or FORMULA tags within a ROW. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +CELL, FORMULA + +=cut diff --git a/lib/Excel/Template/Container/Scope.pm b/lib/Excel/Template/Container/Scope.pm new file mode 100644 index 0000000..97c4124 --- /dev/null +++ b/lib/Excel/Template/Container/Scope.pm @@ -0,0 +1,66 @@ +package Excel::Template::Container::Scope; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Container); + + use Excel::Template::Container; +} + +# This is used as a placeholder for scoping values across any number +# of children. It does nothing on its own. + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Scope + +=head1 PURPOSE + +To provide scoping of parameters for children + +=head1 NODE NAME + +SCOPE + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here ... + + +In the above example, the children would all have access to the parameters +param1 and param2. This is useful if you have a section of your template that +all has the same set of parameter values, but don't have a common parent. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/Container/Shadow.pm b/lib/Excel/Template/Container/Shadow.pm new file mode 100644 index 0000000..8597218 --- /dev/null +++ b/lib/Excel/Template/Container/Shadow.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Shadow; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{FONT_SHADOW} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Shadow - Excel::Template::Container::Shadow + +=head1 PURPOSE + +To format all children in shadow + +=head1 NODE NAME + +SHADOW + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a shadow format. All other formatting will remain the same and the +"shadow"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Strikeout.pm b/lib/Excel/Template/Container/Strikeout.pm new file mode 100644 index 0000000..bc6b96b --- /dev/null +++ b/lib/Excel/Template/Container/Strikeout.pm @@ -0,0 +1,75 @@ +package Excel::Template::Container::Strikeout; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container::Format ); + + use Excel::Template::Container::Format; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{FONT_STRIKEOUT} = 1; + + return $self; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Strikeout - Excel::Template::Container::Strikeout + +=head1 PURPOSE + +To format all children in bold + +=head1 NODE NAME + +STRIKEOUT + +=head1 INHERITANCE + +Excel::Template::Container::Format + +=head1 ATTRIBUTES + +None + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be displayed (if they are displaying +elements) in a bold format. All other formatting will remain the same and the +"bold"-ness will end at the end tag. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +FORMAT + +=cut diff --git a/lib/Excel/Template/Container/Workbook.pm b/lib/Excel/Template/Container/Workbook.pm new file mode 100644 index 0000000..2defd37 --- /dev/null +++ b/lib/Excel/Template/Container/Workbook.pm @@ -0,0 +1,62 @@ +package Excel::Template::Container::Workbook; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw( Excel::Template::Container ); + + use Excel::Template::Container; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Workbook - Excel::Template::Container::Workbook + +=head1 PURPOSE + +The root node + +=head1 NODE NAME + +WORKBOOK + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +Currently, none. There will be attributes added here, regarding how the +workbook as a whole will behave. + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +Nothing + +=cut diff --git a/lib/Excel/Template/Container/Worksheet.pm b/lib/Excel/Template/Container/Worksheet.pm new file mode 100644 index 0000000..c707779 --- /dev/null +++ b/lib/Excel/Template/Container/Worksheet.pm @@ -0,0 +1,80 @@ +package Excel::Template::Container::Worksheet; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Container); + + use Excel::Template::Container; +} + +sub render +{ + my $self = shift; + my ($context) = @_; + + $context->new_worksheet($self->{NAME}); + + return $self->SUPER::render($context); +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Container::Worksheet - Excel::Template::Container::Worksheet + +=head1 PURPOSE + +To provide a new worksheet. + +=head1 NODE NAME + +WORKSHEET + +=head1 INHERITANCE + +Excel::Template::Container + +=head1 ATTRIBUTES + +=over 4 + +=item * NAME + +This is the name of the worksheet to be added. + +=back 4 + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + ... Children here + + +In the above example, the children will be executed in the context of the +"My Taxes" worksheet. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +ROW, CELL, FORMULA + +=cut diff --git a/lib/Excel/Template/Context.pm b/lib/Excel/Template/Context.pm new file mode 100644 index 0000000..79c5415 --- /dev/null +++ b/lib/Excel/Template/Context.pm @@ -0,0 +1,262 @@ +package Excel::Template::Context; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Base); + + use Excel::Template::Base; +} + +use Excel::Template::Format; + +# This is a helper object. It is not instantiated by the user, nor does it +# represent an XML object. Rather, every container will use this object to +# maintain the context for its children. + +my %isAbsolute = map { $_ => 1 } qw( + ROW + COL +); + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{ACTIVE_WORKSHEET} = undef; + $self->{ACTIVE_FORMAT} = Excel::Template::Format->blank_format($self); + + UNIVERSAL::isa($self->{$_}, 'ARRAY') || ($self->{$_} = []) + for qw( STACK PARAM_MAP NAME_MAP ); + + $self->{$_} = 0 for keys %isAbsolute; + + return $self; +} + +sub _find_param_in_map +{ + my $self = shift; + my ($map, $param, $depth) = @_; + $param = uc $param; + $depth ||= 0; + + my $val = undef; + my $found = 0; + + for my $map (reverse @{$self->{$map}}) + { + next unless exists $map->{$param}; + $depth--, next if $depth; + + $found = 1; + $val = $map->{$param}; + last; + } + + die "Parameter '$param' not found\n" + if !$found && $self->{DIE_ON_NO_PARAM}; + + return $val; +} + +sub param +{ + my $self = shift; + $self->_find_param_in_map( + 'PARAM_MAP', + @_, + ); +} + +sub named_param +{ + my $self = shift; + $self->_find_param_in_map( + 'NAME_MAP', + @_, + ); +} + +sub resolve +{ + my $self = shift; + my ($obj, $key, $depth) = @_; + $key = uc $key; + $depth ||= 0; + + my $obj_val = $obj->{$key}; + + $obj_val = $self->param($1) + if $obj_val =~ /^\$(\S+)$/o; + +#GGG Remove this once NAME_MAP is working +# $obj_val = $self->named_param($1) +# if $obj_val =~ /^\\(\S+)$/o; + +#GGG Does this adequately test values to make sure they're legal?? + # A value is defined as: + # 1) An optional operator (+, -, *, or /) + # 2) A decimal number + +#GGG Convert this to use //x + my ($op, $val) = $obj_val =~ m!^\s*([\+\*\/\-])?\s*([\d.]*\d)\s*$!oi; + + # Unless it's a relative value, we have what we came for. + return $obj_val unless $op; + + my $prev_val = $isAbsolute{$key} + ? $self->{$key} + : $self->get($obj, $key, $depth + 1); + + return $obj_val unless defined $prev_val; + return $prev_val unless defined $obj_val; + + # Prevent divide-by-zero issues. + return $val if $op eq '/' and $val == 0; + + my $new_val; + for ($op) + { + /^\+$/ && do { $new_val = ($prev_val + $val); last; }; + /^\-$/ && do { $new_val = ($prev_val - $val); last; }; + /^\*$/ && do { $new_val = ($prev_val * $val); last; }; + /^\/$/ && do { $new_val = ($prev_val / $val); last; }; + + die "Unknown operator '$op' in arithmetic resolve\n"; + } + + return $new_val if defined $new_val; + return; +} + +sub enter_scope +{ + my $self = shift; + my ($obj) = @_; + + push @{$self->{STACK}}, $obj; + + for my $key (keys %isAbsolute) + { + next unless exists $obj->{$key}; + $self->{$key} = $self->resolve($obj, $key); + } + + return 1; +} + +sub exit_scope +{ + my $self = shift; + my ($obj, $no_delta) = @_; + + unless ($no_delta) + { + my $deltas = $obj->deltas($self); + $self->{$_} += $deltas->{$_} for keys %$deltas; + } + + pop @{$self->{STACK}}; + + return 1; +} + +sub get +{ + my $self = shift; + my ($dummy, $key, $depth) = @_; + $depth ||= 0; + $key = uc $key; + + return unless @{$self->{STACK}}; + + my $obj = $self->{STACK}[-1]; + + return $self->{$key} if $isAbsolute{$key}; + + my $val = undef; + my $this_depth = $depth; + foreach my $e (reverse @{$self->{STACK}}) + { + next unless exists $e->{$key}; + next if $this_depth-- > 0; + + $val = $self->resolve($e, $key, $depth); + last; + } + + $val = $self->{$key} unless defined $val; + return $val unless defined $val; + + return $self->param($1, $depth) if $val =~ /^\$(\S+)$/o; + + return $val; +} + +sub active_format +{ + my $self = shift; + + $self->{ACTIVE_FORMAT} = $_[0] + if @_; + + $self->{ACTIVE_FORMAT}; +} + +sub new_worksheet +{ + my $self = shift; + my ($name) = @_; + + $self->{ROW} = $self->{COL} = 0; + + $self->active_worksheet( + $self->{XLS}->add_worksheet( + $name || '', + ), + ); +} + +sub active_worksheet +{ + my $self = shift; + + $self->{ACTIVE_WORKSHEET} = $_[0] + if @_; + + $self->{ACTIVE_WORKSHEET}; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Context + +=head1 PURPOSE + +=head1 NODE NAME + +=head1 INHERITANCE + +=head1 ATTRIBUTES + +=head1 CHILDREN + +=head1 AFFECTS + +=head1 DEPENDENCIES + +=head1 USAGE + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/Element.pm b/lib/Excel/Template/Element.pm new file mode 100644 index 0000000..4ca8284 --- /dev/null +++ b/lib/Excel/Template/Element.pm @@ -0,0 +1,41 @@ +package Excel::Template::Element; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Base); + + use Excel::Template::Base; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Element + +=head1 PURPOSE + +=head1 NODE NAME + +=head1 INHERITANCE + +=head1 ATTRIBUTES + +=head1 CHILDREN + +=head1 AFFECTS + +=head1 DEPENDENCIES + +=head1 USAGE + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/Element/Cell.pm b/lib/Excel/Template/Element/Cell.pm new file mode 100644 index 0000000..88a0140 --- /dev/null +++ b/lib/Excel/Template/Element/Cell.pm @@ -0,0 +1,147 @@ +package Excel::Template::Element::Cell; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Element); + + use Excel::Template::Element; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{TXTOBJ} = Excel::Template::Factory->create('TEXTOBJECT'); + + return $self; +} + +sub get_text +{ + my $self = shift; + my ($context) = @_; + + my $txt = $context->get($self, 'TEXT'); + if (defined $txt) + { + my $txt_obj = Excel::Template::Factory->create('TEXTOBJECT'); + push @{$txt_obj->{STACK}}, $txt; + $txt = $txt_obj->resolve($context); + } + elsif ($self->{TXTOBJ}) + { + $txt = $self->{TXTOBJ}->resolve($context) + } + else + { +# $txt = Unicode::String::utf8(''); + $txt = ''; + } + + return $txt; +} + +sub render +{ + my $self = shift; + my ($context) = @_; + + $context->active_worksheet->write( + (map { $context->get($self, $_) } qw(ROW COL)), + $self->get_text($context), + $context->active_format, + ); + + return 1; +} + +sub deltas +{ + return { + COL => +1, + }; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Element::Cell - Excel::Template::Element::Cell + +=head1 PURPOSE + +To actually write stuff to the worksheet + +=head1 NODE NAME + +CELL + +=head1 INHERITANCE + +Excel::Template::Element + +=head1 ATTRIBUTES + +=over 4 + +=item * TEXT + +This is the text to write to the cell. This can either be text or a parameter +with a dollar-sign in front of the parameter name. + +=item * COL + +Optionally, you can specify which column you want this cell to be in. It can be +either a number (zero-based) or an offset. See Excel::Template for more info on +offset-based numbering. + +=back 4 + +There will be more parameters added, as features are added. + +=head1 CHILDREN + +Excel::Template::Element::Formula + +=head1 EFFECTS + +This will consume one column on the current row. + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + Some other text here + + + Some text here + +In the above example, four cells are written out. The first two have text hard- +coded. The second two have variables. The third and fourth items have another +thing that should be noted. If you have text where you want a variable in the +middle, you have to use the latter form. Variables within parameters are the +entire parameter's value. + +Please see Spreadsheet::WriteExcel for what constitutes a legal formula. + +=head1 BACK-REFERENCES + +Currently, you can only use a hard-coded formula. The next release will add the +capability to have a formula reference other nodes in the template dynamically. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +ROW, VAR, FORMULA + +=cut diff --git a/lib/Excel/Template/Element/Formula.pm b/lib/Excel/Template/Element/Formula.pm new file mode 100644 index 0000000..094326e --- /dev/null +++ b/lib/Excel/Template/Element/Formula.pm @@ -0,0 +1,109 @@ +package Excel::Template::Element::Formula; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Element::Cell); + + use Excel::Template::Element::Cell; +} + +sub get_text +{ + my $self = shift; + my ($context) = @_; + + my $text = $self->SUPER::get_text($context); + +# At this point, we must do back-reference dereferencing + + return $text; +} + +sub render +{ + my $self = shift; + my ($context) = @_; + + $context->active_worksheet->write_formula( + (map { $context->get($self, $_) } qw(ROW COL)), + $self->get_text($context), + ); + + return 1; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Element::Formula - Excel::Template::Element::Formula + +=head1 PURPOSE + +To write formulas to the worksheet + +=head1 NODE NAME + +FORMULA + +=head1 INHERITANCE + +Excel::Template::Element::Cell + +=head1 ATTRIBUTES + +=over 4 + +=item * TEXT + +This is the formula to write to the cell. This can either be text or a parameter +with a dollar-sign in front of the parameter name. + +=item * COL + +Optionally, you can specify which column you want this cell to be in. It can be +either a number (zero-based) or an offset. See Excel::Template for more info on +offset-based numbering. + +=back 4 + +There will be more parameters added, as features are added. + +=head1 CHILDREN + +None + +=head1 EFFECTS + +This will consume one column on the current row. + +=head1 DEPENDENCIES + +None + +=head1 USAGE + + + =SUM(A1:A5) + + + =(A1 + ) + +In the above example, four formulas are written out. The first two have the +formula hard-coded. The second two have variables. The third and fourth items +have another thing that should be noted. If you have a formula where you want a +variable in the middle, you have to use the latter form. Variables within +parameters are the entire parameter's value. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +ROW, VAR, CELL + +=cut diff --git a/lib/Excel/Template/Element/Var.pm b/lib/Excel/Template/Element/Var.pm new file mode 100644 index 0000000..b4eac2f --- /dev/null +++ b/lib/Excel/Template/Element/Var.pm @@ -0,0 +1,75 @@ +package Excel::Template::Element::Var; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Element); + + use Excel::Template::Element; +} + +sub resolve { ($_[1])->param($_[1]->resolve($_[0], 'NAME')) } + +1; +__END__ + +=head1 NAME + +Excel::Template::Element::Var + +=head1 PURPOSE + +To provide parameter substitution. + +=head1 NODE NAME + +VAR + +=head1 INHERITANCE + +Excel::Template::Element + +=head1 ATTRIBUTES + +=over 4 + +=item * NAME + +This is the name of the parameter to substitute here. + +=back 4 + +=head1 CHILDREN + +None + +=head1 EFFECTS + +None + +=head1 DEPENDENCIES + +This will only be used within CELL tags. + +=head1 USAGE + +This is used exactly like HTML::Template's TMPL_VAR. There is one exception - +since you can have variable names inside the parameters, you can do something +like: + + + + + +Where the actual name to be substituted is, itself, a parameter. + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +CELL + +=cut diff --git a/lib/Excel/Template/Factory.pm b/lib/Excel/Template/Factory.pm new file mode 100644 index 0000000..6b806c9 --- /dev/null +++ b/lib/Excel/Template/Factory.pm @@ -0,0 +1,167 @@ +package Excel::Template::Factory; + +use strict; + +BEGIN { + use vars qw(%Manifest %isBuildable); +} + +%Manifest = ( + +# These are the instantiable nodes + 'IF' => 'Excel::Template::Container::Conditional', + 'LOOP' => 'Excel::Template::Container::Loop', + 'ROW' => 'Excel::Template::Container::Row', + 'SCOPE' => 'Excel::Template::Container::Scope', + 'WORKBOOK' => 'Excel::Template::Container::Workbook', + 'WORKSHEET' => 'Excel::Template::Container::Worksheet', + + 'CELL' => 'Excel::Template::Element::Cell', + 'FORMULA' => 'Excel::Template::Element::Formula', + 'VAR' => 'Excel::Template::Element::Var', + + 'FORMAT' => 'Excel::Template::Container::Format', + +# These are all the Format short-cut objects + 'BOLD' => 'Excel::Template::Container::Bold', + 'HIDDEN' => 'Excel::Template::Container::Hidden', + 'ITALIC' => 'Excel::Template::Container::Italic', + 'LOCKED' => 'Excel::Template::Container::Locked', + 'OUTLINE' => 'Excel::Template::Container::Outline', + 'SHADOW' => 'Excel::Template::Container::Shadow', + 'STRIKEOUT' => 'Excel::Template::Container::Strikeout', + +# These are the helper objects + + 'CONTEXT' => 'Excel::Template::Context', + 'ITERATOR' => 'Excel::Template::Iterator', + 'TEXTOBJECT' => 'Excel::Template::TextObject', + + 'CONTAINER' => 'Excel::Template::Container', + 'ELEMENT' => 'Excel::Template::Element', + + 'BASE' => 'Excel::Template::Base', +); + +%isBuildable = map { $_ => 1 } qw( + BOLD + CELL + FORMAT + FORMULA + IF + ITALIC + OUTLINE + LOOP + ROW + SHADOW + STRIKEOUT + VAR + WORKBOOK + WORKSHEET +); + +sub register +{ + my %params = @_; + + my @param_names = qw(name class isa); + for (@param_names) + { + unless ($params{$_}) + { + warn "$_ was not supplied to register()\n"; + return 0; + } + } + + my $name = uc $params{name}; + if (exists $Manifest{$name}) + { + warn "$params{name} already exists in the manifest.\n"; + return 0; + } + + my $isa = uc $params{isa}; + unless (exists $Manifest{$isa}) + { + warn "$params{isa} does not exist in the manifest.\n"; + return 0; + } + + $Manifest{$name} = $params{class}; + $isBuildable{$name} = 1; + + { + no strict 'refs'; + unshift @{"$params{class}::ISA"}, $Manifest{$isa}; + } + + return 1; +} + +sub create +{ + my $class = shift; + my $name = uc shift; + + return unless exists $Manifest{$name}; + + (my $filename = $Manifest{$name}) =~ s!::!/!g; + + eval { + require "$filename.pm"; + }; if ($@) { + die "Cannot find or compile PM file for '$name' ($filename)\n"; + } + + return $Manifest{$name}->new(@_); +} + +sub create_node +{ + my $class = shift; + my $name = uc shift; + + return unless exists $isBuildable{$name}; + + return $class->create($name, @_); +} + +sub isa +{ + return unless @_ >= 2; + exists $Manifest{uc $_[1]} + ? UNIVERSAL::isa($_[0], $Manifest{uc $_[1]}) + : UNIVERSAL::isa(@_) +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Factory + +=head1 PURPOSE + +=head1 NODE NAME + +=head1 INHERITANCE + +=head1 ATTRIBUTES + +=head1 CHILDREN + +=head1 AFFECTS + +=head1 DEPENDENCIES + +=head1 USAGE + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/Format.pm b/lib/Excel/Template/Format.pm new file mode 100644 index 0000000..dcd2a1f --- /dev/null +++ b/lib/Excel/Template/Format.pm @@ -0,0 +1,141 @@ +package Excel::Template::Format; + +use strict; + +# This is the format repository. Spreadsheet::WriteExcel does not cache the +# known formats. So, it is very possible to continually add the same format +# over and over until you run out of RAM or addressability in the XLS file. In +# real life, less than 10-20 formats are used, and they're re-used in various +# places in the file. This provides a way of keeping track of already-allocated +# formats and making new formats based on old ones. + +{ + # %_Parameters is a hash with the key being the format name and the value + # being the index/length of the format in the bit-vector. + my %_Formats = ( + bold => [ 0, 1 ], + italic => [ 1, 1 ], + locked => [ 2, 1 ], + hidden => [ 3, 1 ], + font_outline => [ 4, 1 ], + font_shadow => [ 5, 1 ], + font_strikeout => [ 6, 1 ], + ); + + sub _params_to_vec + { + my %params = @_; + $params{lc $_} = delete $params{$_} for keys %params; + + my $vec = ''; + vec( $vec, $_Formats{$_}[0], $_Formats{$_}[1] ) = ($params{$_} && 1) + for grep { exists $_Formats{$_} } + map { lc } keys %params; + + $vec; + } + + sub _vec_to_params + { + my ($vec) = @_; + + my %params; + while (my ($k, $v) = each %_Formats) + { + next unless vec( $vec, $v->[0], $v->[1] ); + $params{$k} = 1; + } + + %params; + } +} + +{ + my %_Formats; + + sub _assign { + $_Formats{$_[0]} = $_[1] unless exists $_Formats{$_[0]}; + $_Formats{$_[1]} = $_[0] unless exists $_Formats{$_[1]}; + } + + sub _retrieve_vec { ref($_[0]) ? ($_Formats{$_[0]}) : ($_[0]); } + sub _retrieve_format { ref($_[0]) ? ($_[0]) : ($_Formats{$_[0]}); } +} + +sub blank_format +{ + shift; + my ($context) = @_; + + my $blank_vec = _params_to_vec(); + + my $format = _retrieve_format($blank_vec); + return $format if $format; + + $format = $context->{XLS}->add_format; + _assign($blank_vec, $format); + $format; +} + +sub copy +{ + shift; + my ($context, $old_format, %properties) = @_; + + defined(my $vec = _retrieve_vec($old_format)) + || die "Internal Error: Cannot find vector for format '$old_format'!\n"; + + my $new_vec = _params_to_vec(%properties); + + $new_vec |= $vec; + + my $format = _retrieve_format($new_vec); + return $format if $format; + + $format = $context->{XLS}->add_format(_vec_to_params($new_vec)); + _assign($new_vec, $format); + $format; +} + +1; +__END__ + +Category Description Property Method Name Implemented +-------- ----------- -------- ----------- ----------- +Font Font type font set_font() + Font size size set_size() + Font color color set_color() + Bold bold set_bold() YES + Italic italic set_italic() YES + Underline underline set_underline() + Strikeout font_strikeout set_font_strikeout() YES + Super/Subscript font_script set_font_script() + Outline font_outline set_font_outline() YES + Shadow font_shadow set_font_shadow() YES + +Number Numeric format num_format set_num_format() + +Protection Lock cells locked set_locked() YES + Hide formulas hidden set_hidden() YES + +Alignment Horizontal align align set_align() + Vertical align valign set_align() + Rotation rotation set_rotation() + Text wrap text_wrap set_text_wrap() + Justify last text_justlast set_text_justlast() + Merge merge set_merge() + +Pattern Cell pattern pattern set_pattern() + Background color bg_color set_bg_color() + Foreground color fg_color set_fg_color() + +Border Cell border border set_border() + Bottom border bottom set_bottom() + Top border top set_top() + Left border left set_left() + Right border right set_right() + Border color border_color set_border_color() + Bottom color bottom_color set_bottom_color() + Top color top_color set_top_color() + Left color left_color set_left_color() + Right color right_color set_right_color() diff --git a/lib/Excel/Template/Iterator.pm b/lib/Excel/Template/Iterator.pm new file mode 100644 index 0000000..bdd74e2 --- /dev/null +++ b/lib/Excel/Template/Iterator.pm @@ -0,0 +1,211 @@ +package Excel::Template::Iterator; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Base); + + use Excel::Template::Base; +} + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + unless (Excel::Template::Factory::isa($self->{CONTEXT}, 'CONTEXT')) + { + die "Internal Error: No context object passed to ", __PACKAGE__, $/; + } + + $self->{MAXITERS} ||= 0; + + # This is the index we will work on NEXT, in whatever direction the + # iterator is going. + $self->{INDEX} = -1; + + # This is a short-circuit parameter to let the iterator function in a + # null state. + $self->{NO_PARAMS} = 0; + unless ($self->{NAME} =~ /\w/) + { + $self->{NO_PARAMS} = 1; + + warn "INTERNAL ERROR: 'NAME' was blank was blank when passed to ", __PACKAGE__, $/; + + return $self; + } + + # Cache the reference to the appropriate data. + $self->{DATA} = $self->{CONTEXT}->param($self->{NAME}); + + unless (UNIVERSAL::isa($self->{DATA}, 'ARRAY')) + { + $self->{NO_PARAMS} = 1; + warn "'$self->{NAME}' does not have a list of parameters", $/; + + return $self; + } + + unless (@{$self->{DATA}}) + { + $self->{NO_PARAMS} = 1; + } + + $self->{MAX_INDEX} = $#{$self->{DATA}}; + + return $self; +} + +sub enter_scope +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + for my $x ($self->{DATA}[$self->{INDEX}]) + { + $x->{uc $_} = delete $x->{$_} for keys %$x; + } + + push @{$self->{CONTEXT}{PARAM_MAP}}, $self->{DATA}[$self->{INDEX}]; + + return 1; +} + +sub exit_scope +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + # There has to be the base parameter map and at least the one that + # Iterator::enter_scope() added on top. + @{$self->{CONTEXT}{PARAM_MAP}} > 1 || + die "Internal Error: ", __PACKAGE__, "'s internal param_map off!", $/; + + pop @{$self->{CONTEXT}{PARAM_MAP}}; + + return 1; +} + +sub can_continue +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + return 1 if $self->more_params; + + return 0; +} + +sub more_params +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + return 1 if $self->{MAX_INDEX} > $self->{INDEX}; + + return 0; +} + +# Call this method BEFORE incrementing the index to the next value. +sub _do_globals +{ + my $self = shift; + + my $data = $self->{DATA}[$self->{INDEX}]; + + # Perl's arrays are 0-indexed. Thus, the first element is at index "0". + # This means that odd-numbered elements are at even indices, and vice-versa. + # This also means that MAX (the number of elements in the array) can never + # be the value of an index. It is NOT the last index in the array. + + $data->{'__FIRST__'} ||= ($self->{INDEX} == 0); + $data->{'__INNER__'} ||= (0 < $self->{INDEX} && $self->{INDEX} < $self->{MAX_INDEX}); + $data->{'__LAST__'} ||= ($self->{INDEX} == $self->{MAX_INDEX}); + $data->{'__ODD__'} ||= !($self->{INDEX} % 2); + + return 1; +} + +sub next +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + return 0 unless $self->more_params; + + $self->exit_scope; + + $self->{INDEX}++; + + $self->_do_globals; + + $self->enter_scope; + + return 1; +} + +sub back_up +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + $self->exit_scope; + + $self->{INDEX}--; + + $self->_do_globals; + + $self->enter_scope; + + return 1; +} + +sub reset +{ + my $self = shift; + + return 0 if $self->{NO_PARAMS}; + + $self->{INDEX} = -1; + + return 1; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::Iterator + +=head1 PURPOSE + +=head1 NODE NAME + +=head1 INHERITANCE + +=head1 ATTRIBUTES + +=head1 CHILDREN + +=head1 AFFECTS + +=head1 DEPENDENCIES + +=head1 USAGE + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/lib/Excel/Template/TextObject.pm b/lib/Excel/Template/TextObject.pm new file mode 100644 index 0000000..ac7a3d7 --- /dev/null +++ b/lib/Excel/Template/TextObject.pm @@ -0,0 +1,79 @@ +package Excel::Template::TextObject; + +use strict; + +BEGIN { + use vars qw(@ISA); + @ISA = qw(Excel::Template::Base); + + use Excel::Template::Base; + +# use Unicode::String; +} + +# This is a helper object. It is not instantiated by the user, +# nor does it represent an XML object. Rather, certain elements, +# such as , can use this object to do text with variable +# substitutions. + +sub new +{ + my $class = shift; + my $self = $class->SUPER::new(@_); + + $self->{STACK} = [] unless UNIVERSAL::isa($self->{STACK}, 'ARRAY'); + + return $self; +} + +sub resolve +{ + my $self = shift; + my ($context) = @_; + +# my $t = Unicode::String::utf8(''); + my $t = ''; + + for my $tok (@{$self->{STACK}}) + { + my $val = $tok; + $val = $val->resolve($context) + if Excel::Template::Factory::isa($val, 'VAR'); + +# $t .= Unicode::String::utf8("$val"); + $t .= $val; + } + + return $t; +} + +1; +__END__ + +=head1 NAME + +Excel::Template::TextObject + +=head1 PURPOSE + +=head1 NODE NAME + +=head1 INHERITANCE + +=head1 ATTRIBUTES + +=head1 CHILDREN + +=head1 AFFECTS + +=head1 DEPENDENCIES + +=head1 USAGE + +=head1 AUTHOR + +Rob Kinyon (rkinyon@columbus.rr.com) + +=head1 SEE ALSO + +=cut diff --git a/t/001_load.t b/t/001_load.t new file mode 100644 index 0000000..86fd2f1 --- /dev/null +++ b/t/001_load.t @@ -0,0 +1,12 @@ +# -*- perl -*- + +# t/001_load.t - check module loading and create testing directory + +use Test::More tests => 2; + +BEGIN { use_ok( 'Excel::Template' ); } + +my $object = Excel::Template->new (); +isa_ok ($object, 'Excel::Template'); + +