r13935@rob-kinyons-powerbook58: rob | 2006-06-02 16:19:43 -0400 v0.26
Rob Kinyon [Fri, 2 Jun 2006 20:21:28 +0000 (20:21 +0000)]
 Tagged 0.26

86 files changed:
Build.PL [new file with mode: 0644]
Changes [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
MANIFEST.SKIP [new file with mode: 0644]
README [new file with mode: 0644]
Todo [new file with mode: 0644]
lib/Excel/Template.pm [new file with mode: 0644]
lib/Excel/Template/Base.pm [new file with mode: 0644]
lib/Excel/Template/Container.pm [new file with mode: 0644]
lib/Excel/Template/Container/Bold.pm [new file with mode: 0644]
lib/Excel/Template/Container/Conditional.pm [new file with mode: 0644]
lib/Excel/Template/Container/Format.pm [new file with mode: 0644]
lib/Excel/Template/Container/Hidden.pm [new file with mode: 0644]
lib/Excel/Template/Container/Italic.pm [new file with mode: 0644]
lib/Excel/Template/Container/KeepLeadingZeros.pm [new file with mode: 0644]
lib/Excel/Template/Container/Locked.pm [new file with mode: 0644]
lib/Excel/Template/Container/Loop.pm [new file with mode: 0644]
lib/Excel/Template/Container/Outline.pm [new file with mode: 0644]
lib/Excel/Template/Container/Row.pm [new file with mode: 0644]
lib/Excel/Template/Container/Scope.pm [new file with mode: 0644]
lib/Excel/Template/Container/Shadow.pm [new file with mode: 0644]
lib/Excel/Template/Container/Strikeout.pm [new file with mode: 0644]
lib/Excel/Template/Container/Workbook.pm [new file with mode: 0644]
lib/Excel/Template/Container/Worksheet.pm [new file with mode: 0644]
lib/Excel/Template/Context.pm [new file with mode: 0644]
lib/Excel/Template/Element.pm [new file with mode: 0644]
lib/Excel/Template/Element/Backref.pm [new file with mode: 0755]
lib/Excel/Template/Element/Cell.pm [new file with mode: 0755]
lib/Excel/Template/Element/Formula.pm [new file with mode: 0644]
lib/Excel/Template/Element/FreezePanes.pm [new file with mode: 0644]
lib/Excel/Template/Element/Image.pm [new file with mode: 0644]
lib/Excel/Template/Element/Range.pm [new file with mode: 0755]
lib/Excel/Template/Element/Var.pm [new file with mode: 0644]
lib/Excel/Template/Factory.pm [new file with mode: 0644]
lib/Excel/Template/Format.pm [new file with mode: 0644]
lib/Excel/Template/Iterator.pm [new file with mode: 0644]
lib/Excel/Template/TextObject.pm [new file with mode: 0755]
t/001_load.t [new file with mode: 0644]
t/002.xml [new file with mode: 0644]
t/002_workbook.t [new file with mode: 0644]
t/003.xml [new file with mode: 0644]
t/003_worksheet.t [new file with mode: 0644]
t/004.xml [new file with mode: 0644]
t/004_cell.t [new file with mode: 0644]
t/005.xml [new file with mode: 0644]
t/005_formats.t [new file with mode: 0644]
t/006.xml [new file with mode: 0644]
t/006_variables.t [new file with mode: 0644]
t/007.xml [new file with mode: 0644]
t/007_cell_formats.t [new file with mode: 0644]
t/008.xml [new file with mode: 0644]
t/008_formula.t [new file with mode: 0644]
t/009.xml [new file with mode: 0644]
t/009_loop.t [new file with mode: 0644]
t/010.xml [new file with mode: 0644]
t/010_scope.t [new file with mode: 0644]
t/011.xml [new file with mode: 0644]
t/011_conditional.t [new file with mode: 0644]
t/012.xml [new file with mode: 0644]
t/012_backref.t [new file with mode: 0644]
t/013.xml [new file with mode: 0644]
t/013_range.t [new file with mode: 0644]
t/014.xml [new file with mode: 0644]
t/014_heightwidth.t [new file with mode: 0644]
t/015.xml [new file with mode: 0644]
t/015_cell_type.t [new file with mode: 0644]
t/016.xml [new file with mode: 0644]
t/016_renderers.t [new file with mode: 0644]
t/017_filehandle.t [new file with mode: 0644]
t/018_register.t [new file with mode: 0644]
t/019_output.t [new file with mode: 0644]
t/020_worksheet_attributes.t [new file with mode: 0644]
t/021_loop_error.t [new file with mode: 0644]
t/022_keep_leading_zeros.t [new file with mode: 0644]
t/023_relative_values.t [new file with mode: 0644]
t/024_image.t [new file with mode: 0644]
t/025_freezepanes.t [new file with mode: 0644]
t/998_pod.t [new file with mode: 0644]
t/999_pod_coverage.t [new file with mode: 0644]
t/Register_018.pm [new file with mode: 0644]
t/Spreadsheet/WriteExcel.pm [new file with mode: 0644]
t/Spreadsheet/WriteExcel/Big.pm [new file with mode: 0644]
t/Spreadsheet/WriteExcel/Worksheet.pm [new file with mode: 0644]
t/Spreadsheet/WriteExcelXML.pm [new file with mode: 0644]
t/mock.pm [new file with mode: 0644]

diff --git a/Build.PL b/Build.PL
new file mode 100644 (file)
index 0000000..2837695
--- /dev/null
+++ b/Build.PL
@@ -0,0 +1,50 @@
+use Module::Build;
+
+use strict;
+
+my $requires = {
+    'perl'              => '5.6.0',
+    'Test::More'        => 0.01,
+    'XML::Parser'       => 0.01,
+    'IO::Scalar'        => 0.01,
+    'File::Basename'    => 0.01,
+    'Spreadsheet::WriteExcel' => 0.42,
+    'Spreadsheet::WriteExcel::Utility' => 0.01,
+};
+
+if ($] < 5.008) {
+    eval { require Unicode::String; };
+    if ($@) {
+       print "Note: If you want to work with Unicode, you will need to install";
+       print "the optional module Unicode::String and set USE_UNICODE to true.";
+       my $resp = Module::Build->prompt( "Do you want to install it now?", "y");
+       if ( $resp =~ /y/i ) {
+           $requires->{'Unicode::String'} => '0.01';
+       }
+    } else {
+        print "You have Unicode::String installed. If you warn to use Unicode,";
+        print "You will need to set USE_UNICODE to true."
+    }
+}
+
+my $build = Module::Build->new(
+    module_name => 'Excel::Template',
+    license => 'perl',
+    requires => $requires,
+    optional => {
+    },
+    build_requires => {
+        'File::Path'      => '0.01',
+        'File::Temp'      => '0.01',
+        'Test::Deep'      => '0.095',
+        'Test::More'      => '0.47',
+        'Test::Exception' => '0.21',
+    },
+    create_makefile_pl => 'traditional',
+    add_to_cleanup => [
+        'META.yml', '*.bak', '*.gz', 'Makefile.PL', 'cover_db',
+    ],
+#    test_files => 't/??_*.t',
+);
+
+$build->create_build_script;
diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..768b7f7
--- /dev/null
+++ b/Changes
@@ -0,0 +1,145 @@
+Revision history for Perl distribution Excel::Template
+
+0.26 Fri Jun 02 15:30:00 2005
+    - Fixed how widths are whitelisted to allow '.' for fractions
+    - Fixed how certain formats are copied
+    - Added <image>
+    - Added <freezepanes>
+
+0.25 Thu May 26 11:00:00 2005
+    - Changed how the template file is opened to use 3-arg open() if available
+
+0.24 Thu Mar 10 11:00:00 2005
+    - Implemented the KEEP_LEADING_ZEROS node
+      - This wraps the keep_leading_zeros() worksheet method
+    - Improved code coverage with more and better tests
+      - Am now above 90% coverage.
+      - Fixed bug involving relative values.
+      - Fixed bug involving formats when multiple Excel-Template objects ran in
+        the same process.
+    - Improved POD linking
+      - Every module/node reference in POD should link to the appropriate POD
+
+0.23 Fri Feb 25 15:00:00 2005
+    - Improved code coverage with more and better tests
+    - Fixed POD bug with Devel::Cover results
+    - Fixed bugs found in Factory::register while writing tests
+      - Base class wasn't loaded when class was registered.
+      - If registered class didn't exist, wouldn't die until parse()
+
+0.22 Thu Feb 24 15:00:00 2005
+    - new() now accepts either FILE or FILENAME
+    - parse() now accepts a filehandle or a filename
+
+0.21 Thu Feb 24 12:00:00 2005
+    - Fixed documentation bug in BACKREF (Thanks to Paul Williams)
+    - Added code to Makefile.PL to skip .swp files in the Makefile
+    - Added RENDERER option to new()
+      - Deprecated BIG_FILE
+    - Added pod.t and pod_coverage.t
+    - Tests now run under 5.005_02 without warnings
+
+0.20 Wed Jan 26 12:00:00 2005
+    - Removed PM_FILTER by adding an optional USE_UNICODE runtime parameter.
+    - Added a "type" attribute to CELL to allow printing by the write_*() family
+
+0.19 Wed Dec 08 12:00:00 2004
+    - Fixed META.yml
+    - Added more values to the MANIFEST.SKIP
+    - Fixed PM_FILTER to work on Win32 (Thanks, Corion!)
+    - Improved POD
+
+0.18 Fri Nov 12 14:45:00 2004
+    - Removed 'use warnings' from all tests
+    - All warnings are suppressed unless $^W is true
+    - Added 'height' value for ROW
+    - Added 'width' value for CELL
+    - Fixed PM_FILTER to work on Redhat
+
+0.17 Sat Nov 06 23:30:00 2004
+    - Added worksheet protection
+    - Fixed several bugs found by adding more tests
+        - SCOPE node actually works
+        - CONDITIONAL / IF now handles string values correctly
+
+0.16 Fri Nov 05 13:30:00 2004
+    - Fixed Makefile.PL so that it uses PM_FILTER instead of rolling its own
+        - This means that the Unicode handling is cleaner from a source
+          perspective
+    - Added MANIFEST.SKIP to skip /.svn/ and 'vi' .swp files during distcheck
+    - Finally have a semi-real testing suite!
+        - Added minimal Spreadsheet::WriteExcel mockobject
+    - Fixed several bugs in formats found by building tests
+      - HIDDEN node now actually works
+      - LOCKED node now actually works
+
+0.15 Thu Nov 04 16:15:00 2004
+    - Fixed bugs that were:
+        - preventing a worksheet from using a variable name in a loop
+        - allowing two worksheets to have the same name
+        - preventing a worksheet from being called '0' or '0.0'
+    - Added back-references. This allows for one cell to refer to another
+      cell in an Excel-aware way, especially in formulas.
+    - Added the following nodes:
+        - BACKREF
+        - RANGE
+
+0.14 Thu Nov 04 13:30:00 2004
+    - Added new format support for (nearly) all formats supported by
+      Spreadsheet::WriteExcel
+    - Fixed email address everywhere
+
+0.13 Thu Oct 29 07:30:00 2004
+    - Fixed email address and added GoogleGroup
+
+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 (file)
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 (file)
index 0000000..5a83c33
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,87 @@
+Build.PL
+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/KeepLeadingZeros.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/Backref.pm
+lib/Excel/Template/Element/Cell.pm
+lib/Excel/Template/Element/Formula.pm
+lib/Excel/Template/Element/FreezePanes.pm
+lib/Excel/Template/Element/Image.pm
+lib/Excel/Template/Element/Range.pm
+lib/Excel/Template/Element/Var.pm
+t/001_load.t
+t/002_workbook.t
+t/002.xml
+t/003_worksheet.t
+t/003.xml
+t/004_cell.t
+t/004.xml
+t/005_formats.t
+t/005.xml
+t/006_variables.t
+t/006.xml
+t/007_cell_formats.t
+t/007.xml
+t/008_formula.t
+t/008.xml
+t/009_loop.t
+t/009.xml
+t/010_scope.t
+t/010.xml
+t/011_conditional.t
+t/011.xml
+t/012_backref.t
+t/012.xml
+t/013_range.t
+t/013.xml
+t/014_heightwidth.t
+t/014.xml
+t/015_cell_type.t
+t/015.xml
+t/016_renderers.t
+t/016.xml
+t/017_filehandle.t
+t/018_register.t
+t/019_output.t
+t/020_worksheet_attributes.t
+t/021_loop_error.t
+t/022_keep_leading_zeros.t
+t/023_relative_values.t
+t/024_image.t
+t/025_freezepanes.t
+t/998_pod.t
+t/999_pod_coverage.t
+t/mock.pm
+t/Register_018.pm
+t/Spreadsheet/WriteExcel.pm
+t/Spreadsheet/WriteExcelXML.pm
+t/Spreadsheet/WriteExcel/Worksheet.pm
+t/Spreadsheet/WriteExcel/Big.pm
+Makefile.PL
+META.yml                                 Module meta-data (added by MakeMaker)
diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP
new file mode 100644 (file)
index 0000000..b008153
--- /dev/null
@@ -0,0 +1,19 @@
+^_build
+^Build$
+^blib
+~$
+\.bak$
+^MANIFEST\.SKIP$
+CVS
+\.svn
+cover_db
+\..*\.sw.?$
+^Makefile$
+^pm_to_blib$
+^MakeMaker-\d
+^blibdirs$
+\.old$
+^#.*#$
+^\.#
+^\.DS_Store
+^__MACOSX
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..7b2dd94
--- /dev/null
+++ b/README
@@ -0,0 +1,6 @@
+Excel::Template is a layout system to use the data structure from HTML::Template
+and create a Microsoft Excel file.
+
+CAVEAT: All limitations stated in Spreadsheet::WriteExcel are in force, as that
+is the module used for rendering. If the XLS file is corrupted, I would first
+make sure you aren't doing anything that it says is bad.
diff --git a/Todo b/Todo
new file mode 100644 (file)
index 0000000..f076482
--- /dev/null
+++ b/Todo
@@ -0,0 +1,61 @@
+TODO list for Perl distribution Excel::Template
+
+- Add colspan (COL), rowspan (ROW), width (CELL), and height (ROW)
+- Anything else people suggest
+
+Missing S::WE features:
+  Workbook
+    set_tempdir
+    add_char_ext
+    set_custom_color
+    set_1904
+    set_codepage
+  Worksheet
+    write_unicode/write_unicode_le
+    write_url
+    write_url_range
+    write_date_time
+    write_comment
+    activate/select/set_first_sheet
+    set_selection
+    outline_settings
+    thaw_panes
+    merge_range
+    set_zoom
+  Page Setup
+    set_landscape/portrait
+    set_paper
+    center_horizontally/center_vertically
+    set_margins
+    set_header/set_footer
+    repeat_rows/repeat_columns
+    hide_gridlines
+    print_row_col_headers
+    print_area
+    fit_to_pages
+    set_print_scale
+    set_v_pagebreaks/set_h_pagebreaks
+  Formats
+    colors
+    Support for builtin formats
+    set_center_across
+    set_text_wrap
+    set_rotation
+    set_indent
+    set_shrink
+    set_text_justlast
+    set_pattern
+    set_bg_color
+    set_fg_color
+    set_border
+    set_border_color
+    user-defined formats
+  Outlines / Grouping
+    for rows
+    for columns
+  Handling for maximums
+    Maximum number of chars in a string  32767
+    Maximum number of columns            256
+    Maximum number of rows               65536
+    Maximum chars in a sheet name        31
+    Maximum chars in a header/footer     254
diff --git a/lib/Excel/Template.pm b/lib/Excel/Template.pm
new file mode 100644 (file)
index 0000000..453ec74
--- /dev/null
@@ -0,0 +1,524 @@
+package Excel::Template;
+
+use strict;
+
+BEGIN {
+    use Excel::Template::Base;
+    use vars qw ($VERSION @ISA);
+
+    $VERSION  = '0.26';
+    @ISA      = qw( Excel::Template::Base );
+}
+
+use File::Basename;
+use XML::Parser;
+use IO::Scalar;
+
+use constant RENDER_NML => 'normal';
+use constant RENDER_BIG => 'big';
+use constant RENDER_XML => 'xml';
+
+my %renderers = (
+    RENDER_NML, 'Spreadsheet::WriteExcel',
+    RENDER_BIG, 'Spreadsheet::WriteExcel::Big',
+    RENDER_XML, 'Spreadsheet::WriteExcelXML',
+);
+
+sub new
+{
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+
+    $self->{FILE} = $self->{FILENAME}
+        if !defined $self->{FILE} && defined $self->{FILENAME};
+
+    $self->parse_xml($self->{FILE})
+        if defined $self->{FILE};
+
+    my @renderer_classes = ( 'Spreadsheet::WriteExcel' );
+
+    if (exists $self->{RENDERER} && $self->{RENDERER})
+    {
+        if (exists $renderers{ lc $self->{RENDERER} })
+        {
+            unshift @renderer_classes, $renderers{ lc $self->{RENDERER} };
+        }
+        elsif ($^W)
+        {
+            warn "'$self->{RENDERER}' is not recognized\n";
+        }
+    }
+    elsif (exists $self->{BIG_FILE} && $self->{BIG_FILE})
+    {
+        warn "Use of BIG_FILE is deprecated.\n";
+        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" if $^W;
+        } else {
+            $self->{RENDERER} = $class;
+            last;
+        }
+    }
+
+    defined $self->{RENDERER} ||
+        die "Could not find a renderer class. Tried:\n\t" .
+            join("\n\t", @renderer_classes) .
+            "\n";
+
+    $self->{USE_UNICODE} = ~~0
+        if $] >= 5.008;
+
+    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 ref $_[0] eq '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";
+
+    eval {
+        $self->_prepare_output($xls);
+    };
+print $@ if $@;
+
+    $xls->close;
+
+    return if $@;
+
+    return ~~1;
+}
+
+sub output
+{
+    my $self = shift;
+
+    my $output;
+    tie *XLS, 'IO::Scalar', \$output;
+
+    $self->write_file(\*XLS)
+        or return;
+
+    return $output;
+}
+
+sub parse_xml
+{
+    my $self = shift;
+    my ($file) = @_;
+
+    my @stack;
+    my @parms = (
+        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 ( $node->isa( 'WORKBOOK' ) )
+                {
+                    $self->{WORKBOOK} = $node;
+                }
+                elsif ( $node->is_embedded )
+                {
+                    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]);
+            },
+        },
+    );
+
+    if ( ref $file )
+    {
+        *INFILE = $file;
+    }
+    else
+    {
+        my ($filename, $dirname) = fileparse($file);
+        push @parms, Base => $dirname;
+
+        eval q{
+            open( INFILE, '<', $file )
+                || die "Cannot open '$file' for reading: $!\n";
+        }; if ( $@ ) {
+           if ( $@ =~ /Too many arguments for open/ ) {
+                open( INFILE, "< $file" )
+                    || die "Cannot open '$file' for reading: $!\n";
+            } else {
+                die $@;
+            }
+        }
+    }
+
+    my $parser = XML::Parser->new( @parms );
+    $parser->parse(do { local $/ = undef; <INFILE> });
+
+    close INFILE
+        unless ref $file;
+
+    return ~~1;
+}
+*parse = *parse = \&parse_xml;
+
+sub _prepare_output
+{
+    my $self = shift;
+    return unless $self->{WORKBOOK};
+
+    my ($xls) = @_;
+
+    my $context = Excel::Template::Factory->_create(
+        'CONTEXT',
+
+        XLS       => $xls,
+        PARAM_MAP => [ $self->{PARAM_MAP} ],
+        UNICODE   => $self->{UNICODE},
+    );
+
+    $self->{WORKBOOK}->render($context);
+
+    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:
+
+  <workbook>
+      <worksheet name="tester">
+          <cell text="$HOME" />
+          <cell text="$PATH" />
+      </worksheet>
+  </workbook>
+
+Now, create a small program to use it:
+
+  #!/usr/bin/perl -w
+
+  use strict;
+
+  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 called text.xls in your working 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 L<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 L<HTML::Template>.
+
+=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. L<HTML::Template> provides the first, and L<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.
+
+=head3 Parameters
+
+=over 4
+
+=item * FILE / FILENAME
+
+Excel::Template will parse the template in the given file or filehandle automatically. (You can also use the parse() method, described below.)
+
+If you want to use the __DATA__ section, you can do so by passing
+
+  FILE => \*DATA
+
+=item * RENDERER
+
+The default rendering engine is L<Spreadsheet::WriteExcel>. You may, if you choose, change that to another choice. The legal values are:
+
+=over 4
+
+=item * Excel::Template->RENDER_NML
+
+This is the default of L<Spreadsheet::WriteExcel>.
+
+=item * Excel::Template->RENDER_BIG
+
+This attempts to load L<Spreadsheet::WriteExcel::Big>.
+
+=item * Excel::Template->RENDER_XML
+
+This attempts to load L<Spreadsheet::WriteExcelXML>.
+
+=back
+
+=item * USE_UNICODE
+
+This will use L<Unicode::String> to represent strings instead of Perl's internal string handling. You must already have L<Unicode::String> installed on your system.
+
+The USE_UNICODE parameter will be ignored if you are using Perl 5.8 or higher as Perl's internal string handling is unicode-aware.
+
+NOTE: Certain older versions of L<OLE::Storage_Lite> and mod_perl clash for some reason. Upgrading to the latest version of L<OLE::Storage_Lite> should fix the problem.
+
+=back
+
+=head3 Deprecated
+
+=over 4
+
+=item * BIG_FILE
+
+Instead, use RENDERER => Excel::Template->RENDER_BIG
+
+=back
+
+=head2 param()
+
+This method is exactly like L<HTML::Template>'s param() method.
+
+=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.
+
+If a filename is passed in (vs. a filehandle), the directory name will be passed in to L<XML::Parser> as the I<Base> parameter. This will allow for XML directives to work as expected.
+
+=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 L<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.)
+
+=head2 register()
+
+This allows you to register a class as handling a node. q.v. L<Excel::Template::Factory> for more info.
+
+=head1 SUPPORTED NODES
+
+This is a partial 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-type tag will write into.
+
+=over 4
+
+=item * L<WORKBOOK|Excel::Template::Container::Workbook>
+
+This is the node representing the workbook. It is the parent for all other nodes.
+
+=item * L<WORKSHEET|Excel::Template::Container::Worksheet>
+
+This is the node representing a given worksheet.
+
+=item * L<IF|Excel::Template::Container::Conditional>
+
+This node represents a conditional expression. Its children may or may not be rendered. It behaves just like L<HTML::Template>'s TMPL_IF.
+
+=item * L<LOOP|Excel::Template::Container::Loop>
+
+This node represents a loop. It behaves just like L<HTML::Template>'s TMPL_LOOP.
+
+=item * L<ROW|Excel::Template::Container::Row>
+
+This node represents a row of data. This is the 1 in A1. There is no COLUMN node, as of yet.
+
+=item * L<FORMAT|Excel::Template::Container::Format>
+
+This node varies the format for its children. All formatting options supported in L<Spreadsheet::WriteExcel> are supported here. There are also a number of formatting shortcuts, such as L<BOLD|Excel::Template::Container::Bold> and L<ITALIC|Excel::Template::Container::Italic>.
+
+=item * L<BACKREF|Excel::Template::Element::Backref>
+
+This refers back to a cell previously named.
+
+=item * L<CELL|Excel::Template::Element::Cell>
+
+This is the actual cell in a spreadsheet.
+
+=item * L<FORMULA|Excel::Template::Element::Formula>
+
+This is a formula in a spreadsheet.
+
+=item * L<RANGE|Excel::Template::Element::Range>
+
+This is a BACKREF for a number of identically-named cells.
+
+=item * L<VAR|Excel::Template::Element::Var>
+
+This is a variable. It is generally used when the 'text' attribute isn't
+sufficient.
+
+=back
+
+=head1 BUGS
+
+None, that I know of.
+
+=head1 SUPPORT
+
+This is production quality software, used in several production web applications.
+
+=head1 AUTHOR
+
+    Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 CONTRIBUTORS
+
+There is a mailing list at http://groups.google.com/group/ExcelTemplate or exceltemplate@googlegroups.com
+
+=head2 Robert Graff
+
+=over 4
+
+=item * Finishing formats
+
+=item * Fixing several bugs in worksheet naming
+
+=back
+
+=head1 TEST COVERAGE
+
+I use L<Devel::Cover> to test the coverage of my tests. Every release, I intend to improve these numbers.
+
+Excel::Template is also part of the CPAN Kwalitee initiative, being one of the top 100 non-core modules downloaded from CPAN. If you wish to help out, please feel free to contribute tests, patches, and/or suggestions.
+
+  ---------------------------- ------ ------ ------ ------ ------ ------ ------
+  File                           stmt   bran   cond    sub    pod   time  total
+  ---------------------------- ------ ------ ------ ------ ------ ------ ------
+  blib/lib/Excel/Template.pm     93.8   60.0   58.8  100.0  100.0   24.6   83.3
+  ...ib/Excel/Template/Base.pm   94.4   50.0    n/a  100.0    0.0    7.0   80.0
+  ...cel/Template/Container.pm  100.0   50.0   33.3  100.0    0.0    4.1   83.3
+  ...emplate/Container/Bold.pm  100.0    n/a    n/a  100.0    0.0    0.3   95.0
+  .../Container/Conditional.pm   95.9   90.0   66.7  100.0    0.0    1.2   91.0
+  ...plate/Container/Format.pm  100.0    n/a    n/a  100.0    0.0    0.5   96.6
+  ...plate/Container/Hidden.pm  100.0    n/a    n/a  100.0    0.0    0.0   95.0
+  ...plate/Container/Italic.pm  100.0    n/a    n/a  100.0    0.0    0.1   95.0
+  ...ainer/KeepLeadingZeros.pm  100.0  100.0    n/a  100.0    0.0    0.1   96.3
+  ...plate/Container/Locked.pm  100.0    n/a    n/a  100.0    0.0    0.0   95.0
+  ...emplate/Container/Loop.pm   96.8   50.0   50.0  100.0    0.0    0.3   82.7
+  ...late/Container/Outline.pm  100.0    n/a    n/a  100.0    0.0    0.0   95.0
+  ...Template/Container/Row.pm  100.0   75.0    n/a  100.0    0.0    0.2   90.6
+  ...mplate/Container/Scope.pm  100.0    n/a    n/a  100.0    n/a    0.0  100.0
+  ...plate/Container/Shadow.pm  100.0    n/a    n/a  100.0    0.0    0.0   95.0
+  ...te/Container/Strikeout.pm  100.0    n/a    n/a  100.0    0.0    0.0   95.0
+  ...ate/Container/Workbook.pm  100.0    n/a    n/a  100.0    n/a    2.6  100.0
+  ...te/Container/Worksheet.pm   94.7   75.0    n/a  100.0    0.0    1.0   87.1
+  ...Excel/Template/Context.pm   98.0   80.0   75.0  100.0   73.3   21.4   90.7
+  ...Excel/Template/Element.pm  100.0    n/a    n/a  100.0    n/a    0.3  100.0
+  ...mplate/Element/Backref.pm  100.0   50.0   33.3  100.0    0.0    0.4   87.1
+  .../Template/Element/Cell.pm   97.9   75.0   80.0  100.0    0.0    3.5   88.6
+  ...mplate/Element/Formula.pm  100.0    n/a    n/a  100.0    0.0    0.2   94.1
+  ...te/Element/FreezePanes.pm  100.0    n/a    n/a  100.0    0.0    0.0   95.5
+  ...Template/Element/Image.pm  100.0  100.0    n/a  100.0    0.0    0.1   94.3
+  ...Template/Element/Range.pm  100.0   66.7    n/a  100.0    0.0    0.1   88.9
+  ...l/Template/Element/Var.pm  100.0    n/a    n/a  100.0    0.0    0.0   94.1
+  ...Excel/Template/Factory.pm  100.0   73.1   66.7  100.0  100.0   23.5   91.4
+  .../Excel/Template/Format.pm   98.4   75.0   33.3  100.0   66.7    5.4   90.5
+  ...xcel/Template/Iterator.pm   98.6   80.0   70.6  100.0   50.0    1.1   88.8
+  ...el/Template/TextObject.pm   92.9   62.5   33.3  100.0    0.0    1.8   80.9
+  Total                          97.8   74.3   63.5  100.0   35.7  100.0   89.4
+  ---------------------------- ------ ------ ------ ------ ------ ------ ------
+
+=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), L<HTML::Template>, L<Spreadsheet::WriteExcel>
+
+=cut
diff --git a/lib/Excel/Template/Base.pm b/lib/Excel/Template/Base.pm
new file mode 100644 (file)
index 0000000..32e7236
--- /dev/null
@@ -0,0 +1,133 @@
+package Excel::Template::Base;
+
+use strict;
+
+use Excel::Template::Factory;
+
+sub new
+{
+    my $class = shift;
+
+    push @_, %{shift @_} while ref $_[0] eq '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;
+}
+
+*isa = *isa = \&Excel::Template::Factory::isa;
+*is_embedded = *is_embedded = \&Excel::Template::Factory::is_embedded;
+
+#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 {};
+}
+
+# Everyone seems to have their own versions.
+# Maybe, it's part of the API to require that you have the right one of these
+# defined?
+#sub resolve
+#{
+#    my $self = shift;
+#    my ($context) = @_;
+#
+#    '';
+#}
+#
+#sub render
+#{
+#    my $self = shift;
+#    my ($context) = @_;
+#
+#    1;
+#}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Base - Excel::Template::Base
+
+=head1 PURPOSE
+
+Base class for all Excel::Template classes
+
+=head1 NODE NAME
+
+None
+
+=head1 INHERITANCE
+
+None
+
+=head1 ATTRIBUTES
+
+None
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 METHODS
+
+=head2 calculate
+
+This is a wrapper around Excel::Template::Context->get()
+
+=head2 isa
+
+This is a wrapper around Excel::Template::Factory->isa()
+
+=head2 is_embedded
+
+This is a wrapper around Excel::Template::Factory->is_embedded()
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/Excel/Template/Container.pm b/lib/Excel/Template/Container.pm
new file mode 100644 (file)
index 0000000..1693609
--- /dev/null
@@ -0,0 +1,151 @@
+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 exists $self->{ELEMENTS} &&
+            ref $self->{ELEMENTS} eq 'ARRAY';
+
+    return $self;
+}
+
+# Removed as unused code
+#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);
+#}
+
+# Removed as unused code
+#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 (rob.kinyon@gmail.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 (file)
index 0000000..6e53931
--- /dev/null
@@ -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
+
+  <bold>
+    ... Children here
+  </bold>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..0717e6c
--- /dev/null
@@ -0,0 +1,181 @@
+package Excel::Template::Container::Conditional;
+
+#GGG Convert <conditional> to be a special case of <switch>?
+
+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 ref $val eq 'ARRAY';
+    $val = ${$val} while ref $val eq 'SCALAR';
+
+    my $value = $context->get($self, 'VALUE');
+    if (defined $value)
+    {
+        my $op = $context->get($self, 'OP');
+        $op = defined $op && exists $isOp{$op}
+            ? $isOp{$op}
+            : '==';
+
+        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 '$op' in conditional resolve", $/;
+        }
+
+        return $res && 1;
+    }
+
+    my $istrue = $val && 1;
+
+    my $is = uc $context->get($self, 'IS') || 'TRUE';
+    if ($is eq 'TRUE')
+    {
+        return 0 unless $istrue;
+    }
+    else
+    {
+        warn "Conditional 'is' value was [$is], defaulting to 'FALSE'" . $/
+            if $is ne 'FALSE' && $^W;
+
+        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
+
+L<CONTAINER|Excel::Template::Container>
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item * NAME
+
+This is the name of the parameter to test. It is resolved like any other parameter name. (q.v. L<VAR|Excel::Template::Element::Var> for more info.)
+
+=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 Perl the numeric comparison operators or the string comparison operators. All 6 of each kind is supported.
+
+B<Note>: If you want to use < or <=, you must instead use &lt; or &lt;=. This is to make sure it will parse with L<XML::Parser>. You should not need to use &gt; or &gt;= instead of > and >=, respectively.
+
+=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
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <if name="__ODD__" is="false">
+    ... Children here
+  </if>
+
+In the above example, the children will be executed if the value of __ODD__ (which is set by the L<LOOP|Excel::Template::Container::Loop> node) is false. So, for all even iterations.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+L<LOOP|Excel::Template::Container::Loop>, L<VAR|Excel::Template::Element::Var>
+
+=cut
diff --git a/lib/Excel/Template/Container/Format.pm b/lib/Excel/Template/Container/Format.pm
new file mode 100644 (file)
index 0000000..9e7da80
--- /dev/null
@@ -0,0 +1,227 @@
+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 = $context->format_object->copy(
+        $context, $old_format,
+
+        %{$self},
+    );
+    $context->active_format($format);
+
+    my $child_success = $self->iterate_over_children($context);
+
+    $context->active_format($old_format);
+
+    return $child_success;
+}
+
+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
+
+Boolean attributes should be set to 1, 0, true, or false.
+
+Color values can be the color name or the color index. See http://search.cpan.org/~jmcnamara/Spreadsheet-WriteExcel-2.11/lib/Spreadsheet/WriteExcel.pm#COLOURS_IN_EXCEL
+
+=over 4
+
+=item * align
+
+Set to either left, center, right, fill, or justify. Default is left.  See also valign.
+
+=item * bg_color
+
+Set to a color value. Default is none.
+
+=item * bold
+
+This will set bold to on or off, depending on the boolean value.
+
+=item * border
+
+Set the border for all for edges of a cell. Also see bottom, top, left, and right.
+Valid values are 0 - 7. 
+
+http://search.cpan.org/~jmcnamara/Spreadsheet-WriteExcel-2.11/lib/Spreadsheet/WriteExcel.pm#set_border()
+
+=item * border_color
+
+Sets the color value for the border. See also border, top_color, bottom_color, left_color
+and right_color.
+
+=item * bottom
+
+See border.
+
+=item * bottom_color
+
+See border_color
+
+=item * color
+
+This will set the color of the text, depending on color value. Default is black.
+
+=item * fg_color
+
+Set to a color value. This color will be used in foreground of some patterns. See color
+to change the color of text. Also see bg_color and pattern.
+
+=item * font
+
+This will sent the font face. Default is Arial.
+
+=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). This only applies to Excel for Macintosh.
+
+=item * font_strikeout
+
+This will set font_strikeout to on or off, depending on the boolean value. (q.v.
+STRIKEOUT tag)
+
+=item * hidden
+
+This will set whether the cell is hidden to on or off, depending on the boolean
+value.
+
+=item * indent
+
+Set the indentation level for a cell. Positive integers are allowed.
+
+=item * italic
+
+This will set italic to on or off, depending on the boolean value. (q.v. ITALIC
+tag)
+
+=item * left
+
+See border.
+
+=item * left_color
+
+See border_color.
+
+=item * num_format
+
+Set to the index of one of Excel's built-in number formats. See http://search.cpan.org/~jmcnamara/Spreadsheet-WriteExcel-2.11/lib/Spreadsheet/WriteExcel.pm#set_num_format()
+
+=item * pattern
+
+Set to an integer, 0 - 18. Sets the background fill pattern of a ell. Default is 1, solid.
+
+=item * right
+
+See border.
+
+=item * right_color
+
+See border color.
+
+=item * rotation
+
+Set the rotation of the text in a cell. The rotation can be any angle in the range -90 to 90 degrees. 
+The angle 270 is also supported. This indicates text where the letters run from top to bottom.
+
+=item * shrink
+
+A boolean value. If true, text will shrink to fit a cell.
+
+=item * size
+
+This will set the size of the font. Default is 10. Unless a row height is 
+specifically set, the row will grow taller as necessary.
+
+=item * text_justlast
+
+A boolean value to justify the last line. Only applies to Far Eastern versions of Excel.
+
+=item * text_wrap
+
+A boolean value. When set to true, text will wrap in a cell instead of crossing over
+into empty cells. If the row height is not set, the row will grow taller to accomodate
+the wrapping text.
+
+=item * top
+
+See border.
+
+=item * top_color
+
+See border_color
+
+=item * valign
+
+Set to top, vcenter, bottom, or vjustify. Default is vcenter. See also align.
+
+=back
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <format bold="1">
+    ... Children here
+  </format>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..351548c
--- /dev/null
@@ -0,0 +1,76 @@
+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
+
+You must have protected the worksheet containing any cells that are affected by
+this format. Otherwise, this node will have no effect.
+
+=head1 USAGE
+
+  <hidden>
+    ... Children here
+  </hidden>
+
+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 (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+WORKSHEET, FORMAT
+
+=cut
diff --git a/lib/Excel/Template/Container/Italic.pm b/lib/Excel/Template/Container/Italic.pm
new file mode 100644 (file)
index 0000000..e74439b
--- /dev/null
@@ -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
+
+  <italic>
+    ... Children here
+  </italic>
+
+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 (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+FORMAT
+
+=cut
diff --git a/lib/Excel/Template/Container/KeepLeadingZeros.pm b/lib/Excel/Template/Container/KeepLeadingZeros.pm
new file mode 100644 (file)
index 0000000..7c5edad
--- /dev/null
@@ -0,0 +1,94 @@
+package Excel::Template::Container::KeepLeadingZeros;
+
+use strict;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Container);
+
+    use Excel::Template::Container;
+}
+
+sub render
+{
+    my $self = shift;
+    my ($context) = @_;
+
+    my $worksheet = $context->active_worksheet;
+
+    $worksheet
+        ? $worksheet->keep_leading_zeros( 1 )
+        : $context->mark( keep_leading_zeros => 1 );
+
+    my $rv = $self->SUPER::render($context);
+
+    $worksheet
+        ? $worksheet->keep_leading_zeros( 0 )
+        : $context->mark( keep_leading_zeros => 0 );
+
+    return $rv;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Container::KeepLeadingZeros - Excel::Template::Container::KeepLeadingZeros
+
+=head1 PURPOSE
+
+To set the keep_leading_zeros flag for the surrounding worksheet or any worksheets that might be contained within this node.
+
+=head1 NODE NAME
+
+KEEP_LEADING_ZEROS
+
+=head1 INHERITANCE
+
+L<CONTAINER|Excel::Template::Container>
+
+=head1 ATTRIBUTES
+
+None
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+Alters how leading zeros are interpreted by L<Spreadsheet::WriteExcel>.
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <worksheet>
+    ... Cells here will NOT have leading-zeros preserved
+    <keep_leading_zeros>
+      ... Cells here will have leading-zeros preserved
+    </keep_leading_zeros>
+    ... Cells here will NOT have leading-zeros preserved
+  </worksheet>
+
+  <keep_leading_zeros>
+    <worksheet>
+      ... Cells here will have leading-zeros preserved
+    </worksheet>
+    <worksheet>
+      ... Cells here will have leading-zeros preserved
+    </worksheet>
+  </keep_leading_zeros>
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+L<CELL|Excel::Template::Element::Cell>, L<Spreadsheet::WriteExcel>
+
+=cut
diff --git a/lib/Excel/Template/Container/Locked.pm b/lib/Excel/Template/Container/Locked.pm
new file mode 100644 (file)
index 0000000..c7e1aad
--- /dev/null
@@ -0,0 +1,76 @@
+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
+
+You must have protected the worksheet containing any cells that are affected by
+this format. Otherwise, this node will have no effect.
+
+=head1 USAGE
+
+  <locked>
+    ... Children here
+  </locked>
+
+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 (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+WORKSHEET, FORMAT
+
+=cut
diff --git a/lib/Excel/Template/Container/Loop.pm b/lib/Excel/Template/Container/Loop.pm
new file mode 100644 (file)
index 0000000..6848c70
--- /dev/null
@@ -0,0 +1,188 @@
+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 "<loop> 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;
+
+        $self->iterate_over_children($context);
+
+        # It doesn't seem that iterate_over_children() can ever fail, because
+        # I'm not sure that render() can return false. In PDF::Template, where
+        # this module got most of its code, render() can certainly return false,
+        # in the case of page-breaks. I left the code in because it didn't seem
+        # like it would hurt.
+        #unless ($self->iterate_over_children($context))
+        #{
+        #    $iterator->back_up;
+        #    last;
+        #}
+    }
+
+    $iterator->exit_scope;
+
+    return 0 if $iterator->more_params;
+
+    return 1;
+}
+
+# These methods are used in PDF::Template to calculate pagebreaks. I'm not sure
+# if they will ever be needed in Excel::Template.
+#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
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <loop name="LOOPY">
+    ... Children here ...
+  </loop>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..d2551e6
--- /dev/null
@@ -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
+
+  <outline>
+    ... Children here
+  </outline>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..b3067e6
--- /dev/null
@@ -0,0 +1,101 @@
+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;
+
+    # Apply the height to the current row
+    if (my $height = $context->get($self, 'HEIGHT'))
+    {
+        $height =~ s/\D//g;
+        $height *= 1;
+        if ($height > 0)
+        {
+            $context->active_worksheet->set_row(
+                $context->get( $self, 'ROW' ),
+                $height,
+            );
+        }
+    }
+
+    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
+
+=over 4
+
+=item * HEIGHT
+
+Sets the height of the row. The last setting for a given row will win out.
+
+=back
+
+=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
+
+  <row>
+    ... Children here
+  </row>
+
+Generally, you will have CELL and/or FORMULA tags within a ROW.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.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 (file)
index 0000000..700e720
--- /dev/null
@@ -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
+
+  <scope param1="value1" param2="value2">
+    ... Children here ...
+  </scope>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..bd7502e
--- /dev/null
@@ -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
+
+  <shadow>
+    ... Children here
+  </shadow>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..3630831
--- /dev/null
@@ -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
+
+  <bold>
+    ... Children here
+  </bold>
+
+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 (rob.kinyon@gmail.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 (file)
index 0000000..cbc9f3b
--- /dev/null
@@ -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
+
+  <workbook>
+    ... Children here
+  </workbook>
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.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 (file)
index 0000000..378915b
--- /dev/null
@@ -0,0 +1,102 @@
+package Excel::Template::Container::Worksheet;
+
+use strict;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Container);
+
+    use Excel::Template::Container;
+}
+
+sub exit_scope  { $_[1]->active_worksheet( undef ) }
+
+sub render
+{
+    my $self = shift;
+    my ($context) = @_;
+
+    my $worksheet = $context->new_worksheet( $self );
+
+    my $password = $context->get( $self, 'PROTECT' );
+    if (defined $password)
+    {
+        $worksheet->protect( $password );
+    }
+
+    $worksheet->keep_leading_zeros( 1 )
+        if $context->mark( 'keep_leading_zeros' );
+
+    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.
+
+=item * PROTECT
+
+If the attribute exists, it will mark the worksheet as being protected. Whatever
+value is set will be used as the password.
+
+This activates the HIDDEN and LOCKED nodes.
+
+=item * KEEP_LEADING_ZEROS
+
+This will change the behavior of the worksheet to preserve leading zeros.
+
+=back
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <worksheet name="My Taxes">
+    ... Children here
+  </worksheet>
+
+In the above example, the children will be executed in the context of the
+"My Taxes" worksheet.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.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 (file)
index 0000000..c9686b1
--- /dev/null
@@ -0,0 +1,373 @@
+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 node. 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->{FORMAT_OBJECT}    = Excel::Template::Format->new;
+    $self->{ACTIVE_FORMAT}    = $self->{FORMAT_OBJECT}->blank_format($self);
+    $self->{WORKSHEET_NAMES}  = undef;
+
+    $self->{__MARKS} = {};
+
+    # Removed NAME_MAP until I figure out what the heck it's for
+    for (qw( STACK PARAM_MAP ))
+    {
+        next if defined $self->{$_} && ref $self->{$_} eq 'ARRAY';
+        $self->{$_} = [];
+    }
+
+    $self->{$_} = 0 for keys %isAbsolute;
+
+    return $self;
+}
+
+sub use_unicode { $_[0]->{UNICODE} && 1 }
+
+sub _find_param_in_map
+{
+    my $self = shift;
+    my ($map, $param, $depth) = @_;
+    $param = uc $param;
+    $depth ||= 0;
+
+    my ($val, $found);
+    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 $prev_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 ($worksheet) = @_;
+
+    $self->{ROW} = $self->{COL} = 0;
+    $self->{REFERENCES} = {};
+
+    my $name = $self->get( $worksheet, 'NAME' );
+
+    if ( defined $name && length $name )
+    {
+        if ( exists $self->{WORKSHEET_NAMES}{$name} )
+        {
+            $name = '';
+        }
+        else
+        {
+            $self->{WORKSHEET_NAMES}{$name} = undef;
+        }
+    }
+    else
+    {
+        $name = '';
+    }
+
+    return $self->active_worksheet(
+        $self->{XLS}->add_worksheet( $name ),
+    );
+}
+
+sub mark
+{
+    my $self = shift;
+
+    if ( @_ > 1 )
+    {
+        my %args = @_;
+
+        @{$self->{__MARKS}}{keys %args} = values %args;
+    }
+
+    return $self->{__MARKS}{$_[0]}
+}
+
+sub active_worksheet
+{
+    my $self = shift;
+    
+    $self->{ACTIVE_WORKSHEET} = $_[0]
+        if @_;
+
+    $self->{ACTIVE_WORKSHEET};
+}
+
+sub add_reference
+{
+    my $self = shift;
+    my ($ref, $row, $col) = @_;
+
+    $self->{REFERENCES}{$ref} ||= [];
+
+    push @{$self->{REFERENCES}{$ref}}, [ $row, $col ];
+
+    return ~~1;
+}
+
+sub get_all_references
+{
+    my $self = shift;
+    my $ref = uc shift;
+
+    $self->{REFERENCES}{$ref} ||= [];
+
+    return @{ $self->{REFERENCES}{$ref} };
+}
+
+sub get_last_reference
+{
+    my $self = shift;
+    my $ref = uc shift;
+
+    $self->{REFERENCES}{$ref} ||= [];
+
+    return @{ $self->{REFERENCES}{$ref}[-1] };
+}
+
+sub format_object { $_[0]{FORMAT_OBJECT} }
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Context
+
+=head1 PURPOSE
+
+This is a helper node that provides the global context for the nodes do their processing within. It provides attribute scoping, parameter resolution, and other very nice things.
+
+Documentation is provided for if you wish to subclass another node.
+
+=head1 NODE NAME
+
+None
+
+=head1 INHERITANCE
+
+None
+
+=head1 ATTRIBUTES
+
+None
+
+=head1 CHILDREN
+
+None
+
+=head1 AFFECTS
+
+Everything
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 METHODS
+
+=head2 active_format
+
+=head2 active_worksheet
+
+=head2 add_reference
+
+=head2 format_object
+
+=head2 get
+
+=head2 get_all_references
+
+=head2 get_last_reference
+
+=head2 mark
+
+=head2 new_worksheet
+
+=head2 param
+
+=head2 use_unicode
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/Excel/Template/Element.pm b/lib/Excel/Template/Element.pm
new file mode 100644 (file)
index 0000000..16d4d9c
--- /dev/null
@@ -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 (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/Excel/Template/Element/Backref.pm b/lib/Excel/Template/Element/Backref.pm
new file mode 100755 (executable)
index 0000000..c64188f
--- /dev/null
@@ -0,0 +1,89 @@
+package Excel::Template::Element::Backref;
+
+use strict;
+use Spreadsheet::WriteExcel::Utility;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Element);
+
+    use Excel::Template::Element;
+}
+
+sub resolve
+{ 
+    my $self = shift;
+    my ($context) = @_;
+
+    my $ref_name = $context->resolve($self, 'REF');
+
+    my ($row, $col) = $context->get_last_reference( $ref_name );
+    return '' unless defined $row && defined $col;
+
+    return xl_rowcol_to_cell( $row, $col );
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Element::Backref
+
+=head1 PURPOSE
+
+Returns the cell location (i.e. B2) of the last cell to name this reference.  To
+return the location of the entire range of cells to name this reference see RANGE.
+
+=head1 NODE NAME
+
+BACKREF
+
+=head1 INHERITANCE
+
+Excel::Template::Element
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item * REF
+
+This is the name of the reference to look up.
+
+=back
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+This will only be used within CELL tags.
+
+=head1 USAGE
+
+In the example...
+
+  <row>
+    <cell ref="this_cell"/><cell ref="that_cell"><cell ref="that_cell">
+  </row>
+  <row>
+    <formula>=<backref ref="this_cell">+<backref ref="that_cell"></formula>
+  </row>
+
+The formula in row 2 would be =A1+C1.  C1 is the last to reference "that_cell".
+
+=head1 AUTHOR
+
+Rob Kinyon (rkinyon@columbus.rr.com)
+
+=head1 SEE ALSO
+
+CELL, RANGE
+
+=cut
diff --git a/lib/Excel/Template/Element/Cell.pm b/lib/Excel/Template/Element/Cell.pm
new file mode 100755 (executable)
index 0000000..9444fc3
--- /dev/null
@@ -0,0 +1,207 @@
+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);
+    }
+    else
+    {
+        $txt = $self->{TXTOBJ}->resolve($context)
+    }
+
+    return $txt;
+}
+
+my %legal_types = (
+    'blank'   => 'write_blank',
+    'formula' => 'write_formula',
+    'number'  => 'write_number',
+    'string'  => 'write_string',
+    'url'     => 'write_url',
+);
+
+sub render
+{
+    my $self = shift;
+    my ($context, $method) = @_;
+
+    unless ( $method )
+    {
+        my $type = $context->get( $self, 'TYPE' );
+        if ( defined $type )
+        {
+            my $type = lc $type;
+
+            if ( exists $legal_types{ $type } )
+            {
+                $method = $legal_types{ $type };
+            }
+            else
+            {
+                warn "'$type' is not a legal cell type.\n"
+                    if $^W;
+            }
+        }
+    }
+
+    $method ||= 'write';
+
+    my ($row, $col) = map { $context->get($self, $_) } qw(ROW COL);
+
+    my $ref = $context->get( $self, 'REF' );
+    if (defined $ref && length $ref)
+    {
+        $context->add_reference( uc( $ref ), $row, $col );
+    }
+
+    # Apply the cell width to the current column
+    if (my $width = $context->get($self, 'WIDTH'))
+    {
+        $width =~ s/[^\d.]//g;
+        $width *= 1;
+        if ($width > 0)
+        {
+            $context->active_worksheet->set_column($col, $col, $width);
+        }
+    }
+
+    $context->active_worksheet->$method(
+        $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
+
+L<ELEMENT|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 L<Excel::Template> for more info on offset-based numbering.
+
+=item * REF
+
+Adds the current cell to the a list of cells that can be backreferenced.  This is useful when the current cell needs to be referenced by a formula. See L<BACKREF|Excel::Tepmlate::Element::Backref> and L<RANGE|Excel::Tepmlate::Container::Range>.
+
+=item * WIDTH
+
+Sets the width of the column the cell is in. The last setting for a given column
+will win out.
+
+=item * TYPE
+
+This allows you to specify what write_*() method will be used. The default is to call write() and let L<Spreadsheet::WriteExcel> make the right call. However, you may wish to override it. L<Excel::Template> will not do any form of validation on what you provide. You are assumed to know what you're doing.
+
+The legal types (taken from L<Spreadsheet::WriteExcel>) are:
+
+=over 4
+
+=item * blank
+
+=item * formula
+
+=item * number
+
+=item * string
+
+=item * url
+
+=back
+
+q.v. L<Spreadsheet::WriteExcel> for more info.
+
+=back
+
+=head1 CHILDREN
+
+L<FORMULA|Excel::Template::Element::Formula>
+
+=head1 EFFECTS
+
+This will consume one column in the current row.
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <cell text="Some Text Here"/>
+  <cell>Some other text here</cell>
+
+  <cell text="$Param2"/>
+  <cell>Some <var name="Param"> text here</cell>
+
+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 L<Spreadsheet::WriteExcel> for what constitutes a legal formula.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+L<ROW|Excel::Template::Container::Row>, L<VAR|Excel::Template::Element::Var>, L<FORMULA|Excel::Template::Element::Formula>
+
+=cut
diff --git a/lib/Excel/Template/Element/Formula.pm b/lib/Excel/Template/Element/Formula.pm
new file mode 100644 (file)
index 0000000..0edb621
--- /dev/null
@@ -0,0 +1,78 @@
+package Excel::Template::Element::Formula;
+
+use strict;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Element::Cell);
+
+    use Excel::Template::Element::Cell;
+}
+
+sub render { $_[0]->SUPER::render( $_[1], 'write_formula' ) }
+#{
+#    my $self = shift;
+#    my ($context) = @_;
+#
+#    return $self->SUPER::render( $context, 'write_formula' );
+#}
+
+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
+
+All attributes a CELL can have, a FORMULA can have, including the ability to be
+referenced using the 'ref' attribute.
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+This will consume one column on the current row. 
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <formula text="=(1 + 2)"/>
+  <formula>=SUM(A1:A5)</formula>
+
+  <formula text="$Param2"/>
+  <formula>=(A1 + <var name="Param">)</formula>
+
+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 (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+CELL
+
+=cut
diff --git a/lib/Excel/Template/Element/FreezePanes.pm b/lib/Excel/Template/Element/FreezePanes.pm
new file mode 100644 (file)
index 0000000..db4a534
--- /dev/null
@@ -0,0 +1,63 @@
+package Excel::Template::Element::FreezePanes;
+
+use strict;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Element);
+
+    use Excel::Template::Element;
+}
+
+sub render {
+    my $self = shift;
+    my ($context) = @_;
+
+    my ($row, $col) = map { $context->get( $self, $_ ) } qw( ROW COL );
+    $context->active_worksheet->freeze_panes( $row, $col );
+
+    return 1;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Element::FreezePanes - Excel::Template::Element::FreezePanes
+
+=head1 PURPOSE
+
+To insert an image into the worksheet
+
+=head1 NODE NAME
+
+FREEZEPANES
+
+=head1 INHERITANCE
+
+L<ELEMENT|Excel::Template::Element>
+
+=head1 EFFECTS
+
+This will not conume any columns or rows. It is a zero-width assertion.
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <freezepanes />
+
+This will do a Freeze Pane at the current cell.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+Nothing
+
+=cut
diff --git a/lib/Excel/Template/Element/Image.pm b/lib/Excel/Template/Element/Image.pm
new file mode 100644 (file)
index 0000000..dd9b9dd
--- /dev/null
@@ -0,0 +1,89 @@
+package Excel::Template::Element::Image;
+
+use strict;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Element);
+
+    use Excel::Template::Element;
+}
+
+sub render {
+    my $self = shift;
+    my ($context) = @_;
+
+    my ($row, $col, $path, $offset, $scale) = map {
+        $context->get($self, $_)
+    } qw( ROW COL PATH OFFSET SCALE );
+
+    my @offsets = (0,0);
+    if ( $offset =~ /^\s*([\d.]+)\s*,\s*([\d.]+)/ ) {
+        @offsets = ($1,$2);
+    }
+
+    my @scales = (0,0);
+    if ( $scale =~ /^\s*([\d.]+)\s*,\s*([\d.]+)/ ) {
+        @scales = ($1,$2);
+    }
+
+    $context->active_worksheet->insert_bitmap(
+        $row, $col, $path, @offsets, @scales,
+    );
+
+    return 1;
+}
+
+sub deltas {
+    return {
+        COL => +1,
+    };
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Element::Image - Excel::Template::Element::Image
+
+=head1 PURPOSE
+
+To insert an image into the worksheet
+
+=head1 NODE NAME
+
+IMAGE
+
+=head1 INHERITANCE
+
+L<ELEMENT|Excel::Template::Element>
+
+=head1 EFFECTS
+
+This will consume one column in the current row.
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 USAGE
+
+  <image path="/Some/Full/Path" />
+  <image path="/Some/Full/Path" offset="2,5" />
+  <image path="/Some/Full/Path" scale="2,0.4" />
+  <image path="/Some/Full/Path" offset="4,0" scale="0,2" />
+
+Please see L<Spreadsheet::WriteExcel/> for more information about the offset and scaling options as well as any other restrictions that might be in place. This node does B<NOT> perform any sort of validation upon your parameters. You are assumed to know what you are doing.
+
+Note that the offset and scaling values are "X,Y". You I<must> provide both values, even if the Y value is 0. If you provide a 0 value for either scaling option, L<Spreadsheet::WriteExcel/> will default that to 1.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+Nothing
+
+=cut
diff --git a/lib/Excel/Template/Element/Range.pm b/lib/Excel/Template/Element/Range.pm
new file mode 100755 (executable)
index 0000000..d3a84d8
--- /dev/null
@@ -0,0 +1,107 @@
+package Excel::Template::Element::Range;
+
+use strict;
+use Spreadsheet::WriteExcel::Utility;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Element);
+
+    use Excel::Template::Element;
+}
+
+sub min { $_[0] < $_[1] ? $_[0] : $_[1] }
+sub max { $_[0] > $_[1] ? $_[0] : $_[1] }
+
+sub resolve
+{ 
+    my $self = shift;
+    my ($context) = @_;
+
+    my $ref_name = $context->resolve($self, 'REF');
+
+    my @refs = $context->get_all_references( $ref_name );
+    return '' unless @refs;
+
+    my ($top, $left, $bottom, $right) =
+        ( $refs[0][0], $refs[0][1] ) x 2;
+
+    shift @refs;
+    foreach my $ref ( @refs )
+    {
+        $top    = min( $top,    $ref->[0]);
+        $bottom = max( $bottom, $ref->[0]);
+        $left   = min( $left,   $ref->[1]);
+        $right  = max( $right,  $ref->[1]);
+    }
+
+    return join( ':',
+        xl_rowcol_to_cell($top, $left),
+        xl_rowcol_to_cell($bottom, $right)
+    );
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Element::Range
+
+=head1 PURPOSE
+
+Returns a range of cell locations (i.e. B2:C2) that contains all calls using
+this reference. To return the location of the last cell, use BACKREF.
+
+=head1 NODE NAME
+
+RANGE
+
+=head1 INHERITANCE
+
+Excel::Template::Element
+
+=head1 ATTRIBUTES
+
+=over 4
+
+=item * REF
+
+This is the name of the reference to look up.
+
+=back
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+This will only be used within CELL tags.
+
+=head1 USAGE
+
+In the example...
+
+  <row>
+    <cell ref="this_cell"/><cell ref="that_cell"><cell ref="that_cell">
+  </row>
+  <row>
+    <formula>=SUM(<range ref="that_cell">)</formula>
+  </row>
+
+The formula in row 2 would be =SUM(B1:C1).
+
+=head1 AUTHOR
+
+Rob Kinyon (rkinyon@columbus.rr.com)
+
+=head1 SEE ALSO
+
+CELL, BACKREF
+
+=cut
diff --git a/lib/Excel/Template/Element/Var.pm b/lib/Excel/Template/Element/Var.pm
new file mode 100644 (file)
index 0000000..3d365fc
--- /dev/null
@@ -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
+
+=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:
+
+  <loop name="LOOPY">
+    <var name="$SomeParam"/>
+  </loop>
+
+Where the actual name to be substituted is, itself, a parameter.
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+CELL
+
+=cut
diff --git a/lib/Excel/Template/Factory.pm b/lib/Excel/Template/Factory.pm
new file mode 100644 (file)
index 0000000..ed87510
--- /dev/null
@@ -0,0 +1,196 @@
+package Excel::Template::Factory;
+
+use strict;
+
+my %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',
+
+    'BACKREF'     => 'Excel::Template::Element::Backref',
+    'CELL'        => 'Excel::Template::Element::Cell',
+    'FORMULA'     => 'Excel::Template::Element::Formula',
+    'FREEZEPANES' => 'Excel::Template::Element::FreezePanes',
+    'IMAGE'       => 'Excel::Template::Element::Image',
+    'RANGE'       => 'Excel::Template::Element::Range',
+    'VAR'         => 'Excel::Template::Element::Var',
+
+    'FORMAT'    => 'Excel::Template::Container::Format',
+
+# These are all the Format short-cut objects
+# They are also instantiable
+    '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',
+
+    'KEEP_LEADING_ZEROS' => 'Excel::Template::Container::KeepLeadingZeros',
+
+# These are the helper objects
+# They are also in here to make E::T::Factory::isa() work.
+    'CONTEXT'    => 'Excel::Template::Context',
+    'ITERATOR'   => 'Excel::Template::Iterator',
+    'TEXTOBJECT' => 'Excel::Template::TextObject',
+
+    'CONTAINER'  => 'Excel::Template::Container',
+    'ELEMENT'    => 'Excel::Template::Element',
+
+    'BASE'       => 'Excel::Template::Base',
+);
+
+my %isBuildable = map { $_ => ~~1 } qw(
+    WORKBOOK WORKSHEET
+    FORMAT BOLD HIDDEN ITALIC LOCKED OUTLINE SHADOW STRIKEOUT
+    IF ROW LOOP SCOPE KEEP_LEADING_ZEROS
+    CELL FORMULA FREEZEPANES IMAGE
+    VAR BACKREF RANGE
+);
+
+{
+    my %Loaded;
+    sub _load_class
+    {
+        my $self = shift;
+        my ($class) = @_;
+
+        unless ( exists $Loaded{$class} )
+        {
+            (my $filename = $class) =~ s!::!/!g;
+            eval {
+                require "$filename.pm";
+            }; if ($@) {
+                die "Cannot find or compile PM file for '$class' ($filename)\n";
+            }
+
+            $Loaded{$class} = ~~1;
+        }
+
+        return ~~1;
+    }
+}
+
+{
+    my @param_names = qw(name class isa);
+    sub register
+    {
+        my $self = shift;
+        my %params = @_;
+
+        for (@param_names)
+        {
+            unless ($params{$_})
+            {
+                warn "$_ was not supplied to register()\n" if $^W;
+                return;
+            }
+        }
+
+        my $name = uc $params{name};
+        if (exists $Manifest{$name})
+        {
+            warn "$params{name} already exists in the manifest.\n" if $^W;
+            return;
+        }
+
+        my $isa = uc $params{isa};
+        unless (exists $Manifest{$isa})
+        {
+            warn "$params{isa} does not exist in the manifest.\n" if $^W;
+            return;
+        }
+
+        {
+            no strict 'refs';
+            unshift @{"$params{class}::ISA"}, $Manifest{$isa};
+        }
+
+        $self->_load_class( $Manifest{$isa} );
+        $self->_load_class( $params{class} );
+
+        $Manifest{$name} = $params{class};
+        $isBuildable{$name} = ~~1;
+
+        return ~~1;
+    }
+}
+
+sub _create
+{
+    my $self = shift;
+    my $name = uc shift;
+
+    return unless exists $Manifest{$name};
+
+    $self->_load_class( $Manifest{$name} );
+    return $Manifest{$name}->new(@_);
+}
+
+sub _create_node
+{
+    my $self = shift;
+    my $name = uc shift;
+
+    return unless exists $isBuildable{$name};
+
+    return $self->_create($name, @_);
+}
+
+sub isa
+{
+    return unless @_ >= 2;
+    exists $Manifest{uc $_[1]}
+        ? UNIVERSAL::isa($_[0], $Manifest{uc $_[1]})
+        : UNIVERSAL::isa(@_)
+}
+
+sub is_embedded
+{
+    return unless @_ >= 1;
+
+    isa( $_[0], $_ ) && return ~~1 for qw( VAR BACKREF RANGE );
+    return;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Factory
+
+=head1 PURPOSE
+
+To provide a common way to instantiate Excel::Template nodes
+
+=head1 USAGE
+
+=head2 register()
+
+Use this to register your own nodes.
+
+Example forthcoming.
+
+=head1 METHODS
+
+=head2 isa
+
+This is a customized isa() wrapper for syntactic sugar
+
+=head2 is_embedded
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+=cut
diff --git a/lib/Excel/Template/Format.pm b/lib/Excel/Template/Format.pm
new file mode 100644 (file)
index 0000000..778c49d
--- /dev/null
@@ -0,0 +1,200 @@
+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.
+
+sub new { bless {}, shift }
+
+sub _assign { $_[0]{$_[1]} = $_[2]; $_[0]{$_[2]} = $_[1] }
+#    my $self = shift;
+#    my ($key, $format) = @_;
+#    $self->{$key} = $format;
+#    $self->{$format} = $key;
+#}
+
+sub _retrieve_key { $_[0]{ $_[1] } }
+#    my $self = shift;
+#    my ($format) = @_;
+#    return $self->{$format};
+#}
+
+*_retrieve_format = \&_retrieve_key;
+#sub _retrieve_format {
+#    my $self = shift;
+#    my ($key) = @_;
+#    return $self->{$key};
+#}
+
+{
+    my @_boolean_formats = qw(
+        bold italic locked hidden font_outline font_shadow font_strikeout
+        text_wrap text_justlast shrink
+    );
+
+    my @_integer_formats = qw(
+        size num_format underline rotation indent pattern border
+        bottom top left right
+    );
+
+    my @_string_formats = qw(
+        font color align valign bg_color fg_color border_color
+        bottom_color top_color left_color right_color
+    );
+
+    sub _params_to_key
+    {
+        my %params = @_;
+        $params{lc $_} = delete $params{$_} for keys %params;
+
+        my @parts = (
+            (map { $params{$_} ? 1 : '' } @_boolean_formats),
+            (map { $params{$_} ? $params{$_} + 0 : '' } @_integer_formats),
+            (map { $params{$_} || '' } @_string_formats),
+        );
+
+        return join( "\n", @parts );
+    }
+
+    sub _key_to_params
+    {
+        my ($key) = @_;
+
+        my @key_parts = split /\n/, $key;
+
+        my @boolean_parts = splice @key_parts, 0, scalar( @_boolean_formats );
+        my @integer_parts = splice @key_parts, 0, scalar( @_integer_formats );
+        my @string_parts  = splice @key_parts, 0, scalar( @_string_formats );
+
+        my %params;
+        $params{ $_boolean_formats[$_] } = ~~1
+            for grep { $boolean_parts[$_] } 0 .. $#_boolean_formats;
+
+        $params{ $_integer_formats[$_] } = $integer_parts[$_]
+            for grep { defined $integer_parts[$_] && length $integer_parts[$_] } 0 .. $#_integer_formats;
+
+        $params{ $_string_formats[$_] } = $string_parts[$_]
+            for grep { $string_parts[$_] } 0 .. $#_string_formats;
+
+        return %params;
+    }
+
+    sub copy
+    {
+        my $self = shift;
+        my ($context, $old_fmt, %properties) = @_;
+
+        # This is a key used for non-format book-keeping.
+        delete $properties{ ELEMENTS };
+
+        defined(my $key = _retrieve_key($self, $old_fmt))
+            || die "Internal Error: Cannot find key for format '$old_fmt'!\n";
+
+        my %params = _key_to_params($key);
+        PROPERTY:
+        while ( my ($prop, $value) = each %properties )
+        {
+            $prop = lc $prop;
+            foreach (@_boolean_formats)
+            {
+                if ($prop eq $_) {
+                    $params{$_} = ($value && $value !~ /false/i);
+                    next PROPERTY;
+                }
+            }
+            foreach (@_integer_formats, @_string_formats)
+            {
+                if ($prop eq $_) {
+                    $params{$_} = $value;
+                    next PROPERTY;
+                }
+            }
+
+            warn "Property '$prop' is unrecognized\n" if $^W;
+        }
+
+        my $new_key = _params_to_key(%params);
+
+        my $format = _retrieve_format($self, $new_key);
+        return $format if $format;
+
+        $format = $context->{XLS}->add_format(%params);
+        _assign($self, $new_key, $format);
+        return $format;
+    }
+}
+
+sub blank_format
+{
+    my $self = shift;
+    my ($context) = @_;
+
+    my $blank_key = _params_to_key();
+
+    my $format = _retrieve_format($self, $blank_key);
+    return $format if $format;
+
+    $format = $context->{XLS}->add_format;
+    _assign($self, $blank_key, $format);
+    return $format;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Excel::Template::Format - Excel::Template::Format
+
+=head1 PURPOSE
+
+Helper class for FORMAT
+
+=head1 NODE NAME
+
+None
+
+=head1 INHERITANCE
+
+None
+
+=head1 ATTRIBUTES
+
+None
+
+=head1 CHILDREN
+
+None
+
+=head1 EFFECTS
+
+None
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 METHODS
+
+=head2 blank_format
+
+Provides a blank format for use
+
+=head2 copy
+
+Clones an existing format, so that a new format can be built from it
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+FORMAT
+
+=cut
diff --git a/lib/Excel/Template/Iterator.pm b/lib/Excel/Template/Iterator.pm
new file mode 100644 (file)
index 0000000..2ec00aa
--- /dev/null
@@ -0,0 +1,253 @@
+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__, $/ if $^W;
+
+        return $self;
+    }
+
+    # Cache the reference to the appropriate data.
+    $self->{DATA} = $self->{CONTEXT}->param($self->{NAME});
+
+    unless (ref $self->{DATA} eq 'ARRAY')
+    {
+        $self->{NO_PARAMS} = 1;
+        warn "'$self->{NAME}' does not have a list of parameters", $/ if $^W;
+
+        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;
+}
+
+# This method doesn't seem to be used ...
+# If it is reinstated, here's the POD for it
+#=head2 back_up
+#
+#Go to the previous iteration of the loop
+#
+#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;
+#}
+
+# This method doesn't seem to be used ...
+# If it is reinstated, here's the POD for it
+#=head2 reset
+#
+#Resets the iterator
+#
+#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
+
+This is meant for internal use only. Documentation is provided for subclassing.
+
+=head1 NODE NAME
+
+None
+
+=head1 INHERITANCE
+
+None
+
+=head1 ATTRIBUTES
+
+None
+
+=head1 CHILDREN
+
+None
+
+=head1 AFFECTS
+
+This is a helper class for LOOP
+
+=head1 DEPENDENCIES
+
+None
+
+=head1 METHODS
+
+=head2 can_continue
+
+Determines if the iterator can continue.
+
+Currently, this wraps more_params(), but there other possible situations, such as the page ending.
+
+=head2 more_params
+
+Determines if the iterator for the loop has more parameters that it can consume
+
+=head2 next
+
+Go to the next iteration of the loop
+
+=head1 AUTHOR
+
+Rob Kinyon (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+LOOP
+
+=cut
diff --git a/lib/Excel/Template/TextObject.pm b/lib/Excel/Template/TextObject.pm
new file mode 100755 (executable)
index 0000000..38e3c6c
--- /dev/null
@@ -0,0 +1,90 @@
+package Excel::Template::TextObject;
+
+use strict;
+
+BEGIN {
+    use vars qw(@ISA);
+    @ISA = qw(Excel::Template::Base);
+
+    use Excel::Template::Base;
+}
+
+# This is a helper object. It is not instantiated by the user,
+# nor does it represent an XML object. Rather, certain elements,
+# such as <textbox>, can use this object to do text with variable
+# substitutions.
+
+sub new
+{
+    my $class = shift;
+    my $self = $class->SUPER::new(@_);
+
+    $self->{STACK} = []
+        unless defined $self->{STACK} &&
+            ref $self->{STACK} eq 'ARRAY';
+
+    return $self;
+}
+
+sub resolve
+{
+    my $self = shift;
+    my ($context) = @_;
+
+    my $use_unicode = $context->use_unicode;
+
+    my $t;
+    if ($use_unicode)
+    {
+        require Unicode::String;
+        $t = Unicode::String::utf8('');
+    }
+    else
+    {
+        $t = '';
+    }
+
+    for my $tok (@{$self->{STACK}})
+    {
+        my $val = $tok;
+        $val = $val->resolve($context)
+            if Excel::Template::Factory::is_embedded( $val );
+
+        $t .= $use_unicode
+            ? Unicode::String::utf8("$val")
+            : $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 (rob.kinyon@gmail.com)
+
+=head1 SEE ALSO
+
+=cut
diff --git a/t/001_load.t b/t/001_load.t
new file mode 100644 (file)
index 0000000..0f8bd95
--- /dev/null
@@ -0,0 +1,12 @@
+use strict;
+
+use Test::More tests => 2;
+
+my $CLASS = 'Excel::Template';
+
+use_ok( $CLASS );
+
+my $object = $CLASS->new ();
+isa_ok( $object, $CLASS );
+
+
diff --git a/t/002.xml b/t/002.xml
new file mode 100644 (file)
index 0000000..1f2269b
--- /dev/null
+++ b/t/002.xml
@@ -0,0 +1 @@
+<workbook />
diff --git a/t/002_workbook.t b/t/002_workbook.t
new file mode 100644 (file)
index 0000000..75dec5e
--- /dev/null
@@ -0,0 +1,24 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/002.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/003.xml b/t/003.xml
new file mode 100644 (file)
index 0000000..881d6cf
--- /dev/null
+++ b/t/003.xml
@@ -0,0 +1,5 @@
+<workbook>
+  <worksheet />
+  <worksheet name="foo"/>
+  <worksheet name="foo"/>
+</workbook>
diff --git a/t/003_worksheet.t b/t/003_worksheet.t
new file mode 100644 (file)
index 0000000..c9af1ce
--- /dev/null
@@ -0,0 +1,30 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/003.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'foo' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/004.xml b/t/004.xml
new file mode 100644 (file)
index 0000000..bd01d93
--- /dev/null
+++ b/t/004.xml
@@ -0,0 +1,7 @@
+<workbook>
+  <worksheet name="cell">
+    <cell>Test1</cell>
+    <cell text="Test2" />
+    <cell />
+  </worksheet>
+</workbook>
diff --git a/t/004_cell.t b/t/004_cell.t
new file mode 100644 (file)
index 0000000..2543ec5
--- /dev/null
@@ -0,0 +1,29 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/004.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'cell' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'Test1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'Test2', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', '', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/005.xml b/t/005.xml
new file mode 100644 (file)
index 0000000..f3be8f8
--- /dev/null
+++ b/t/005.xml
@@ -0,0 +1,39 @@
+<workbook>
+  <bold />
+  <hidden />
+  <italic />
+  <locked />
+  <outline />
+  <shadow />
+  <strikeout />
+
+  <format font_outline="1" />
+  <format shrink="1" />
+  <format text_wrap="1" />
+  <format text_justlast="1" />
+  <format size="3" />
+  <format num_format="3" />
+  <format underline="3" />
+  <format rotation="3" />
+  <format indent="3" />
+  <format pattern="3" />
+  <format border="3" />
+  <format bottom="3" />
+  <format top="3" />
+  <format left="3" />
+  <format right="3" />
+  <format font="3" />
+  <format color="3" />
+  <format align="3" />
+  <format valign="3" />
+  <format bg_color="3" />
+  <format fg_color="3" />
+  <format border_color="3" />
+  <format bottom_color="3" />
+  <format top_color="3" />
+  <format left_color="3" />
+  <format right_color="3" />
+
+
+  <bold><italic><hidden /></italic></bold>
+</workbook>
diff --git a/t/005_formats.t b/t/005_formats.t
new file mode 100644 (file)
index 0000000..8e282f4
--- /dev/null
@@ -0,0 +1,58 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/005.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_format( 'bold', '1' )
+Spreadsheet::WriteExcel::add_format( 'hidden', '1' )
+Spreadsheet::WriteExcel::add_format( 'italic', '1' )
+Spreadsheet::WriteExcel::add_format( 'locked', '1' )
+Spreadsheet::WriteExcel::add_format( 'font_outline', '1' )
+Spreadsheet::WriteExcel::add_format( 'font_shadow', '1' )
+Spreadsheet::WriteExcel::add_format( 'font_strikeout', '1' )
+Spreadsheet::WriteExcel::add_format( 'shrink', '1' )
+Spreadsheet::WriteExcel::add_format( 'text_wrap', '1' )
+Spreadsheet::WriteExcel::add_format( 'text_justlast', '1' )
+Spreadsheet::WriteExcel::add_format( 'size', '3' )
+Spreadsheet::WriteExcel::add_format( 'num_format', '3' )
+Spreadsheet::WriteExcel::add_format( 'underline', '3' )
+Spreadsheet::WriteExcel::add_format( 'rotation', '3' )
+Spreadsheet::WriteExcel::add_format( 'indent', '3' )
+Spreadsheet::WriteExcel::add_format( 'pattern', '3' )
+Spreadsheet::WriteExcel::add_format( 'border', '3' )
+Spreadsheet::WriteExcel::add_format( 'bottom', '3' )
+Spreadsheet::WriteExcel::add_format( 'top', '3' )
+Spreadsheet::WriteExcel::add_format( 'left', '3' )
+Spreadsheet::WriteExcel::add_format( 'right', '3' )
+Spreadsheet::WriteExcel::add_format( 'font', '3' )
+Spreadsheet::WriteExcel::add_format( 'color', '3' )
+Spreadsheet::WriteExcel::add_format( 'align', '3' )
+Spreadsheet::WriteExcel::add_format( 'valign', '3' )
+Spreadsheet::WriteExcel::add_format( 'bg_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'fg_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'border_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'bottom_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'top_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'left_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'right_color', '3' )
+Spreadsheet::WriteExcel::add_format( 'bold', '1', 'italic', '1' )
+Spreadsheet::WriteExcel::add_format( 'bold', '1', 'hidden', '1', 'italic', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/006.xml b/t/006.xml
new file mode 100644 (file)
index 0000000..75037a5
--- /dev/null
+++ b/t/006.xml
@@ -0,0 +1,7 @@
+<workbook>
+  <worksheet name="cell">
+    <cell><var name="test1" /></cell>
+    <cell text="$test2" />
+    <cell>PRE <var name="test1" /> POST</cell>
+  </worksheet>
+</workbook>
diff --git a/t/006_variables.t b/t/006_variables.t
new file mode 100644 (file)
index 0000000..e08a690
--- /dev/null
@@ -0,0 +1,37 @@
+use strict;
+
+use Test::More tests => 5;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/006.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok(
+    $object->param( 
+        test1 => 'test1',
+        test2 => 'test2',
+    ),
+    'Parameters set',
+);
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'cell' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'test1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'test2', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', 'PRE test1 POST', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/007.xml b/t/007.xml
new file mode 100644 (file)
index 0000000..2900a4a
--- /dev/null
+++ b/t/007.xml
@@ -0,0 +1,11 @@
+<workbook>
+  <worksheet name="cell">
+    <cell />
+    <bold>
+      <cell />
+      <italic>
+        <cell />
+      </italic>
+    </bold>
+  </worksheet>
+</workbook>
diff --git a/t/007_cell_formats.t b/t/007_cell_formats.t
new file mode 100644 (file)
index 0000000..078b3a0
--- /dev/null
@@ -0,0 +1,31 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/007.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'cell' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '', '1' )
+Spreadsheet::WriteExcel::add_format( 'bold', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', '', '2' )
+Spreadsheet::WriteExcel::add_format( 'bold', '1', 'italic', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', '', '3' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/008.xml b/t/008.xml
new file mode 100644 (file)
index 0000000..d481cc4
--- /dev/null
+++ b/t/008.xml
@@ -0,0 +1,7 @@
+<workbook>
+  <worksheet name="formula">
+    <formula>Test1</formula>
+    <formula text="Test2" />
+    <formula />
+  </worksheet>
+</workbook>
diff --git a/t/008_formula.t b/t/008_formula.t
new file mode 100644 (file)
index 0000000..a85b030
--- /dev/null
@@ -0,0 +1,29 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/008.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'formula' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write_formula( '0', '0', 'Test1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write_formula( '0', '1', 'Test2', '1' )
+Spreadsheet::WriteExcel::Worksheet::write_formula( '0', '2', '', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/009.xml b/t/009.xml
new file mode 100644 (file)
index 0000000..7880fa8
--- /dev/null
+++ b/t/009.xml
@@ -0,0 +1,36 @@
+<workbook>
+  <worksheet name="loops">
+    <loop name="loopy">
+      <row>
+        <cell text="$value" />
+        <cell text="text" />
+      </row>
+    </loop>
+
+    <loop name="outer">
+      <row>
+        <cell text="$iter" />
+        <loop name="inner">
+          <cell text="$value" />
+        </loop>
+      </row>
+    </loop>
+
+    <loop name="no_iters">
+      <cell text="$value" />
+    </loop>
+
+    <loop name="no_params">
+      <cell text="$value" />
+    </loop>
+
+    <loop name="empty" />
+
+    <loop name="" />
+
+  </worksheet>
+
+  <loop name="worksheets">
+    <worksheet name="$value" />
+  </loop>
+</workbook>
diff --git a/t/009_loop.t b/t/009_loop.t
new file mode 100644 (file)
index 0000000..5052961
--- /dev/null
@@ -0,0 +1,67 @@
+BEGIN{ $^W = 0 }
+use strict;
+
+use Test::More tests => 5;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/009.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok(
+    $object->param( 
+        loopy => [
+            { value => 1 },
+            { value => 2 },
+            { value => 3 },
+        ],
+        outer => [
+            { iter => 'a', inner => [ { value => 1 }, { value => 2 } ] },
+            { iter => 'b', inner => [ { value => 3 }, { value => 4 } ] },
+        ],
+        worksheets => [
+            { value => 1 },
+            { value => 2 },
+            { value => 3 },
+        ],
+        no_iters => [
+        ],
+    ),
+    'Parameters set',
+);
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'loops' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'text', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '0', '2', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '1', 'text', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '0', '3', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '1', 'text', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '0', 'a', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '1', '1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '2', '2', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '4', '0', 'b', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '4', '1', '3', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '4', '2', '4', '1' )
+Spreadsheet::WriteExcel::add_worksheet( '1' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_worksheet( '2' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_worksheet( '3' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/010.xml b/t/010.xml
new file mode 100644 (file)
index 0000000..24a6f72
--- /dev/null
+++ b/t/010.xml
@@ -0,0 +1,8 @@
+<workbook>
+  <worksheet name="scope">
+    <scope text="1">
+      <cell />
+      <cell />
+    </scope>
+  </worksheet>
+</workbook>
diff --git a/t/010_scope.t b/t/010_scope.t
new file mode 100644 (file)
index 0000000..0e528fa
--- /dev/null
@@ -0,0 +1,28 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/010.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'scope' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', '1', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/011.xml b/t/011.xml
new file mode 100644 (file)
index 0000000..d2c6cea
--- /dev/null
+++ b/t/011.xml
@@ -0,0 +1,50 @@
+<workbook>
+  <worksheet name="conditional">
+    <loop name="loopy">
+      <row>
+        <if name="int">
+          <cell text="bool true" />
+        </if>
+        <if name="int" is="FALSE">
+          <cell text="bool false" />
+        </if>
+        <if name="int" op="==" value="0">
+          <cell text="num == passes" />
+        </if>
+        <if name="int" op="!=" value="0">
+          <cell text="num != passes" />
+        </if>
+        <if name="int" op=">" value="0">
+          <cell text="num > passes" />
+        </if>
+        <if name="int" op=">=" value="0">
+            <cell text="num >= passes" />
+        </if>
+        <if name="int" op='&lt;' value="0">
+          <cell text="num &lt; passes" />
+        </if>
+        <if name="int" op="&lt;=" value="0">
+          <cell text="num &lt;= passes" />
+        </if>
+        <if name="char" op="eq" value="y">
+          <cell text="char eq passes" />
+        </if>
+        <if name="char" op="ne" value="y">
+          <cell text="char ne passes" />
+        </if>
+        <if name="char" op="gt" value="y">
+          <cell text="char gt passes" />
+        </if>
+        <if name="char" op="ge" value="y">
+          <cell text="char ge passes" />
+        </if>
+        <if name="char" op="lt" value="y">
+          <cell text="char lt passes" />
+        </if>
+        <if name="char" op="le" value="y">
+          <cell text="char le passes" />
+        </if>
+      </row>
+    </loop>
+  </worksheet>
+</workbook>
diff --git a/t/011_conditional.t b/t/011_conditional.t
new file mode 100644 (file)
index 0000000..093e332
--- /dev/null
@@ -0,0 +1,66 @@
+use strict;
+
+use Test::More tests => 5;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/011.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok(
+    $object->param( 
+        loopy => [
+            { int => 0, char => 'n' },
+            { int => 0, char => 'y' },
+            { int => 1, char => 'z' },
+            { int => -1, char => 'y' },
+        ],
+    ),
+    'Parameters set',
+);
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'conditional' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'bool false', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'num == passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', 'num >= passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '3', 'num <= passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '4', 'char ne passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '5', 'char lt passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '6', 'char le passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '0', 'bool false', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '1', 'num == passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '2', 'num >= passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '3', 'num <= passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '4', 'char eq passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '5', 'char ge passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '6', 'char le passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '0', 'bool true', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '1', 'num != passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '2', 'num > passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '3', 'num >= passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '4', 'char ne passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '5', 'char gt passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '6', 'char ge passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '0', 'bool true', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '1', 'num != passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '2', 'num < passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '3', 'num <= passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '4', 'char eq passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '5', 'char ge passes', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '6', 'char le passes', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/012.xml b/t/012.xml
new file mode 100644 (file)
index 0000000..8ed109c
--- /dev/null
+++ b/t/012.xml
@@ -0,0 +1,7 @@
+<workbook>
+  <worksheet name="backref">
+    <cell ref="foo" text="not me" />
+    <cell ref="foo" text="me" />
+    <cell>=<backref ref="foo"/></cell>
+  </worksheet>
+</workbook>
diff --git a/t/012_backref.t b/t/012_backref.t
new file mode 100644 (file)
index 0000000..ffd18b1
--- /dev/null
@@ -0,0 +1,29 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/012.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'backref' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'not me', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'me', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', '=B1', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/013.xml b/t/013.xml
new file mode 100644 (file)
index 0000000..4862743
--- /dev/null
+++ b/t/013.xml
@@ -0,0 +1,8 @@
+<workbook>
+  <worksheet name="backref">
+    <cell ref="foo" text="1" />
+    <cell ref="foo" text="2" />
+    <cell ref="foo" text="3" />
+    <cell>=SUM(<range ref="foo"/>)</cell>
+  </worksheet>
+</workbook>
diff --git a/t/013_range.t b/t/013_range.t
new file mode 100644 (file)
index 0000000..3873149
--- /dev/null
@@ -0,0 +1,30 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/013.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'backref' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '1', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', '2', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', '3', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '3', '=SUM(A1:C1)', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/014.xml b/t/014.xml
new file mode 100644 (file)
index 0000000..3af4178
--- /dev/null
+++ b/t/014.xml
@@ -0,0 +1,8 @@
+<workbook>
+  <worksheet name="heightwidth">
+    <row height="30">
+      <cell width="10" text="1" />
+      <cell width="0.5" text="1" />
+    </row>
+  </worksheet>
+</workbook>
diff --git a/t/014_heightwidth.t b/t/014_heightwidth.t
new file mode 100644 (file)
index 0000000..b919183
--- /dev/null
@@ -0,0 +1,31 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/014.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'heightwidth' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '0', '30' )
+Spreadsheet::WriteExcel::Worksheet::set_column( '0', '0', '10' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '1', '1' )
+Spreadsheet::WriteExcel::Worksheet::set_column( '1', '1', '0.5' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', '1', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/015.xml b/t/015.xml
new file mode 100644 (file)
index 0000000..8934a6e
--- /dev/null
+++ b/t/015.xml
@@ -0,0 +1,9 @@
+<workbook>
+  <worksheet name="cell_type">
+    <cell type="string">String</cell>
+    <cell type="number">Number</cell>
+    <cell type="blank">Blank</cell>
+    <cell type="url">URL</cell>
+    <cell type="formula">Formula</cell>
+  </worksheet>
+</workbook>
diff --git a/t/015_cell_type.t b/t/015_cell_type.t
new file mode 100644 (file)
index 0000000..2c48544
--- /dev/null
@@ -0,0 +1,31 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => 't/015.xml',
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'cell_type' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write_string( '0', '0', 'String', '1' )
+Spreadsheet::WriteExcel::Worksheet::write_number( '0', '1', 'Number', '1' )
+Spreadsheet::WriteExcel::Worksheet::write_blank( '0', '2', 'Blank', '1' )
+Spreadsheet::WriteExcel::Worksheet::write_url( '0', '3', 'URL', '1' )
+Spreadsheet::WriteExcel::Worksheet::write_formula( '0', '4', 'Formula', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
diff --git a/t/016.xml b/t/016.xml
new file mode 100644 (file)
index 0000000..1f2269b
--- /dev/null
+++ b/t/016.xml
@@ -0,0 +1 @@
+<workbook />
diff --git a/t/016_renderers.t b/t/016_renderers.t
new file mode 100644 (file)
index 0000000..a8754f1
--- /dev/null
@@ -0,0 +1,63 @@
+use strict;
+
+use Test::More tests => 10;
+
+use lib 't';
+use mock;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+{
+    mock::reset;
+    my $object = $CLASS->new(
+        renderer => 'big',
+        filename => 't/016.xml',
+    );
+    isa_ok( $object, $CLASS );
+
+    ok( $object->write_file( 'filename' ), 'Something returned' );
+
+    my @calls = mock::get_calls;
+    is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::Big::new( 'filename' )
+Spreadsheet::WriteExcel::Big::add_format( '' )
+Spreadsheet::WriteExcel::Big::close( '' )
+__END_EXPECTED__
+}
+
+{
+    mock::reset;
+    my $object = $CLASS->new(
+        renderer => Excel::Template->RENDER_XML,
+        filename => 't/016.xml',
+    );
+    isa_ok( $object, $CLASS );
+
+    ok( $object->write_file( 'filename' ), 'Something returned' );
+
+    my @calls = mock::get_calls;
+    is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcelXML::new( 'filename' )
+Spreadsheet::WriteExcelXML::add_format( '' )
+Spreadsheet::WriteExcelXML::close( '' )
+__END_EXPECTED__
+}
+
+{
+    mock::reset;
+    my $object = $CLASS->new(
+        renderer => Excel::Template->RENDER_NML,
+        filename => 't/016.xml',
+    );
+    isa_ok( $object, $CLASS );
+
+    ok( $object->write_file( 'filename' ), 'Something returned' );
+
+    my @calls = mock::get_calls;
+    is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+}
diff --git a/t/017_filehandle.t b/t/017_filehandle.t
new file mode 100644 (file)
index 0000000..ddb6ed1
--- /dev/null
@@ -0,0 +1,37 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'foo' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet />
+  <worksheet name="foo"/>
+  <worksheet name="foo"/>
+</workbook>
diff --git a/t/018_register.t b/t/018_register.t
new file mode 100644 (file)
index 0000000..97f50b4
--- /dev/null
@@ -0,0 +1,79 @@
+use strict;
+
+use Test::More tests => 15;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+{
+    local $^W=0;
+    ok( !$CLASS->register(), "Must pass in class, name, and isa" );
+    ok( !$CLASS->register( class => 'Register_018' ), "Must pass in class, name, and isa" );
+    ok( !$CLASS->register( name => 'header' ), "Must pass in class, name, and isa" );
+    ok( !$CLASS->register( isa => 'cell' ), "Must pass in class, name, and isa" );
+    ok( !$CLASS->register( class => 'Register_018', isa => 'cell' ), "Must pass in class, name, and isa" );
+    ok( !$CLASS->register( class => 'Register_018', name => 'header' ), "Must pass in class, name, and isa" );
+    ok( !$CLASS->register( name => 'header', isa => 'cell' ), "Must pass in class, name, and isa" );
+
+    eval {
+        $CLASS->register(
+            class => 'NOT::A::CLASS',
+            name => 'not_a_node',
+            isa => 'cell',
+        );
+    };
+    like( $@, qr/Cannot find or compile/, "Verify registering a non-existent class fails" );
+
+    ok(
+        !$CLASS->register(
+            class => 'NOT::A::CLASS',
+            name => 'cell',
+            isa => 'row',
+        ), "Cannot add a nodename we already have",
+    );
+
+    ok(
+        !$CLASS->register(
+            class => 'NOT::A::CLASS',
+            name => 'new_node',
+            isa => 'not_a_node',
+        ), "Cannot inherit from a nodename we don't have",
+    );
+}
+
+ok(
+    $CLASS->register(
+        class => 'Register_018',
+        name => 'header',
+        isa => 'cell',
+    ), "Register Register_018 class",
+);
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::add_format( 'align', 'center', 'bold', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'test', '2' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet>
+    <header text="test" />
+  </worksheet>
+</workbook>
diff --git a/t/019_output.t b/t/019_output.t
new file mode 100644 (file)
index 0000000..6964daa
--- /dev/null
@@ -0,0 +1,28 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( my $output = $object->output( 'filename' ), "Something returned" );
+
+my $val = <<__END_EXPECTED__;
+Spreadsheet::WriteExcel::new\\( 'GLOB\\([^)]+\\)' \\)
+Spreadsheet::WriteExcel::add_format\\( '' \\)
+Spreadsheet::WriteExcel::close\\( '' \\)
+__END_EXPECTED__
+
+like( $output, qr/$val/, 'Calls match up' );
+
+__DATA__
+<workbook />
diff --git a/t/020_worksheet_attributes.t b/t/020_worksheet_attributes.t
new file mode 100644 (file)
index 0000000..3e28278
--- /dev/null
@@ -0,0 +1,34 @@
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    filename => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Something returned' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( 'worksheet attributes' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '03', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet name="worksheet attributes">
+    <cell text="03" />
+  </worksheet>
+</workbook>
diff --git a/t/021_loop_error.t b/t/021_loop_error.t
new file mode 100644 (file)
index 0000000..b87e4cf
--- /dev/null
@@ -0,0 +1,38 @@
+BEGIN{ $^W = 0 }
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok(
+    $object->param( 
+        test => [
+            { value => 1 },
+            { value => 2 },
+            [ value => 3 ],
+        ],
+    ),
+    'Parameters set',
+);
+
+ok( !$object->write_file( 'filename' ), 'Failed to write file' );
+
+__DATA__
+<workbook>
+  <worksheet>
+    <loop name="test">
+      <cell text="$value" />
+    </loop>
+  </worksheet>
+</workbook>
diff --git a/t/022_keep_leading_zeros.t b/t/022_keep_leading_zeros.t
new file mode 100644 (file)
index 0000000..97fcce1
--- /dev/null
@@ -0,0 +1,59 @@
+BEGIN{ $^W = 0 }
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Successfuly wrote file' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'before', '1' )
+Spreadsheet::WriteExcel::Worksheet::keep_leading_zeros( '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'inside', '1' )
+Spreadsheet::WriteExcel::Worksheet::keep_leading_zeros( '0' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', 'after', '1' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::keep_leading_zeros( '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'within', '1' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'after', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet>
+    <cell text="before" />
+    <keep_leading_zeros>
+      <cell text="inside" />
+    </keep_leading_zeros>
+    <cell text="after" />
+  </worksheet>
+  <keep_leading_zeros>
+    <worksheet>
+      <cell text="within" />
+    </worksheet>
+  </keep_leading_zeros>
+  <worksheet>
+    <cell text="after" />
+  </worksheet>
+</workbook>
+
diff --git a/t/023_relative_values.t b/t/023_relative_values.t
new file mode 100644 (file)
index 0000000..f5a9030
--- /dev/null
@@ -0,0 +1,64 @@
+BEGIN{ $^W = 0 }
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Successfuly wrote file' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '0', '8' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', '', '1' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '1', '10' )
+Spreadsheet::WriteExcel::Worksheet::write( '1', '0', '', '1' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '2', '6' )
+Spreadsheet::WriteExcel::Worksheet::write( '2', '0', '', '1' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '3', '16' )
+Spreadsheet::WriteExcel::Worksheet::write( '3', '0', '', '1' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '4', '4' )
+Spreadsheet::WriteExcel::Worksheet::write( '4', '0', '', '1' )
+Spreadsheet::WriteExcel::Worksheet::set_row( '5', '8' )
+Spreadsheet::WriteExcel::Worksheet::write( '5', '0', '', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet height="8">
+    <row>
+      <cell />
+    </row>
+    <row height="+2">
+      <cell />
+    </row>
+    <row height="-2">
+      <cell />
+    </row>
+    <row height="*2">
+      <cell />
+    </row>
+    <row height="/2">
+      <cell />
+    </row>
+    <row height="/0">
+      <cell />
+    </row>
+  </worksheet>
+</workbook>
+
diff --git a/t/024_image.t b/t/024_image.t
new file mode 100644 (file)
index 0000000..a0a344a
--- /dev/null
@@ -0,0 +1,45 @@
+BEGIN{ $^W = 0 }
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Successfuly wrote file' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'before', '1' )
+Spreadsheet::WriteExcel::Worksheet::insert_bitmap( '0', '1', '/full/path', '0', '0', '0', '0' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '2', 'after', '1' )
+Spreadsheet::WriteExcel::Worksheet::insert_bitmap( '0', '3', '/full/path', '2', '2', '0', '0' )
+Spreadsheet::WriteExcel::Worksheet::insert_bitmap( '0', '4', '/full/path', '0', '0', '2', '2' )
+Spreadsheet::WriteExcel::Worksheet::insert_bitmap( '0', '5', '/full/path', '0', '1', '1.1', '0' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet>
+    <cell text="before" />
+    <image path="/full/path" />
+    <cell text="after" />
+    <image path="/full/path" offset="2,2"/>
+    <image path="/full/path" scale="2,2"/>
+    <image path="/full/path" scale="1.1,0" offset="0,1"/>
+  </worksheet>
+</workbook>
diff --git a/t/025_freezepanes.t b/t/025_freezepanes.t
new file mode 100644 (file)
index 0000000..44626c1
--- /dev/null
@@ -0,0 +1,39 @@
+BEGIN{ $^W = 0 }
+use strict;
+
+use Test::More tests => 4;
+
+use lib 't';
+use mock;
+mock::reset;
+
+my $CLASS = 'Excel::Template';
+use_ok( $CLASS );
+
+my $object = $CLASS->new(
+    file => \*DATA,
+);
+isa_ok( $object, $CLASS );
+
+ok( $object->write_file( 'filename' ), 'Successfuly wrote file' );
+
+my @calls = mock::get_calls;
+is( join( $/, @calls, '' ), <<__END_EXPECTED__, 'Calls match up' );
+Spreadsheet::WriteExcel::new( 'filename' )
+Spreadsheet::WriteExcel::add_format( '' )
+Spreadsheet::WriteExcel::add_worksheet( '' )
+Spreadsheet::WriteExcel::Worksheet::new( '' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '0', 'before', '1' )
+Spreadsheet::WriteExcel::Worksheet::freeze_panes( '0', '1' )
+Spreadsheet::WriteExcel::Worksheet::write( '0', '1', 'after', '1' )
+Spreadsheet::WriteExcel::close( '' )
+__END_EXPECTED__
+
+__DATA__
+<workbook>
+  <worksheet>
+    <cell text="before" />
+    <freezepanes />
+    <cell text="after" />
+  </worksheet>
+</workbook>
diff --git a/t/998_pod.t b/t/998_pod.t
new file mode 100644 (file)
index 0000000..82b971a
--- /dev/null
@@ -0,0 +1,8 @@
+use strict;
+
+use Test::More;
+
+eval "use Test::Pod 1.14";
+plan skip_all => "Test::Pod 1.14 required for testing POD" if $@;
+
+all_pod_files_ok();
diff --git a/t/999_pod_coverage.t b/t/999_pod_coverage.t
new file mode 100644 (file)
index 0000000..269fec3
--- /dev/null
@@ -0,0 +1,25 @@
+use strict;
+
+use Test::More;
+
+eval "use Test::Pod::Coverage 1.04";
+plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@;
+
+# These are methods that need naming work
+my @private_methods = qw(
+    render new min max resolve deltas
+    enter_scope exit_scope iterate_over_children
+);
+
+# These are method names that have been commented out, for now
+# max_of total_of
+# begin_page end_page
+
+my $private_regex = do {
+    local $"='|';
+    qr/^(?:@private_methods)$/
+};
+
+all_pod_coverage_ok( {
+    also_private => [ $private_regex ],
+});
diff --git a/t/Register_018.pm b/t/Register_018.pm
new file mode 100644 (file)
index 0000000..b96db0e
--- /dev/null
@@ -0,0 +1,27 @@
+package Register_018;
+
+use strict;
+
+sub render
+{
+    my ($self, $context) = @_;
+
+    my $old_format = $context->active_format;
+    my $format = $context->format_object->copy(
+        $context, $old_format,
+
+        align => 'center', bold => 1,
+    );
+
+    $context->active_format($format);
+
+    my $child_success = $self->SUPER::render($context);
+
+    $context->active_format($old_format);
+
+    return $child_success;
+
+}
+
+1;
+__END__
diff --git a/t/Spreadsheet/WriteExcel.pm b/t/Spreadsheet/WriteExcel.pm
new file mode 100644 (file)
index 0000000..cab958d
--- /dev/null
@@ -0,0 +1,57 @@
+package Spreadsheet::WriteExcel;
+
+use strict;
+
+use mock;
+use Spreadsheet::WriteExcel::Worksheet;
+
+sub new {
+    my $self = bless {
+    }, shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, ref($self) . "::new( '@_' )";
+    }
+
+    $self->{file} = shift;
+
+    return $self;
+}
+
+sub close {
+    my $self = shift;
+    {
+        local $" = "', '";
+        push @mock::calls, ref($self) . "::close( '@_' )";
+    }
+
+    if ( ref $self->{file} ) {
+        my $fh = $self->{file};
+        print $fh join "\n", @mock::calls, ''; 
+    }
+}
+
+sub add_worksheet {
+    my $self = shift;
+    {
+        local $" = "', '";
+        push @mock::calls, ref($self) . "::add_worksheet( '@_' )";
+    }
+    return Spreadsheet::WriteExcel::Worksheet->new;
+}
+
+my $format_num = 1;
+sub add_format {
+    my $self = shift;
+    my %x = @_;
+    my @x = map { $_ => $x{$_} } sort keys %x;
+    {
+        local $" = "', '";
+        push @mock::calls, ref($self) . "::add_format( '@x' )";
+    }
+    return $format_num++;
+}
+
+1;
+__END__
diff --git a/t/Spreadsheet/WriteExcel/Big.pm b/t/Spreadsheet/WriteExcel/Big.pm
new file mode 100644 (file)
index 0000000..ffbd25a
--- /dev/null
@@ -0,0 +1,25 @@
+package Spreadsheet::WriteExcel::Big;
+
+use strict;
+
+use vars qw/ @ISA /;
+@ISA = qw( Spreadsheet::WriteExcel );
+
+use Spreadsheet::WriteExcel;
+
+use mock;
+
+sub new {
+    my $self = bless {
+    }, shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, ref($self) . "::new( '@_' )";
+    }
+
+    return $self;
+}
+
+1;
+__END__
diff --git a/t/Spreadsheet/WriteExcel/Worksheet.pm b/t/Spreadsheet/WriteExcel/Worksheet.pm
new file mode 100644 (file)
index 0000000..a3e6d16
--- /dev/null
@@ -0,0 +1,119 @@
+package Spreadsheet::WriteExcel::Worksheet;
+
+use strict;
+
+use mock;
+
+sub new {
+    my $self = bless {
+    }, shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::new( '@_' )";
+    }
+
+    return $self;
+}
+
+sub write_string {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::write_string( '@_' )";
+    }
+}
+
+sub write_number {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::write_number( '@_' )";
+    }
+}
+
+sub write_blank {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::write_blank( '@_' )";
+    }
+}
+
+sub write_url {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::write_url( '@_' )";
+    }
+}
+
+sub write_formula {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::write_formula( '@_' )";
+    }
+}
+
+sub write {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::write( '@_' )";
+    }
+}
+
+sub set_row {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::set_row( '@_' )";
+    }
+}
+
+sub set_column {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::set_column( '@_' )";
+    }
+}
+
+sub keep_leading_zeros {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::keep_leading_zeros( '@_' )";
+    }
+}
+
+sub insert_bitmap {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::insert_bitmap( '@_' )";
+    }
+}
+
+sub freeze_panes {
+    my $self = shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, __PACKAGE__ . "::freeze_panes( '@_' )";
+    }
+}
+
+1;
+__END__
diff --git a/t/Spreadsheet/WriteExcelXML.pm b/t/Spreadsheet/WriteExcelXML.pm
new file mode 100644 (file)
index 0000000..d79b767
--- /dev/null
@@ -0,0 +1,25 @@
+package Spreadsheet::WriteExcelXML;
+
+use strict;
+
+use vars qw/ @ISA /;
+@ISA = qw( Spreadsheet::WriteExcel );
+
+use Spreadsheet::WriteExcel;
+
+use mock;
+
+sub new {
+    my $self = bless {
+    }, shift;
+
+    {
+        local $" = "', '";
+        push @mock::calls, ref($self) . "::new( '@_' )";
+    }
+
+    return $self;
+}
+
+1;
+__END__
diff --git a/t/mock.pm b/t/mock.pm
new file mode 100644 (file)
index 0000000..3edd711
--- /dev/null
+++ b/t/mock.pm
@@ -0,0 +1,14 @@
+package mock;
+
+use strict;
+
+use vars qw/ @calls /;
+
+@calls = ();
+
+sub reset { @calls = (); }
+sub get_calls { @calls }
+
+
+1;
+__END__