The Apache POI team is pleased to announce the release of ${version.id}.
+ Featured are a handful of new areas of functionality and numerous bug fixes.
+
A summary of changes is available in the
+ Release Notes.
+ A full list of changes is available in the change log.
+ People interested should also follow the dev list
+ to track progress.
+
+ The POI source release as well as the pre-built binary deployment packages are listed below.
+ Pre-built versions of all POI components
+ are available in the central Maven repository under Group ID "org.apache.poi" and Version
+ "${version.id}".
+
+
+
+]]>
+
+
diff --git a/trunk/doap_POI.rdf b/trunk/doap_POI.rdf
new file mode 100644
index 000000000..5369344cd
--- /dev/null
+++ b/trunk/doap_POI.rdf
@@ -0,0 +1,135 @@
+
+
+
+
+
+ 2006-01-26
+
+ Apache POI
+
+
+ Java API To Access Microsoft Document File Formats
+ APIs for manipulating various file formats based upon Open Office XML (ECMA-376) and Microsoft's OLE 2 Compound Document formats using pure Java. Apache POI is your Java Excel, Word and PowerPoint solution. We have a complete API for porting other OOXML and OLE 2 Compound Document formats and welcome others to participate.
+
+
+
+ Java
+
+
+
+
+ Apache POI 3.14
+ 2016-03-05
+ 3.14
+
+
+
+
+ Apache POI 3.13
+ 2015-09-29
+ 3.13
+
+
+
+
+ Apache POI 3.12
+ 2015-05-11
+ 3.12
+
+
+
+
+ Apache POI 3.11
+ 2014-12-21
+ 3.11
+
+
+
+
+ Apache POI 3.10.1
+ 2014-08-18
+ 3.10.1
+
+
+
+
+ Apache POI 3.10
+ 2014-02-08
+ 3.10
+
+
+
+
+ Apache POI 3.9
+ 2012-12-03
+ 3.9
+
+
+
+
+ Apache POI 3.8
+ 2012-03-26
+ 3.8
+
+
+
+
+ Apache POI 3.7
+ 2010-10-29
+ 3.7
+
+
+
+
+ Apache POI 3.6
+ 2009-12-14
+ 3.6
+
+
+
+
+ Apache POI 3.5
+ 2009-09-28
+ 3.5
+
+
+
+
+ Apache POI 3.2
+ 2008-10-19
+ 3.2
+
+
+
+
+
+
+
+
+
+
+ POI Committers
+
+
+
+
+
diff --git a/trunk/legal/LICENSE b/trunk/legal/LICENSE
new file mode 100644
index 000000000..19246db04
--- /dev/null
+++ b/trunk/legal/LICENSE
@@ -0,0 +1,513 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+APACHE POI SUBCOMPONENTS:
+
+Apache POI includes subcomponents with separate copyright notices and
+license terms. Your use of these subcomponents is subject to the terms
+and conditions of the following licenses:
+
+
+Office Open XML schemas (ooxml-schemas-1.*.jar)
+
+ The Office Open XML schema definitions used by Apache POI are
+ a part of the Office Open XML ECMA Specification (ECMA-376, [1]).
+ As defined in section 9.4 of the ECMA bylaws [2], this specification
+ is available to all interested parties without restriction:
+
+ 9.4 All documents when approved shall be made available to
+ all interested parties without restriction.
+
+ Furthermore, both Microsoft and Adobe have granted patent licenses
+ to this work [3,4,5].
+
+ [1] http://www.ecma-international.org/publications/standards/Ecma-376.htm
+ [2] http://www.ecma-international.org/memento/Ecmabylaws.htm
+ [3] http://www.microsoft.com/openspecifications/en/us/programs/osp/default.aspx
+ [4] http://www.ecma-international.org/publications/files/ECMA-ST/Ecma%20PATENT/
+ Patent%20statements%20ok/ECMA-376%20Edition%202%20Microsoft%20Patent%20Declaration.pdf
+ [5] http://www.ecma-international.org/publications/files/ECMA-ST/Ecma%20PATENT/
+ Patent%20statements%20ok/ECMA-376%20Adobe%20Patent%20Declaration.pdf
+
+
+Bouncy Castle library (bcprov-*.jar, bcpg-*.jar, bcpkix-*.jar)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+
+JUnit test library (junit-4.*.jar) & JaCoCo (*jacoco*)
+
+ Eclipse Public License - v 1.0
+
+ THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
+ LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+ CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+ 1. DEFINITIONS
+
+ "Contribution" means:
+
+ a) in the case of the initial Contributor, the initial code and documentation
+ distributed under this Agreement, and
+ b) in the case of each subsequent Contributor:
+ i) changes to the Program, and
+ ii) additions to the Program;
+ where such changes and/or additions to the Program originate from and are
+ distributed by that particular Contributor. A Contribution 'originates' from
+ a Contributor if it was added to the Program by such Contributor itself or
+ anyone acting on such Contributor's behalf. Contributions do not include
+ additions to the Program which: (i) are separate modules of software
+ distributed in conjunction with the Program under their own license agreement,
+ and (ii) are not derivative works of the Program.
+
+ "Contributor" means any person or entity that distributes the Program.
+
+ "Licensed Patents" mean patent claims licensable by a Contributor which are
+ necessarily infringed by the use or sale of its Contribution alone or when
+ combined with the Program.
+
+ "Program" means the Contributions distributed in accordance with this Agreement.
+
+ "Recipient" means anyone who receives the Program under this Agreement,
+ including all Contributors.
+
+ 2. GRANT OF RIGHTS
+
+ a) Subject to the terms of this Agreement, each Contributor hereby grants
+ Recipient a non-exclusive, worldwide, royalty-free copyright license to
+ reproduce, prepare derivative works of, publicly display, publicly
+ perform, distribute and sublicense the Contribution of such Contributor,
+ if any, and such derivative works, in source code and object code form.
+ b) Subject to the terms of this Agreement, each Contributor hereby grants
+ Recipient a non-exclusive, worldwide, royalty-free patent license under
+ Licensed Patents to make, use, sell, offer to sell, import and otherwise
+ transfer the Contribution of such Contributor, if any, in source code
+ and object code form. This patent license shall apply to the combination
+ of the Contribution and the Program if, at the time the Contribution is
+ added by the Contributor, such addition of the Contribution causes such
+ combination to be covered by the Licensed Patents. The patent license
+ shall not apply to any other combinations which include the Contribution.
+ No hardware per se is licensed hereunder.
+ c) Recipient understands that although each Contributor grants the licenses
+ to its Contributions set forth herein, no assurances are provided by any
+ Contributor that the Program does not infringe the patent or other
+ intellectual property rights of any other entity. Each Contributor
+ disclaims any liability to Recipient for claims brought by any other
+ entity based on infringement of intellectual property rights or
+ otherwise. As a condition to exercising the rights and licenses granted
+ hereunder, each Recipient hereby assumes sole responsibility to secure
+ any other intellectual property rights needed, if any. For example, if
+ a third party patent license is required to allow Recipient to distribute
+ the Program, it is Recipient's responsibility to acquire that license
+ before distributing the Program.
+ d) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+ 3. REQUIREMENTS
+
+ A Contributor may choose to distribute the Program in object code form under
+ its own license agreement, provided that:
+
+ a) it complies with the terms and conditions of this Agreement; and
+ b) its license agreement:
+ i) effectively disclaims on behalf of all Contributors all warranties and
+ conditions, express and implied, including warranties or conditions of
+ title and non-infringement, and implied warranties or conditions of
+ merchantability and fitness for a particular purpose;
+ ii) effectively excludes on behalf of all Contributors all liability for
+ damages, including direct, indirect, special, incidental and
+ consequential damages, such as lost profits;
+ iii) states that any provisions which differ from this Agreement are
+ offered by that Contributor alone and not by any other party; and
+ iv) states that source code for the Program is available from such
+ Contributor, and informs licensees how to obtain it in a reasonable
+ manner on or through a medium customarily used for software exchange.
+
+ When the Program is made available in source code form:
+
+ a) it must be made available under this Agreement; and
+ b) a copy of this Agreement must be included with each copy of the Program.
+ Contributors may not remove or alter any copyright notices contained
+ within the Program.
+
+ Each Contributor must identify itself as the originator of its Contribution,
+ if any, in a manner that reasonably allows subsequent Recipients to identify
+ the originator of the Contribution.
+
+ 4. COMMERCIAL DISTRIBUTION
+
+ Commercial distributors of software may accept certain responsibilities with
+ respect to end users, business partners and the like. While this license is
+ intended to facilitate the commercial use of the Program, the Contributor
+ who includes the Program in a commercial product offering should do so in a
+ manner which does not create potential liability for other Contributors.
+ Therefore, if a Contributor includes the Program in a commercial product
+ offering, such Contributor ("Commercial Contributor") hereby agrees to
+ defend and indemnify every other Contributor ("Indemnified Contributor")
+ against any losses, damages and costs (collectively "Losses") arising from
+ claims, lawsuits and other legal actions brought by a third party against
+ the Indemnified Contributor to the extent caused by the acts or omissions
+ of such Commercial Contributor in connection with its distribution of the
+ Program in a commercial product offering. The obligations in this section
+ do not apply to any claims or Losses relating to any actual or alleged
+ intellectual property infringement. In order to qualify, an Indemnified
+ Contributor must: a) promptly notify the Commercial Contributor in writing
+ of such claim, and b) allow the Commercial Contributor to control, and
+ cooperate with the Commercial Contributor in, the defense and any related
+ settlement negotiations. The Indemnified Contributor may participate in any
+ such claim at its own expense.
+
+ For example, a Contributor might include the Program in a commercial product
+ offering, Product X. That Contributor is then a Commercial Contributor. If
+ that Commercial Contributor then makes performance claims, or offers
+ warranties related to Product X, those performance claims and warranties are
+ such Commercial Contributor's responsibility alone. Under this section, the
+ Commercial Contributor would have to defend claims against the other
+ Contributors related to those performance claims and warranties, and if a
+ court requires any other Contributor to pay any damages as a result, the
+ Commercial Contributor must pay those damages.
+
+ 5. NO WARRANTY
+
+ EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
+ AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+ EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
+ CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
+ PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
+ appropriateness of using and distributing the Program and assumes all risks
+ associated with its exercise of rights under this Agreement , including but
+ not limited to the risks and costs of program errors, compliance with
+ applicable laws, damage to or loss of data, programs or equipment, and
+ unavailability or interruption of operations.
+
+ 6. DISCLAIMER OF LIABILITY
+
+ EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+ CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
+ LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+ EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
+ OF SUCH DAMAGES.
+
+ 7. GENERAL
+
+ If any provision of this Agreement is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of the
+ remainder of the terms of this Agreement, and without further action by the
+ parties hereto, such provision shall be reformed to the minimum extent
+ necessary to make such provision valid and enforceable.
+
+ If Recipient institutes patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+ (excluding combinations of the Program with other software or hardware)
+ infringes such Recipient's patent(s), then such Recipient's rights granted
+ under Section 2(b) shall terminate as of the date such litigation is filed.
+
+ All Recipient's rights under this Agreement shall terminate if it fails to
+ comply with any of the material terms or conditions of this Agreement and
+ does not cure such failure in a reasonable period of time after becoming
+ aware of such noncompliance. If all Recipient's rights under this Agreement
+ terminate, Recipient agrees to cease use and distribution of the Program as
+ soon as reasonably practicable. However, Recipient's obligations under this
+ Agreement and any licenses granted by Recipient relating to the Program
+ shall continue and survive.
+
+ Everyone is permitted to copy and distribute copies of this Agreement, but
+ in order to avoid inconsistency the Agreement is copyrighted and may only
+ be modified in the following manner. The Agreement Steward reserves the
+ right to publish new versions (including revisions) of this Agreement from
+ time to time. No one other than the Agreement Steward has the right to
+ modify this Agreement. The Eclipse Foundation is the initial Agreement
+ Steward. The Eclipse Foundation may assign the responsibility to serve as
+ the Agreement Steward to a suitable separate entity. Each new version of
+ the Agreement will be given a distinguishing version number. The Program
+ (including Contributions) may always be distributed subject to the version
+ of the Agreement under which it was received. In addition, after a new
+ version of the Agreement is published, Contributor may elect to distribute
+ the Program (including its Contributions) under the new version. Except as
+ expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+ rights or licenses to the intellectual property of any Contributor under
+ this Agreement, whether expressly, by implication, estoppel or otherwise.
+ All rights in the Program not expressly granted under this Agreement are
+ reserved.
+
+ This Agreement is governed by the laws of the State of New York and the
+ intellectual property laws of the United States of America. No party to this
+ Agreement will bring a legal action under this Agreement more than one year
+ after the cause of action arose. Each party waives its rights to a jury
+ trial in any resulting litigation.
+
+Hamcrest library (hamcrest-*.jar) & CuvesAPI / Curve API
+
+ BSD License
+
+ Copyright (c) 2000-2006, www.hamcrest.org
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer. Redistributions in binary
+ form must reproduce the above copyright notice, this list of conditions and
+ the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ Neither the name of Hamcrest nor the names of its contributors may be used
+ to endorse or promote products derived from this software without specific
+ prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+SLF4J library (slf4j-api-*.jar)
+
+ Copyright (c) 2004-2013 QOS.ch
+ All rights reserved.
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/trunk/legal/NOTICE b/trunk/legal/NOTICE
new file mode 100644
index 000000000..43d9b3b23
--- /dev/null
+++ b/trunk/legal/NOTICE
@@ -0,0 +1,23 @@
+Apache POI
+Copyright 2003-2016 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (https://www.apache.org/).
+
+This product contains parts that were originally based on software from BEA.
+Copyright (c) 2000-2003, BEA Systems, .
+
+This product contains W3C XML Schema documents. Copyright 2001-2003 (c)
+World Wide Web Consortium (Massachusetts Institute of Technology, European
+Research Consortium for Informatics and Mathematics, Keio University)
+
+This product contains the Piccolo XML Parser for Java
+(http://piccolo.sourceforge.net/). Copyright 2002 Yuval Oren.
+
+This product contains the chunks_parse_cmds.tbl file from the vsdump program.
+Copyright (C) 2006-2007 Valek Filippov (frob@df.ru)
+
+This product contains parts of the eID Applet project
+(http://eid-applet.googlecode.com). Copyright (c) 2009-2014
+FedICT (federal ICT department of Belgium), e-Contract.be BVBA (https://www.e-contract.be),
+Bart Hanssens from FedICT
diff --git a/trunk/maven/multisign.sh b/trunk/maven/multisign.sh
new file mode 100755
index 000000000..1ba8c91f4
--- /dev/null
+++ b/trunk/maven/multisign.sh
@@ -0,0 +1,54 @@
+#! /bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# create md5 checksums and gpg signatures
+
+echo "If you use gpg2 you need to set GPG_BIN accordingly"
+
+GPG_BIN=gpg
+
+stty -echo
+echo "enter your GPG passphrase"
+read passphrase
+stty echo
+
+# Do we have md5sum and sha1sum?
+# (We can use openssl if not, but the files it produces aren't as nice)
+which md5sum > /dev/null
+LACKING_MD5SUM=$?
+which sha1sum > /dev/null
+LACKING_SHA1SUM=$?
+
+for i in *; do
+ echo ""
+ echo Signing $i
+ echo $passphrase | $GPG_BIN --passphrase-fd 0 --output $i.asc --detach-sig --armor $i
+ $GPG_BIN --verify $i.asc $i
+ echo Hashing $i
+
+ if [ "$LACKING_MD5SUM" = "1" ]; then
+ openssl md5 < $i > $i.md5
+ else
+ md5sum $i > $i.md5
+ fi
+
+ if [ "$LACKING_SHA1SUM" = "1" ]; then
+ openssl sha1 < $i > $i.sha1
+ else
+ sha1sum $i > $i.sha1
+ fi
+done
diff --git a/trunk/maven/mvn-deploy.sh b/trunk/maven/mvn-deploy.sh
new file mode 100755
index 000000000..a66936a13
--- /dev/null
+++ b/trunk/maven/mvn-deploy.sh
@@ -0,0 +1,62 @@
+#! /bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Shell script to deploy POI artifacts in a maven repository.
+#
+# Note, You should configure your settings.xml and add a server with id=apache-releases:
+#
+#
+# apache-releases
+# apacheId
+# mySecurePassw0rd
+#
+#
+#
+#
+# apache-releases
+#
+#
+#
+#
+#
+#
+# Usage:
+# 1. ant dist
+# 2. cd build/dist
+# 3. ./mvn-deploy.sh
+
+M2_REPOSITORY=https://repository.apache.org/service/local/staging/deploy/maven2
+
+VERSION=@VERSION@
+DSTAMP=@DSTAMP@
+
+for artifactId in poi poi-scratchpad poi-ooxml poi-examples poi-ooxml-schemas poi-excelant
+do
+ SENDS="-Dfile=$artifactId-$VERSION-$DSTAMP.jar"
+ SENDS="$SENDS -DpomFile=$artifactId-$VERSION.pom"
+ if [ -r $artifactId-$VERSION-sources-$DSTAMP.jar ]; then
+ SENDS="$SENDS -Dsources=$artifactId-$VERSION-sources-$DSTAMP.jar"
+ fi
+ if [ -r $artifactId-$VERSION-javadocs-$DSTAMP.jar ]; then
+ SENDS="$SENDS -Djavadoc=$artifactId-$VERSION-javadocs-$DSTAMP.jar"
+ fi
+
+ mvn gpg:sign-and-deploy-file \
+ -DrepositoryId=apache-releases -P apache-releases \
+ -Durl=$M2_REPOSITORY \
+ $SENDS
+done
diff --git a/trunk/maven/ooxml-schemas.pom b/trunk/maven/ooxml-schemas.pom
new file mode 100644
index 000000000..da49544ae
--- /dev/null
+++ b/trunk/maven/ooxml-schemas.pom
@@ -0,0 +1,70 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ ooxml-schemas
+ @VERSION@
+ jar
+ OOXML schemas
+ XmlBeans generated from the Ecma supplied xsds:
+ http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%20Part%204%20(DOCX).zip
+ http://poi.apache.org/
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ 2.3.0
+
+
+
diff --git a/trunk/maven/ooxml-security.pom b/trunk/maven/ooxml-security.pom
new file mode 100644
index 000000000..de9112e21
--- /dev/null
+++ b/trunk/maven/ooxml-security.pom
@@ -0,0 +1,79 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ ooxml-security
+ @VERSION@
+ jar
+ OOXML security
+
+ XmlBeans generated from various supplied xsds for encryption and signing:
+ http://msdn.microsoft.com/en-us/library/dd925810(v=office.12).aspx
+ http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%201st%20edition%20Part%202%20(PDF).zip
+ http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd
+ http://uri.etsi.org/01903/v1.3.2/XAdES.xsd
+ http://uri.etsi.org/01903/v1.4.1/XAdESv141.xsd
+ http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd
+ http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd
+ http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcmitype.xsd
+
+ http://poi.apache.org/
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ 2.3.0
+
+
+
diff --git a/trunk/maven/poi-examples.pom b/trunk/maven/poi-examples.pom
new file mode 100644
index 000000000..249f2500f
--- /dev/null
+++ b/trunk/maven/poi-examples.pom
@@ -0,0 +1,78 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ poi-examples
+ @VERSION@
+ jar
+ Apache POI
+ http://poi.apache.org/
+ Apache POI Examples
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.poi
+ poi
+ @VERSION@
+
+
+ org.apache.poi
+ poi-scratchpad
+ @VERSION@
+
+
+ org.apache.poi
+ poi-ooxml
+ @VERSION@
+
+
+
diff --git a/trunk/maven/poi-excelant.pom b/trunk/maven/poi-excelant.pom
new file mode 100644
index 000000000..6b23e4a12
--- /dev/null
+++ b/trunk/maven/poi-excelant.pom
@@ -0,0 +1,78 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ poi-excelant
+ @VERSION@
+ jar
+ Apache POI
+ http://poi.apache.org/
+ Apache POI Excel Ant Tasks
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.poi
+ poi
+ @VERSION@
+
+
+ org.apache.poi
+ poi-ooxml
+ @VERSION@
+
+
+ org.apache.ant
+ ant
+ 1.8.2
+
+
+
diff --git a/trunk/maven/poi-ooxml-schemas.pom b/trunk/maven/poi-ooxml-schemas.pom
new file mode 100644
index 000000000..baf531d54
--- /dev/null
+++ b/trunk/maven/poi-ooxml-schemas.pom
@@ -0,0 +1,68 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ poi-ooxml-schemas
+ @VERSION@
+ jar
+ Apache POI
+ http://poi.apache.org/
+ Apache POI - Java API To Access Microsoft Format Files
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ 2.6.0
+
+
+
diff --git a/trunk/maven/poi-ooxml.pom b/trunk/maven/poi-ooxml.pom
new file mode 100644
index 000000000..0f3cdb1d5
--- /dev/null
+++ b/trunk/maven/poi-ooxml.pom
@@ -0,0 +1,78 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ poi-ooxml
+ @VERSION@
+ jar
+ Apache POI
+ http://poi.apache.org/
+ Apache POI - Java API To Access Microsoft Format Files
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.poi
+ poi
+ @VERSION@
+
+
+ org.apache.poi
+ poi-ooxml-schemas
+ @VERSION@
+
+
+ com.github.virtuald
+ curvesapi
+ 1.04
+
+
+
diff --git a/trunk/maven/poi-scratchpad.pom b/trunk/maven/poi-scratchpad.pom
new file mode 100644
index 000000000..d1b4392de
--- /dev/null
+++ b/trunk/maven/poi-scratchpad.pom
@@ -0,0 +1,68 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ poi-scratchpad
+ @VERSION@
+ jar
+ Apache POI
+ http://poi.apache.org/
+ Apache POI - Java API To Access Microsoft Format Files
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ org.apache.poi
+ poi
+ @VERSION@
+
+
+
diff --git a/trunk/maven/poi.pom b/trunk/maven/poi.pom
new file mode 100644
index 000000000..efd24cfe0
--- /dev/null
+++ b/trunk/maven/poi.pom
@@ -0,0 +1,101 @@
+
+
+
+
+
+ 4.0.0
+ org.apache.poi
+ poi
+ @VERSION@
+ jar
+ Apache POI
+ http://poi.apache.org/
+ Apache POI - Java API To Access Microsoft Format Files
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+
+ commons-logging
+ commons-logging
+ 1.2
+ runtime
+ true
+
+
+ log4j
+ log4j
+ 1.2.17
+ runtime
+ true
+
+
+ commons-codec
+ commons-codec
+ 1.10
+
+
+
+ org.hamcrest
+ hamcrest-core
+ test
+ 1.3
+
+
+ junit
+ junit
+ test
+ 4.12
+
+
+ org.apache.commons
+ commons-collections4
+ 4.1
+
+
+
+
diff --git a/trunk/osgi/build.xml b/trunk/osgi/build.xml
new file mode 100644
index 000000000..35dd87bcc
--- /dev/null
+++ b/trunk/osgi/build.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+ The Apache POI OSGi Bundle System.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trunk/osgi/bundle.info b/trunk/osgi/bundle.info
new file mode 100644
index 000000000..1886a0aef
--- /dev/null
+++ b/trunk/osgi/bundle.info
@@ -0,0 +1,38 @@
+\u001B[1mSYNOPSIS\u001B[0m
+ ${project.description}
+
+ Original Maven URLs:
+ \u001B[33mmvn:${pkgGroupId}/poi/${pkgVersion}\u001B[0m
+ \u001B[33mmvn:${pkgGroupId}/poi-ooxml/${pkgVersion}\u001B[0m
+ \u001B[33mmvn:${pkgGroupId}/poi-scratchpad/${pkgVersion}\u001B[0m
+
+\u001B[1mDESCRIPTION\u001B[0m
+ The Apache POI Project's mission is to create and maintain Java APIs for manipulating various file formats based
+ upon the Office Open XML standards (OOXML) and Microsoft's OLE 2 Compound Document format (OLE2). In short, you can
+ read and write MS Excel files using Java. In addition, you can read and write MS Word and MS PowerPoint files using
+ Java. Apache POI is your Java Excel solution (for Excel 97-2008). We have a complete API for porting other OOXML and
+ OLE2 formats and welcome others to participate.
+
+ OLE2 files include most Microsoft Office files such as XLS, DOC, and PPT as well as MFC serialization API based file
+ formats. The project provides APIs for the OLE2 Filesystem (POIFS) and OLE2 Document Properties (HPSF).
+
+ Office OpenXML Format is the new standards based XML file format found in Microsoft Office 2007 and 2008. This
+ includes XLSX, DOCX and PPTX. The project provides a low level API to support the Open Packaging Conventions using
+ openxml4j.
+
+ For each MS Office application there exists a component module that attempts to provide a common high level Java api
+ to both OLE2 and OOXML document formats. This is most developed for Excel workbooks (SS=HSSF+XSSF). Work is
+ progressing for Word documents (HWPF+XWPF) and PowerPoint presentations (HSLF+XSLF).
+
+ The project has recently added support for Outlook (HSMF). Microsoft opened the specifications to this format in
+ October 2007. We would welcome contributions.
+
+ There are also projects for Visio (HDGF), TNEF (HMEF), and Publisher (HPBF).
+
+ As a general policy we collaborate as much as possible with other projects to provide this functionality.
+ Examples include: Cocoon for which there are serializers for HSSF; Open Office.org with whom we collaborate in
+ documenting the XLS format; and Tika / Lucene, for which we provide format interpretors.
+ When practical, we donate components directly to those projects for POI-enabling them.
+
+\u001B[1mSEE ALSO\u001B[0m
+ \u001B[36mhttp://poi.apache.org/\u001B[0m
diff --git a/trunk/osgi/pom.xml b/trunk/osgi/pom.xml
new file mode 100644
index 000000000..2849467a9
--- /dev/null
+++ b/trunk/osgi/pom.xml
@@ -0,0 +1,229 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache
+ apache
+ 10
+
+
+
+ org.apache.poi
+ poi-bundle
+ bundle
+ Apache POI OSGi bundle
+
+ OSGi bundle that contains Apache POI, and the dependencies.
+
+ http://poi.apache.org/
+ ${poi.version}
+
+
+
+ 1.6
+ 1.6
+ 4.4.0
+
+
+
+
+ ${project.groupId}
+ poi
+ ${poi.version}
+
+
+ ${project.groupId}
+ poi-scratchpad
+ ${poi.version}
+
+
+ ${project.groupId}
+ poi-ooxml
+ ${poi.version}
+
+
+
+
+ junit
+ junit
+ 4.12
+
+
+ org.ops4j.pax.exam
+ pax-exam-junit4
+ ${pax.exam.version}
+ test
+
+
+ org.ops4j.pax.exam
+ pax-exam-container-native
+ ${pax.exam.version}
+ test
+
+
+ org.apache.felix
+ org.apache.felix.framework
+ 4.6.0
+ test
+
+
+ org.ops4j.pax.exam
+ pax-exam-link-assembly
+ ${pax.exam.version}
+ test
+
+
+ org.ops4j.pax.url
+ pax-url-aether
+ 2.3.0
+ test
+
+
+ javax.inject
+ javax.inject
+ 1
+ test
+
+
+ org.osgi
+ org.osgi.core
+ 5.0.0
+ provided
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+
+ org.apache.poi.osgi.Activator
+
+
+ poi;inline=true,
+ poi-scratchpad;inline=true,
+ poi-ooxml;inline=true,
+ poi-ooxml-schemas,
+ xmlbeans
+
+ true
+ ${project.url}
+
+ org.apache.poi.*
+
+
+ !org.junit,
+ *,
+ org.apache.xmlbeans.impl.xpath.saxon;resolution:=optional,
+ org.apache.xmlbeans.impl.xquery.saxon;resolution:=optional,
+ org.bouncycastle.cert;resolution:=optional,
+ org.bouncycastle.cert.ocsp;resolution:=optional,
+ org.bouncycastle.cms.bc;resolution:=optional,
+ org.bouncycastle.cert.jcajce;resolution:=optional,
+ org.bouncycastle.operator;resolution:=optional,
+ org.bouncycastle.operator.bc;resolution:=optional,
+ org.bouncycastle.tsp;resolution:=optional,
+ org.openxmlformats.schemas.officeDocument.x2006.math;resolution:=optional,
+ org.openxmlformats.schemas.schemaLibrary.x2006.main;resolution:=optional,
+ schemasMicrosoftComOfficePowerpoint;resolution:=optional,
+ schemasMicrosoftComOfficeWord;resolution:=optional,
+
+
+
+
+
+ maven-compiler-plugin
+ 3.2
+
+
+ ${maven.compiler.target}
+
+
+
+
+
+
+
+ java6
+
+ [1.6,)
+
+
+
+
+ maven-assembly-plugin
+
+
+ pre-integration-test
+
+ single
+
+
+ test-bundles.xml
+ test
+ false
+
+
+
+
+
+ maven-failsafe-plugin
+ 2.10
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+ WARN
+
+
+
+
+
+
+
+
+
+
+ The Apache Software Founation
+ http://www.apache.org
+
+
+ http://svn.apache.org/viewvc/poi/trunk/osgi
+ scm:svn:http://svn.apache.org/repos/asf/poi/trunk/osgi
+ scm:svn:https://svn.apache.org/repos/asf/poi/trunk/osgi
+
+
diff --git a/trunk/osgi/src/main/java/org/apache/poi/osgi/Activator.java b/trunk/osgi/src/main/java/org/apache/poi/osgi/Activator.java
new file mode 100644
index 000000000..2f0212bd4
--- /dev/null
+++ b/trunk/osgi/src/main/java/org/apache/poi/osgi/Activator.java
@@ -0,0 +1,28 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.osgi;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+ public void start(BundleContext context) throws Exception {
+ }
+ public void stop(BundleContext context) throws Exception {
+ }
+}
diff --git a/trunk/osgi/src/test/java/org/apache/poi/osgi/TestOSGiBundle.java b/trunk/osgi/src/test/java/org/apache/poi/osgi/TestOSGiBundle.java
new file mode 100644
index 000000000..738d96b99
--- /dev/null
+++ b/trunk/osgi/src/test/java/org/apache/poi/osgi/TestOSGiBundle.java
@@ -0,0 +1,84 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.osgi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.options;
+
+import javax.inject.Inject;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerMethod;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Test to ensure that all our main formats can create, write
+ * and read back in, when running under OSGi
+ */
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerMethod.class)
+public class TestOSGiBundle {
+
+ private final File TARGET = new File("target");
+
+ @Inject
+ private BundleContext bc;
+
+ @Configuration
+ public Option[] configuration() throws IOException, URISyntaxException {
+ File base = new File(TARGET, "test-bundles");
+ return options(
+ junitBundles(),
+ bundle(new File(base, "poi-bundle.jar").toURI().toURL().toString()));
+ }
+
+ @Test
+ public void testHSSF() throws Exception {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet s = wb.createSheet("OSGi");
+ s.createRow(0).createCell(0).setCellValue("With OSGi");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ wb.write(baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+
+ wb = new HSSFWorkbook(bais);
+ assertEquals(1, wb.getNumberOfSheets());
+
+ s = wb.getSheet("OSGi");
+ assertEquals("With OSGi", s.getRow(0).getCell(0).toString());
+ }
+}
diff --git a/trunk/osgi/test-bundles.xml b/trunk/osgi/test-bundles.xml
new file mode 100644
index 000000000..11502761e
--- /dev/null
+++ b/trunk/osgi/test-bundles.xml
@@ -0,0 +1,34 @@
+
+
+ bundles
+
+ dir
+
+ false
+
+
+
+ ${artifact.artifactId}.jar
+
+ org.apache.poi:poi-bundle
+
+
+
+
diff --git a/trunk/patch.xml b/trunk/patch.xml
new file mode 100644
index 000000000..ed52a91d7
--- /dev/null
+++ b/trunk/patch.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trunk/sonar/examples/pom.xml b/trunk/sonar/examples/pom.xml
new file mode 100644
index 000000000..e0e6a24a1
--- /dev/null
+++ b/trunk/sonar/examples/pom.xml
@@ -0,0 +1,76 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+
+ poi-examples
+ jar
+
+ Apache POI Examples package
+
+
+
+
+
+ maven-resources-plugin
+ ${maven.plugin.resources.version}
+
+
+ copy-sources
+
+ generate-sources
+
+ copy-resources
+
+
+ ${basedir}/src/main/java
+
+
+ ../../src/examples/src
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ ${maven.plugin.clean.version}
+
+
+
+ src
+ false
+
+
+
+
+
+
+
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-ooxml
+ ${project.version}
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+
+
+
diff --git a/trunk/sonar/excelant/pom.xml b/trunk/sonar/excelant/pom.xml
new file mode 100644
index 000000000..db1a4513d
--- /dev/null
+++ b/trunk/sonar/excelant/pom.xml
@@ -0,0 +1,114 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+
+ poi-excelant
+ jar
+
+ Apache POI ExcelAnt package
+
+
+
+
+
+ maven-resources-plugin
+ ${maven.plugin.resources.version}
+
+
+ copy-sources
+
+ generate-sources
+
+ copy-resources
+
+
+ ${basedir}/src/main/java
+
+
+ ../../src/excelant/java
+
+
+
+
+
+ copy-resources
+
+ generate-resources
+
+ copy-resources
+
+
+ ${basedir}/src/main/resources
+
+
+ ../../src/excelant/resources
+
+
+
+
+
+ copy-tests
+
+ generate-test-sources
+
+ copy-resources
+
+
+ ${basedir}/src/test/java
+
+
+ ../../src/excelant/testcases
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ ${maven.plugin.clean.version}
+
+
+
+ src
+ false
+
+
+
+
+
+
+
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+ test-jar
+ test
+
+
+ ${project.groupId}
+ poi-ooxml
+ ${project.version}
+
+
+
+ org.apache.ant
+ ant
+ 1.8.2
+
+
+
diff --git a/trunk/sonar/main/pom.xml b/trunk/sonar/main/pom.xml
new file mode 100644
index 000000000..ffc9499d9
--- /dev/null
+++ b/trunk/sonar/main/pom.xml
@@ -0,0 +1,129 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+
+ poi-main
+ jar
+
+ Apache POI Main package
+
+
+
+
+
+ maven-resources-plugin
+ ${maven.plugin.resources.version}
+
+
+ copy-sources
+ generate-sources
+
+ copy-resources
+
+
+ ${basedir}/src/main/java
+
+
+ ../../src/java
+
+
+
+
+
+ copy-resources
+ generate-resources
+
+ copy-resources
+
+
+ ${basedir}/src/main/resources
+
+
+ ../../src/resources/main
+
+
+
+
+
+ copy-tests
+
+ generate-test-sources
+
+ copy-resources
+
+
+ ${basedir}/src/test/java
+
+
+ ../../src/testcases
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ ${maven.plugin.clean.version}
+
+
+
+ src
+ false
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${maven.plugin.jar.version}
+
+
+
+ test-jar
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.plugin.surefire.version}
+
+ -Duser.language=en -Duser.country=US -Xmx1024m -XX:MaxPermSize=256m -XX:-OmitStackTraceInFastThrow
+
+
+
+
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.1
+
+
+ commons-codec
+ commons-codec
+ 1.10
+
+
+ commons-logging
+ commons-logging
+ 1.2
+
+
+
diff --git a/trunk/sonar/ooxml-schema-encryption/pom.xml b/trunk/sonar/ooxml-schema-encryption/pom.xml
new file mode 100644
index 000000000..1366a2bad
--- /dev/null
+++ b/trunk/sonar/ooxml-schema-encryption/pom.xml
@@ -0,0 +1,55 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+ ..
+
+ poi-ooxml-schema-encryption
+ jar
+
+ Apache POI - Openxmlformats Encryption Schema package
+
+
+
+ target/generated-sources/*
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ ${maven.plugin.antrun.version}
+
+
+ unzip-schema
+ generate-sources
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ ${xmlbeans.version}
+
+
+
diff --git a/trunk/sonar/ooxml-schema-encryption/xmlbeans.marker b/trunk/sonar/ooxml-schema-encryption/xmlbeans.marker
new file mode 100644
index 000000000..2744493ac
--- /dev/null
+++ b/trunk/sonar/ooxml-schema-encryption/xmlbeans.marker
@@ -0,0 +1 @@
+This purpose of this marker file is solely to activate the xmlbeans maven profile.
\ No newline at end of file
diff --git a/trunk/sonar/ooxml-schema-security/pom.xml b/trunk/sonar/ooxml-schema-security/pom.xml
new file mode 100644
index 000000000..336bf88fb
--- /dev/null
+++ b/trunk/sonar/ooxml-schema-security/pom.xml
@@ -0,0 +1,144 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+ ..
+
+ poi-ooxml-schema-security
+ jar
+
+ Apache POI - Openxmlformats Security-Schema package
+
+
+
+ target/generated-sources/*
+ true
+ true
+ true
+
+
+
+
+
+
+ com.googlecode.maven-download-plugin
+ maven-download-plugin
+ ${maven.plugin.download.version}
+
+
+ install-xsds-part-1
+ generate-sources
+ wget
+
+ http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%201st%20edition%20Part%202%20(PDF).zip
+ true
+ c8f0eac388691d5be0d1647146400a10
+
+
+
+ install-xsds-part-2
+ generate-sources
+ wget
+
+ target/schemas
+ http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd
+
+
+
+ install-xsds-part-3
+ generate-sources
+ wget
+
+ target/schemas
+ http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd
+
+
+
+ install-xsds-part-4
+ generate-sources
+ wget
+
+ target/schemas
+ http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcmitype.xsd
+
+
+
+ install-xsds-part-5
+ generate-sources
+ wget
+
+ target/schemas
+ http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd
+
+
+
+ install-xsds-part-6
+ generate-sources
+ wget
+
+ target/schemas
+ http://uri.etsi.org/01903/v1.3.2/XAdES.xsd
+
+
+
+ install-xsds-part-7
+ generate-sources
+ wget
+
+ target/schemas
+ http://uri.etsi.org/01903/v1.4.1/XAdESv141.xsd
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ ${maven.plugin.antrun.version}
+
+
+ unzip-schema
+ generate-sources
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ ${xmlbeans.version}
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-scratchpad
+ ${project.version}
+
+
+
diff --git a/trunk/sonar/ooxml-schema-security/xmlbeans.marker b/trunk/sonar/ooxml-schema-security/xmlbeans.marker
new file mode 100644
index 000000000..2744493ac
--- /dev/null
+++ b/trunk/sonar/ooxml-schema-security/xmlbeans.marker
@@ -0,0 +1 @@
+This purpose of this marker file is solely to activate the xmlbeans maven profile.
\ No newline at end of file
diff --git a/trunk/sonar/ooxml-schema/pom.xml b/trunk/sonar/ooxml-schema/pom.xml
new file mode 100644
index 000000000..c6e7d7d85
--- /dev/null
+++ b/trunk/sonar/ooxml-schema/pom.xml
@@ -0,0 +1,89 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+ ..
+
+ poi-ooxml-schema
+ jar
+
+ Apache POI - Openxmlformats Schema package
+
+
+
+ target/generated-sources/*
+ true
+ ${basedir}/../../src/ooxml/resources/org/apache/poi/schemas/ooxmlSchemas.xsdconfig
+
+
+
+
+
+
+ com.googlecode.maven-download-plugin
+ maven-download-plugin
+ ${maven.plugin.download.version}
+
+
+ download-xsds
+ generate-sources
+
+ wget
+
+
+ http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%201st%20edition%20Part%204%20(PDF).zip
+ true
+ abe6bb6e7799e854934b3c634e8bcf7b
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+ ${maven.plugin.antrun.version}
+
+
+ unzip-schema
+ generate-sources
+
+
+
+
+
+
+
+
+ run
+
+
+
+
+
+
+
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-scratchpad
+ ${project.version}
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ ${xmlbeans.version}
+
+
+
diff --git a/trunk/sonar/ooxml-schema/xmlbeans.marker b/trunk/sonar/ooxml-schema/xmlbeans.marker
new file mode 100644
index 000000000..2744493ac
--- /dev/null
+++ b/trunk/sonar/ooxml-schema/xmlbeans.marker
@@ -0,0 +1 @@
+This purpose of this marker file is solely to activate the xmlbeans maven profile.
\ No newline at end of file
diff --git a/trunk/sonar/ooxml/pom.xml b/trunk/sonar/ooxml/pom.xml
new file mode 100644
index 000000000..3a0079cf6
--- /dev/null
+++ b/trunk/sonar/ooxml/pom.xml
@@ -0,0 +1,165 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+
+ poi-ooxml
+ jar
+
+ Apache POI OOXML package
+
+
+
+
+
+ maven-resources-plugin
+ ${maven.plugin.resources.version}
+
+
+ copy-sources
+ generate-sources
+
+ copy-resources
+
+
+ ${basedir}/src/main/java
+
+
+ ../../src/ooxml/java
+
+
+
+
+
+ copy-resources
+ generate-resources
+
+ copy-resources
+
+
+ ${basedir}/src/main/resources
+
+
+ ../../src/resources/ooxml
+
+
+
+
+
+ copy-tests
+ generate-test-sources
+
+ copy-resources
+
+
+ ${basedir}/src/test/java
+
+
+ ../../src/ooxml/testcases
+
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ ${maven.plugin.clean.version}
+
+
+
+ src
+ false
+
+
+ build
+ false
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.plugin.surefire.version}
+
+ -Duser.language=en -Duser.country=US -Xmx1024m -XX:MaxPermSize=256m -XX:-OmitStackTraceInFastThrow
+
+
+
+
+
+
+
+ ${project.groupId}
+ poi-ooxml-schema
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-ooxml-schema-encryption
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-ooxml-schema-security
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+ test-jar
+ test
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ ${xmlbeans.version}
+
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ 1.54
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ 1.54
+
+
+ org.apache.santuario
+ xmlsec
+ 2.0.5
+
+
+ com.github.virtuald
+ curvesapi
+ 1.04
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+
+
+
diff --git a/trunk/sonar/pom.xml b/trunk/sonar/pom.xml
new file mode 100644
index 000000000..508a02343
--- /dev/null
+++ b/trunk/sonar/pom.xml
@@ -0,0 +1,272 @@
+
+ 4.0.0
+ org.apache.poi
+ poi-parent
+ pom
+ 3.15-beta3-SNAPSHOT
+ Apache POI - the Java API for Microsoft Documents
+ Maven build of Apache POI for Sonar checks
+ http://poi.apache.org/
+
+
+
+ POI Users List
+ user-subscribe@poi.apache.org
+ user-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-user/
+
+
+ POI Developer List
+ dev-subscribe@poi.apache.org
+ dev-unsubscribe@poi.apache.org
+ http://mail-archives.apache.org/mod_mbox/poi-dev/
+
+
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Apache Software Foundation
+ http://www.apache.org/
+
+
+
+ bugzilla
+ https://issues.apache.org/bugzilla/
+
+
+
+ scm:svn:http://svn.apache.org/repos/asf/poi/trunk
+ scm:svn:https://svn.apache.org/repos/asf/poi/trunk
+ http://svn.apache.org/viewvc/poi
+
+
+
+ main
+ ooxml-schema
+ ooxml-schema-encryption
+ ooxml-schema-security
+ ooxml
+ scratchpad
+ excelant
+ examples
+
+
+
+ ASCII
+
+ true
+
+
+ 2.6.0
+ 4.12
+ 3.0.1
+ 3.0.1
+ 3.0.0
+ 1.1.0
+ 1.8
+ 2.19.1
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ 1.6
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven.plugin.surefire.version}
+
+
+ org.apache.maven.surefire
+ surefire-junit47
+ ${maven.plugin.surefire.version}
+
+
+
+
+ ../../test-data
+ true
+ org.apache.poi.util.NullLogger
+
+
+ -Duser.language=en -Duser.country=US -Xmx1024m
+
+ **/All*Tests.java
+ **/TestUnfixedBugs.java
+ **/TestcaseRecordInputStream.java
+ **/POITestCase.java
+
+ **/TestWordToConverterSuite*.java
+ **/TestExcelConverterSuite*.java
+
+
+
+
+
+
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+ 32bitstuff
+
+
+ sun.arch.data.model
+ 32
+
+
+
+ 512m
+ -Xmx768m -XX:MaxPermSize=128m @{argLine}
+
+
+
+
+ 64bitstuff
+
+
+ sun.arch.data.model
+ 64
+
+
+
+ 768m
+ -Xmx1024m -XX:MaxPermSize=256m @{argLine}
+
+
+
+
+ xmlbean
+
+ xmlbeans.marker
+
+
+
+
+ org.codehaus.mojo
+ xmlbeans-maven-plugin
+ 2.3.3
+
+
+ process-sources
+
+ xmlbeans
+
+
+ ${basedir}/target/schemas
+ 1.5
+ true
+ ${xmlbeans.noUpa}
+ ${xmlbeans.noPvr}
+
+ ${basedir}/../../src/ooxml/resources/org/apache/poi/poifs/crypt/encryptionCertificate.xsdconfig
+ ${basedir}/../../src/ooxml/resources/org/apache/poi/poifs/crypt/encryptionInfo.xsdconfig
+ ${basedir}/../../src/ooxml/resources/org/apache/poi/poifs/crypt/encryptionPassword.xsdconfig
+ ${basedir}/../../src/ooxml/resources/org/apache/poi/schemas/ooxmlSchemas.xsdconfig
+
+
+
+
+
+
+
+ maven-antrun-plugin
+ ${maven.plugin.antrun.version}
+
+
+ copy-xmltype-and-xsdconfig
+ generate-sources
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ replace-xmltypeloader
+ process-sources
+ run
+
+
+
+ org.apache.xmlbeans.XmlBeans.getContextTypeLoader()
+ org.apache.poi.POIXMLTypeLoader
+
+
+
+
+
+ remove-xmltypeloader-from-schema-jar
+ prepare-package
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.xmlbeans
+ xmlbeans
+ ${xmlbeans.version}
+
+
+
+
+
diff --git a/trunk/sonar/scratchpad/pom.xml b/trunk/sonar/scratchpad/pom.xml
new file mode 100644
index 000000000..790299cbe
--- /dev/null
+++ b/trunk/sonar/scratchpad/pom.xml
@@ -0,0 +1,109 @@
+
+ 4.0.0
+
+ org.apache.poi
+ poi-parent
+ 3.15-beta3-SNAPSHOT
+
+ poi-scratchpad
+ jar
+
+ Apache POI Scratchpad package
+
+
+
+ src/main/java/org/apache/poi/hwpf/model/types/*,src/main/java/org/apache/poi/hdf/model/hdftypes/definitions/*
+
+
+
+
+
+
+
+ maven-resources-plugin
+ ${maven.plugin.resources.version}
+
+
+ copy-sources
+
+ generate-sources
+
+ copy-resources
+
+
+ ${basedir}/src/main/java
+
+
+ ../../src/scratchpad/src
+
+
+
+
+
+ copy-resources
+
+ generate-resources
+
+ copy-resources
+
+
+ ${basedir}/src/main/resources
+
+
+ ../../src/resources/scratchpad
+
+
+
+
+
+ copy-tests
+
+ generate-test-sources
+
+ copy-resources
+
+
+ ${basedir}/src/test/java
+
+
+ ../../src/scratchpad/testcases
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ ${maven.plugin.clean.version}
+
+
+
+ src
+ false
+
+
+
+
+
+
+
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+
+
+ ${project.groupId}
+ poi-main
+ ${project.version}
+ test-jar
+ test
+
+
+
diff --git a/trunk/src/contrib/poi-ruby/Makefile b/trunk/src/contrib/poi-ruby/Makefile
new file mode 100644
index 000000000..a298da329
--- /dev/null
+++ b/trunk/src/contrib/poi-ruby/Makefile
@@ -0,0 +1,332 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+# Makefile for building Poi4R
+#
+# Supported operating systems: Linux, Mac OS X and Windows.
+# See INSTALL file for requirements.
+#
+# Steps to build
+# 1. Edit the sections below as documented
+# 2. make all
+# 3. make install
+#
+# The install target installs the Poi4R python extension in python's
+# site-packages directory. On Mac OS X, it also installs the gcj runtime
+# libraries into $(PREFIX)/lib.
+#
+# To successfully import the Poi4R extension into Ruby, all required
+# libraries need to be found. If the locations you chose are non-standard,
+# the relevant DYLD_LIBRARY_PATH (Mac OS X), LD_LIBRARY_PATH (Linux), or
+# PATH (Windows) need to be set accordingly.
+#
+
+
+VERSION=0.1.0
+POI_VER=$(shell grep '
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "org/apache/poi/hssf/usermodel/HSSFWorkbook.h"
+#include "org/apache/poi/hssf/usermodel/HSSFSheet.h"
+#include "org/apache/poi/hssf/usermodel/HSSFRow.h"
+#include "org/apache/poi/hssf/usermodel/HSSFCell.h"
+#include "org/apache/poi/hssf/usermodel/HSSFFont.h"
+#include "org/apache/poi/hssf/usermodel/HSSFCellStyle.h"
+#include "org/apache/poi/hssf/usermodel/HSSFDataFormat.h"
+#include "org/apache/poi/hssf/usermodel/HSSFHeader.h"
+#include "org/apache/poi/hssf/usermodel/HSSFFooter.h"
+#include "org/apache/poi/RubyOutputStream.h"
+
+
+typedef ::org::apache::poi::hssf::usermodel::HSSFWorkbook *jhworkbook;
+typedef ::org::apache::poi::hssf::usermodel::HSSFSheet *jhsheet;
+typedef ::org::apache::poi::hssf::usermodel::HSSFRow *jhrow;
+typedef ::org::apache::poi::hssf::usermodel::HSSFCell *jhcell;
+typedef ::org::apache::poi::hssf::usermodel::HSSFCellStyle *jhcellstyle;
+typedef ::org::apache::poi::hssf::usermodel::HSSFFont *jhfont;
+typedef ::org::apache::poi::hssf::usermodel::HSSFFooter *jhfooter;
+typedef ::org::apache::poi::hssf::usermodel::HSSFHeader *jhheader;
+typedef ::org::apache::poi::hssf::usermodel::HSSFDataFormat *jhdataformat;
+
+typedef ::java::util::Date *jdate;
+typedef ::java::util::Calendar *jcalendar;
+typedef ::java::io::OutputStream *joutputstream;
+typedef ::java::io::InputStream *jinputstream;
+typedef ::java::util::Collection *jstringCollection;
+typedef ::java::util::Collection *jtermCollection;
+typedef ::java::util::Locale *jlocale;
+typedef ::java::lang::Comparable *jcomparable;
+typedef JArray *jobjectArray;
+typedef JArray *jstringArray;
+
+
+static java::lang::Thread *nextThread;
+static java::util::HashMap *objects;
+
+
+static void store_reference(jobject object) {
+ java::lang::Integer *ji =new java::lang::Integer(java::lang::System::identityHashCode(object));
+ jobject jo = objects->get(ji);
+ if (!jo) {
+ // printf("put object in hash\n");
+ objects->put(ji,object);
+ }
+}
+static VALUE jo2rv(jobject object, swig_type_info *descriptor)
+{
+ if (object == NULL)
+ {
+ return Qnil;
+ }
+ else
+ {
+ return SWIG_NewPointerObj((void *) object, descriptor, 0);
+ }
+}
+static int cvtptr(VALUE value, void **jo, swig_type_info *info)
+{
+ if (SWIG_ConvertPtr(value, jo, info, 0) == 0)
+ return 0;
+ else
+ {
+ return -1;
+ }
+}
+
+static int rv2jo(VALUE rv, jobject *jo, swig_type_info *descriptor)
+{
+ if (NIL_P(rv))
+ *jo = NULL;
+ else
+ {
+ java::lang::Object *javaObj;
+
+ if (cvtptr(rv, (void **) &javaObj, descriptor) == -1)
+ return 0;
+
+ *jo = javaObj;
+ }
+
+ return 1;
+}
+
+
+static jstring r2j(VALUE object)
+{
+ if (NIL_P(object)){
+ return NULL;
+ }
+ else {
+ char *ps = STR2CSTR(object);
+ jstring js = JvNewStringLatin1(ps);
+
+ if (!js)
+ {
+ rb_raise(rb_eRuntimeError, "ruby str cannot be converted to java: %s",ps);
+ }
+
+ return js;
+ }
+}
+
+VALUE j2r(jstring js)
+{
+ if (!js)
+ {
+ return Qnil;
+ }
+ else
+ {
+ jint len = JvGetStringUTFLength(js);
+ char buf[len + 1];
+
+ JvGetStringUTFRegion(js, 0, len, buf);
+ buf[len] = '\0';
+
+ return rb_str_new2(buf);
+ }
+}
+
+static void free_java_obj(void* arg1) {
+ jobject object =(jobject) arg1;
+ java::lang::Integer *ji =new java::lang::Integer(java::lang::System::identityHashCode(object));
+ jobject jo = objects->get(ji);
+ if (jo) {
+ // printf("removed object from hash\n");
+ objects->remove(ji);
+ }
+}
+
+static void raise_ruby_error(java::lang::Throwable *e) {
+ java::io::StringWriter *buffer = new java::io::StringWriter();
+ java::io::PrintWriter *writer = new java::io::PrintWriter(buffer);
+ e->printStackTrace(writer);
+ writer->close();
+ jstring message = buffer->toString();
+ jint len = JvGetStringUTFLength(message);
+ char buf[len + 1];
+ JvGetStringUTFRegion(message, 0, len, buf);
+ buf[len] = '\0';
+ rb_raise(rb_eRuntimeError, "error calling poi \n %s", buf);
+}
+
+%}
+
+typedef long jint;
+typedef long long jlong;
+typedef char jbyte;
+typedef float jfloat;
+typedef float jdouble;
+typedef int jshort;
+typedef bool jboolean;
+
+%typemap(in) SWIGTYPE * {
+
+ if (!rv2jo($input, (jobject *) &$1, $1_descriptor))
+ rb_raise(rb_eRuntimeError, "Unrecoverable error in SWIG typemapping");
+}
+%typemap(out) SWIGTYPE * {
+
+ $result = jo2rv($1, $1_descriptor);
+}
+
+%typemap(in) org::apache::poi::hssf::usermodel::HSSFWorkbook{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFWorkbook *)))
+ SWIG_fail;
+}
+%typemap(out) org::apache::poi::hssf::usermodel::HSSFWorkbook {
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFWorkbook *));
+}
+
+%typemap(in) jhsheet{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFSheet *)))
+ SWIG_fail;
+}
+%typemap(out) jhsheet {
+
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFSheet *));
+}
+%typemap(in) jhrow{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFRow *)))
+ SWIG_fail;
+}
+%typemap(out) jhrow {
+
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFRow *));
+}
+%typemap(in) jhcell{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFCell *)))
+ SWIG_fail;
+}
+%typemap(out) jhcell {
+
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFCell *));
+}
+%typemap(in) jhfont{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFFont *)))
+ rb_raise(rb_eRuntimeError, "Unrecoverable error in SWIG typemapping of HSSFFont");
+}
+
+%typemap(out) jhfont {
+
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFFont *));
+}
+
+%typemap(in) jhcellstyle{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFCellStyle *)))
+ rb_raise(rb_eRuntimeError, "Unrecoverable error in SWIG typemapping of HSSFCellStyle");
+}
+%typemap(out) jhcellstyle {
+
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFCellStyle *));
+}
+%typemap(in) jhdataformat{
+
+ if (!rv2jo($input, (jobject *) &$1,
+ $descriptor(org::apache::poi::hssf::usermodel::HSSFDataFormat *)))
+ rb_raise(rb_eRuntimeError, "Unrecoverable error in SWIG typemapping of HSSFDataFormat");
+}
+%typemap(out) jhdataformat {
+
+ $result = jo2rv($1, $descriptor(org::apache::poi::hssf::usermodel::HSSFDataFormat *));
+}
+
+
+%typemap(in) jstring {
+ $1 = r2j($input);
+}
+%typemap(out) jstring {
+ $result = j2r($1);
+}
+%typecheck(SWIG_TYPECHECK_STRING) jstring {
+ $1 = ( NIL_P($input) || TYPE($input)==T_STRING );
+}
+
+%typemap(in) joutputstream {
+
+ jlong ptr;
+ if (!rb_respond_to($input, rb_intern("putc"))) rb_raise(rb_eTypeError,"Expected IO");
+ *(VALUE *) &ptr = (VALUE) $input;
+ $1 = new org::apache::poi::RubyOutputStream(ptr);
+}
+%typemap(in) jcalendar {
+ $1 = java::util::Calendar::getInstance();
+ //$1->setTimeInMillis((long long) NUM2DBL(rb_funcall($input,rb_intern("to_i"),0,NULL))*1000.0);
+ $1->set(FIX2INT(rb_funcall($input,rb_intern("year"),0,NULL)),
+ FIX2INT(rb_funcall($input,rb_intern("mon"),0,NULL))-1,
+ FIX2INT(rb_funcall($input,rb_intern("day"),0,NULL)),
+ FIX2INT(rb_funcall($input,rb_intern("hour"),0,NULL)),
+ FIX2INT(rb_funcall($input,rb_intern("min"),0,NULL)),
+ FIX2INT(rb_funcall($input,rb_intern("sec"),0,NULL))
+ );
+}
+
+%typecheck(SWIG_TYPECHECK_POINTER) jcalendar {
+ $1 = rb_respond_to($input, rb_intern("asctime"));
+}
+
+%typemap(out) jdate {
+ jlong t = ((jdate) $1)->getTime();
+ //TODO: separate seconds and microsecs
+ int ts=t/1000;
+ $result=rb_time_new((time_t) ts, 0 );
+}
+
+
+%freefunc org::apache::poi::hssf::usermodel::HSSFWorkbook "free_java_obj";
+
+%exception {
+ try {
+ $action
+ } catch (java::lang::Throwable *e) {
+ raise_ruby_error(e);
+ }
+}
+%exception org::apache::poi::hssf::usermodel::HSSFWorkbook::HSSFWorkbook {
+ try {
+ $action
+ store_reference(result);
+ } catch (java::lang::Throwable *e) {
+ raise_ruby_error(e);
+ }
+}
+
+
+
+
+namespace java {
+ namespace lang {
+ class Object {
+ jstring toString();
+ };
+%nodefault;
+ class System : public Object {
+ public:
+ static jstring getProperty(jstring);
+ static jstring getProperty(jstring, jstring);
+ static void load(jstring);
+ static void loadLibrary(jstring);
+ static void mapLibraryName(jstring);
+ static void runFinalization();
+ static void setProperty(jstring, jstring);
+ };
+%makedefault;
+ }
+ namespace io {
+%nodefault;
+ class InputStream : public ::java::lang::Object {
+ };
+ class OutputStream : public ::java::lang::Object {
+ };
+
+%makedefault;
+ }
+ namespace util {
+ class Date : public ::java::lang::Object {
+ public:
+ Date();
+ Date(jlong);
+ void setTime(jlong);
+ jstring toString();
+ };
+ }
+}
+
+
+namespace org {
+ namespace apache {
+ namespace poi {
+ namespace hssf {
+ namespace usermodel {
+%nodefault;
+ class HSSFWorkbook : public ::java::lang::Object {
+ public:
+ HSSFWorkbook();
+ jstring getSheetName(jint);
+ jint getNumberOfSheets();
+ void setSheetOrder(jstring,jint);
+ void setSheetName(jint,jstring);
+ void setSheetName(jint,jstring,jshort);
+ jint getSheetIndex(jstring);
+ jhsheet createSheet();
+ jhsheet cloneSheet(jint);
+ jhsheet createSheet(jstring);
+ jhsheet getSheetAt(jint);
+ jhsheet getSheet(jstring);
+ void removeSheetAt(jint);
+ jhcellstyle createCellStyle();
+ jhfont createFont();
+ jhdataformat createDataFormat();
+ void write(joutputstream);
+
+ };
+
+ class HSSFSheet : public ::java::lang::Object {
+ public:
+ jhrow createRow(jint);
+ jhrow getRow(jint);
+ jhfooter getFooter();
+ jhheader getHeader();
+ };
+ class HSSFRow : public ::java::lang::Object {
+ public:
+ jhcell createCell(jshort);
+ jhcell getCell(jshort);
+ //jboolean getProtect(); //only in 2.5
+
+ };
+ class HSSFCell : public ::java::lang::Object {
+ public:
+ void setCellValue(jdouble);
+ void setCellValue(jstring);
+ void setCellValue(jboolean);
+ void setCellValue(jcalendar);
+ void setCellFormula(jstring);
+ jstring getStringCellValue();
+ jdouble getNumericCellValue();
+ jdate getDateCellValue();
+ jstring getCellFormula();
+ jboolean getBooleanCellValue();
+ jint getCellType();
+ jshort getEncoding();
+ void setAsActiveCell();
+
+ void setCellStyle(jhcellstyle);
+ void setEncoding(jshort encoding);
+
+ static const jint CELL_TYPE_BLANK;
+ static const jint CELL_TYPE_BOOLEAN;
+ static const jint CELL_TYPE_ERROR;
+ static const jint CELL_TYPE_FORMULA;
+ static const jint CELL_TYPE_NUMERIC;
+ static const jint CELL_TYPE_STRING;
+
+ static const jshort ENCODING_COMPRESSED_UNICODE;
+ static const jshort ENCODING_UTF_16;
+ };
+ class HSSFCellStyle : public ::java::lang::Object {
+ public:
+ static const jshort ALIGN_CENTER;
+ static const jshort ALIGN_CENTER_SELECTION;
+ static const jshort ALIGN_FILL;
+ static const jshort ALIGN_GENERAL;
+ static const jshort ALIGN_JUSTIFY;
+ static const jshort ALIGN_LEFT;
+ static const jshort ALIGN_RIGHT;
+ static const jshort ALT_BARS;
+ static const jshort BIG_SPOTS;
+ static const jshort BORDER_DASH_DOT;
+ static const jshort BORDER_DASH_DOT_DOT;
+ static const jshort BORDER_DASHED;
+ static const jshort BORDER_DOTTED;
+ static const jshort BORDER_DOUBLE;
+ static const jshort BORDER_HAIR;
+ static const jshort BORDER_MEDIUM;
+ static const jshort BORDER_MEDIUM_DASH_DOT;
+ static const jshort BORDER_MEDIUM_DASH_DOT_DOT;
+ static const jshort BORDER_MEDIUM_DASHED;
+ static const jshort BORDER_NONE;
+ static const jshort BORDER_SLANTED_DASH_DOT;
+ static const jshort BORDER_THICK;
+ static const jshort BORDER_THIN;
+ static const jshort BRICKS;
+ static const jshort DIAMONDS;
+ static const jshort FINE_DOTS;
+ static const jshort NO_FILL;
+ static const jshort SOLID_FOREGROUND;
+ static const jshort SPARSE_DOTS;
+ static const jshort SQUARES;
+ static const jshort THICK_BACKWARD_DIAG;
+ static const jshort THICK_FORWARD_DIAG;
+ static const jshort THICK_HORZ_BANDS;
+ static const jshort THICK_VERT_BANDS;
+ static const jshort THIN_BACKWARD_DIAG;
+ static const jshort THIN_FORWARD_DIAG;
+ static const jshort THIN_HORZ_BANDS;
+ static const jshort THIN_VERT_BANDS;
+ static const jshort VERTICAL_BOTTOM;
+ static const jshort VERTICAL_CENTER;
+ static const jshort VERTICAL_JUSTIFY;
+ static const jshort VERTICAL_TOP;
+
+ jshort getAlignment();
+ jshort getBorderBottom();
+ jshort getBorderLeft();
+ jshort getBorderRight();
+ jshort getBorderTop();
+ jshort getBottomBorderColor();
+ jshort getDataFormat();
+ jshort getFillBackgroundColor();
+ jshort getFillForegroundColor();
+ jshort getFillPattern();
+ jshort getFontIndex();
+ jboolean getHidden();
+ jshort getIndention();
+ jshort getIndex();
+ jshort getLeftBorderColor();
+ jboolean getLocked();
+ jshort getRightBorderColor();
+ jshort getRotation();
+ jshort getTopBorderColor();
+ jshort getVerticalAlignment();
+ jboolean getWrapText();
+ void setAlignment(jshort) ;
+ void setBorderBottom(jshort );
+ void setBorderLeft(jshort );
+ void setBorderRight(jshort );
+ void setBorderTop(jshort );
+ void setBottomBorderColor(jshort );
+ void setDataFormat(jshort );
+ void setFillBackgroundColor(jshort );
+ void setFillForegroundColor(jshort );
+ void setFillPattern(jshort );
+ void setFont(jhfont );
+ void setHidden(jboolean );
+ void setIndention(jshort );
+ void setLeftBorderColor(jshort );
+ void setLocked(jboolean );
+ void setRightBorderColor(jshort );
+ void setRotation(jshort );
+ void setTopBorderColor(jshort );
+ void setVerticalAlignment(jshort );
+ void setWrapText(jboolean );
+ };
+ class HSSFDataFormat : public ::java::lang::Object {
+ public:
+ static jstring getBuiltinFormat(jshort);
+ static jshort getBuiltinFormat(jstring);
+ jstring getFormat(jshort);
+ jshort getFormat(jstring);
+ static jint getNumberOfBuiltinBuiltinFormats();
+ //TODO static jlist getBuiltinFormats();
+
+ };
+ class HSSFFont : public ::java::lang::Object {
+ public:
+ static const jshort BOLDWEIGHT_BOLD;
+static const jshort BOLDWEIGHT_NORMAL;
+static const jshort COLOR_NORMAL;
+static const jshort COLOR_RED;
+static const jstring FONT_ARIAL;
+static const jshort SS_NONE;
+static const jshort SS_SUB;
+static const jshort SS_SUPER;
+static const jshort U_DOUBLE;
+static const jshort U_DOUBLE_ACCOUNTING;
+static const jshort U_NONE;
+static const jshort U_SINGLE;
+static const jshort U_SINGLE_ACCOUNTING;
+
+ jshort getBoldweight();
+ jshort getColor();
+ jshort getFontHeight();
+ jshort getFontHeightInPoints();
+ jstring getFontName();
+ jshort getIndex();
+ jboolean getItalic();
+ jboolean getStrikeout();
+ jshort getTypeOffset();
+ jshort getUnderline();
+ void setBoldweight(jshort );
+ void setColor(jshort );
+ void setFontHeight(jshort );
+ void setFontHeightInPoints(jshort );
+ void setFontName(jstring );
+ void setItalic(jboolean );
+ void setStrikeout(jboolean );
+ void setTypeOffset(jshort );
+ void setUnderline(jshort );
+};
+%makedefault;
+ }
+ }
+ }
+ }
+}
+
+
+
+
+
+%init %{
+
+ JvCreateJavaVM(NULL);
+ JvAttachCurrentThread(NULL, NULL);
+
+ nextThread = new java::lang::Thread();
+ objects = new java::util::HashMap();
+
+ java::util::HashMap *props = (java::util::HashMap *)
+ java::lang::System::getProperties();
+ props->put(JvNewStringUTF("inRuby"), objects);
+
+ JvInitClass(&org::apache::poi::hssf::usermodel::HSSFFont::class$);
+ JvInitClass(&org::apache::poi::hssf::usermodel::HSSFCell::class$);
+ JvInitClass(&org::apache::poi::hssf::usermodel::HSSFSheet::class$);
+ JvInitClass(&org::apache::poi::hssf::usermodel::HSSFCellStyle::class$);
+
+%}
+
diff --git a/trunk/src/contrib/poi-ruby/cpp/RubyIO.cpp b/trunk/src/contrib/poi-ruby/cpp/RubyIO.cpp
new file mode 100644
index 000000000..d4a4638cf
--- /dev/null
+++ b/trunk/src/contrib/poi-ruby/cpp/RubyIO.cpp
@@ -0,0 +1,44 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+#include
+
+#include "ruby.h"
+#include "org/apache/poi/RubyOutputStream.h"
+
+
+/**
+ * The native functions declared in org.apache.poi.RubyoutputStream
+ *
+ * @author aviks
+ */
+
+ namespace org {
+ namespace apache {
+ namespace poi {
+
+ void RubyOutputStream::close(void)
+ {
+ rb_funcall3((VALUE ) rubyIO, rb_intern("close"), 0, NULL);
+ }
+
+ void RubyOutputStream::write(jint toWrite)
+ {
+ rb_funcall((VALUE ) rubyIO, rb_intern("putc"),1,INT2FIX(toWrite));
+ }
+ }
+ }
+}
diff --git a/trunk/src/contrib/poi-ruby/java/org/apache/poi/RubyOutputStream.java b/trunk/src/contrib/poi-ruby/java/org/apache/poi/RubyOutputStream.java
new file mode 100644
index 000000000..e23a0a7d3
--- /dev/null
+++ b/trunk/src/contrib/poi-ruby/java/org/apache/poi/RubyOutputStream.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi;
+
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * @author aviks
+ * Wrap a java.io.OutputStream around a Ruby IO object
+ */
+
+public class RubyOutputStream extends OutputStream {
+
+ //pointer to native ruby VALUE
+ protected long rubyIO;
+
+ public RubyOutputStream (long rubyIO)
+ {
+ this.rubyIO = rubyIO;
+// incRef();
+ }
+
+ @Override
+ protected void finalize()
+ throws Throwable
+ {
+// decRef();
+ }
+
+// protected native void incRef();
+// protected native void decRef();
+
+ @Override
+ public native void close()
+ throws IOException;
+
+
+ /* (non-Javadoc)
+ * @see java.io.OutputStream#write(int)
+ */
+ @Override
+ public native void write(int arg0) throws IOException;
+}
+
diff --git a/trunk/src/contrib/poi-ruby/tests/tc_base_tests.rb b/trunk/src/contrib/poi-ruby/tests/tc_base_tests.rb
new file mode 100644
index 000000000..33492c7ab
--- /dev/null
+++ b/trunk/src/contrib/poi-ruby/tests/tc_base_tests.rb
@@ -0,0 +1,100 @@
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ====================================================================
+
+require 'test/unit'
+require 'release/poi4r'
+
+class TC_base_tests < Test::Unit::TestCase
+
+ def setup()
+ end
+
+ def test_get_constant
+ h=Poi4r::HSSFWorkbook.new
+ s=h.createSheet("Sheet1")
+ r=s.createRow(0)
+ c=r.createCell(0)
+ assert_equal(3,Poi4r::HSSFCell.CELL_TYPE_BLANK,"Constant CELL_TYPE_BLANK")
+ end
+
+ def test_base
+ system("rm test.xls")
+ h=Poi4r::HSSFWorkbook.new
+
+ #Test Sheet Creation
+ s=h.createSheet("Sheet1")
+ s=h.createSheet("Sheet2")
+ assert_equal(2,h.getNumberOfSheets(),"Number of sheets is 2")
+
+ #Test setting cell values
+ s=h.getSheetAt(0)
+ r=s.createRow(0)
+ c=r.createCell(0)
+ c.setCellValue(1.5)
+ assert_equal(c.getNumericCellValue(),1.5,"Numeric Cell Value")
+ c=r.createCell(1)
+ c.setCellValue("Ruby")
+ assert_equal(c.getStringCellValue(),"Ruby","String Cell Value")
+ #Test error handling
+ assert_raise (RuntimeError) {c.getNumericCellValue()}
+
+ #Test styles
+ st = h.createCellStyle()
+ c=r.createCell(2)
+ st.setAlignment(Poi4r::HSSFCellStyle.ALIGN_CENTER)
+ c.setCellStyle(st)
+ c.setCellValue("centr'd")
+
+ #Date handling
+ c=r.createCell(3)
+ t1=Time.now
+ c.setCellValue(Time.now)
+ t2= c.getDateCellValue().gmtime
+ assert_equal(t1.year,t2.year,"year")
+ assert_equal(t1.mon,t2.mon,"month")
+ assert_equal(t1.day,t2.day,"day")
+ assert_equal(t1.hour,t2.hour,"hour")
+ assert_equal(t1.min,t2.min,"min")
+ assert_equal(t1.sec,t2.sec,"sec")
+ st=h.createCellStyle();
+ st.setDataFormat(Poi4r::HSSFDataFormat.getBuiltinFormat("m/d/yy h:mm"))
+ c.setCellStyle(st)
+
+ #Fonts
+ c=r.createCell(4)
+ font = h.createFont();
+ font.setFontHeightInPoints(24);
+ font.setFontName("Courier New");
+ font.setItalic(true);
+ font.setStrikeout(true);
+ style = h.createCellStyle();
+ style.setFont(font);
+ c.setCellValue("This is a test of fonts");
+ c.setCellStyle(style);
+
+ #Formulas
+ c=r.createCell(5)
+ c.setCellFormula("A1*2")
+ assert_equal("A1*2",c.getCellFormula,"formula")
+
+ #Test writing
+ h.write(File.new("test.xls","w"))
+ assert_nothing_raised {File.new("test.xls","r")}
+ #h.write(0.1)
+ end
+
+end
diff --git a/trunk/src/contrib/poi-ruby/tests/tc_gc.rb b/trunk/src/contrib/poi-ruby/tests/tc_gc.rb
new file mode 100644
index 000000000..82c3c2b13
--- /dev/null
+++ b/trunk/src/contrib/poi-ruby/tests/tc_gc.rb
@@ -0,0 +1,32 @@
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ====================================================================
+
+require 'test/unit'
+require 'release/poi4r'
+
+class TC_gc < Test::Unit::TestCase
+ def test_premature_collection
+ h=Poi4r::HSSFWorkbook.new
+ h.createSheet("Sheet1");
+ 5000.times do
+ hh=Poi4r::HSSFWorkbook.new
+ GC.start()
+ end
+ assert_equal(1,h.getNumberOfSheets(),"Number of sheets")
+ end
+end
+
diff --git a/trunk/src/contrib/poi-ruby/tests/ts_all.rb b/trunk/src/contrib/poi-ruby/tests/ts_all.rb
new file mode 100644
index 000000000..ef50ad5d6
--- /dev/null
+++ b/trunk/src/contrib/poi-ruby/tests/ts_all.rb
@@ -0,0 +1,20 @@
+# ====================================================================
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ====================================================================
+
+require 'test/unit'
+require 'tests/tc_base_tests'
+require 'tests/tc_gc'
diff --git a/trunk/src/contrib/testcases/dummy.txt b/trunk/src/contrib/testcases/dummy.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/trunk/src/examples/jsp/HSSFExample.jsp b/trunk/src/examples/jsp/HSSFExample.jsp
new file mode 100644
index 000000000..b641846bd
--- /dev/null
+++ b/trunk/src/examples/jsp/HSSFExample.jsp
@@ -0,0 +1,116 @@
+
+<%@page contentType="text/html"
+import="java.io.*,org.apache.poi.poifs.filesystem.POIFSFileSystem,org.apache.poi
+.hssf.record.*,org.apache.poi.hssf.model.*,org.apache.poi.hssf.usermodel.*,org.a
+pache.poi.hssf.util.*" %>
+
+Read Excel file
+
+
+An example of using Jakarta POI's HSSF package to read an excel spreadsheet:
+
+
+
+
+<%
+ String filename = request.getParameter("xls_filename");
+ if (filename != null && !filename.equals("")) {
+%>
+ You chose the file <%= filename %>.
+
It's contents are:
+<%
+ try
+ {
+
+ // create a poi workbook from the excel spreadsheet file
+ POIFSFileSystem fs =
+ new POIFSFileSystem(new FileInputStream(filename));
+ HSSFWorkbook wb = new HSSFWorkbook(fs);
+
+ for (int k = 0; k < wb.getNumberOfSheets(); k++)
+ {
+%>
+
Sheet <%= k %>
+<%
+
+ HSSFSheet sheet = wb.getSheetAt(k);
+ int rows = sheet.getPhysicalNumberOfRows();
+
+ for (int r = 0; r < rows; r++)
+ {
+ HSSFRow row = sheet.getRow(r);
+ if (row != null) {
+ int cells = row.getPhysicalNumberOfCells();
+%>
+ ROW <%=
+row.getRowNum() %>
+<%
+ for (short c = 0; c < cells; c++)
+ {
+ HSSFCell cell = row.getCell(c);
+ if (cell != null) {
+ String value = null;
+
+ switch (cell.getCellType())
+ {
+
+ case HSSFCell.CELL_TYPE_FORMULA :
+ value = "FORMULA ";
+ break;
+
+ case HSSFCell.CELL_TYPE_NUMERIC :
+ value = "NUMERIC value="
+ + cell.getNumericCellValue
+();
+ break;
+
+ case HSSFCell.CELL_TYPE_STRING :
+ value = "STRING value="
+ + cell.getStringCellValue();
+ break;
+
+ default :
+ }
+%>
+ <%= "CELL col="
+
+ + cell.getColumnIndex()
+ + " VALUE=" + value %>
+<%
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+%>
+ Error occurred: <%= e.getMessage() %>
+<%
+ e.printStackTrace();
+ }
+
+ }
+%>
+
+
+
diff --git a/trunk/src/examples/lib/dummy.txt b/trunk/src/examples/lib/dummy.txt
new file mode 100644
index 000000000..ee37f4519
--- /dev/null
+++ b/trunk/src/examples/lib/dummy.txt
@@ -0,0 +1 @@
+Dummy file so this directory is not deleted by CVS. It's required for the build.
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java b/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java
new file mode 100644
index 000000000..b09427c32
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java
@@ -0,0 +1,502 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.hpsf.HPSFRuntimeException;
+import org.apache.poi.hpsf.MarkUnsupportedException;
+import org.apache.poi.hpsf.MutablePropertySet;
+import org.apache.poi.hpsf.NoPropertySetStreamException;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.Util;
+import org.apache.poi.hpsf.WritingNotSupportedException;
+import org.apache.poi.poifs.eventfilesystem.POIFSReader;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.DocumentEntry;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.TempFile;
+
+/**
+ *
This class copies a POI file system to a new file and compares the copy
+ * with the original.
+ *
+ *
Property set streams are copied logically, i.e. the application
+ * establishes a {@link org.apache.poi.hpsf.PropertySet} of an original property
+ * set, creates a {@link org.apache.poi.hpsf.MutablePropertySet} from the
+ * {@link org.apache.poi.hpsf.PropertySet} and writes the
+ * {@link org.apache.poi.hpsf.MutablePropertySet} to the destination POI file
+ * system. - Streams which are no property set streams are copied bit by
+ * bit.
+ *
+ *
The comparison of the POI file systems is done logically. That means that
+ * the two disk files containing the POI file systems do not need to be
+ * exactly identical. However, both POI file systems must contain the same
+ * files, and most of these files must be bitwise identical. Property set
+ * streams, however, are compared logically: they must have the same sections
+ * with the same attributs, and the sections must contain the same properties.
+ * Details like the ordering of the properties do not matter.
+ */
+public class CopyCompare
+{
+ /**
+ *
Runs the example program. The application expects one or two
+ * arguments:
+ *
+ *
+ *
+ *
The first argument is the disk file name of the POI filesystem to
+ * copy.
+ *
+ *
The second argument is optional. If it is given, it is the name of
+ * a disk file the copy of the POI filesystem will be written to. If it is
+ * not given, the copy will be written to a temporary file which will be
+ * deleted at the end of the program.
+ *
+ *
+ *
+ * @param args Command-line arguments.
+ * @exception MarkUnsupportedException if a POI document stream does not
+ * support the mark() operation.
+ * @exception NoPropertySetStreamException if the application tries to
+ * create a property set from a POI document stream that is not a property
+ * set stream.
+ * @exception IOException if any I/O exception occurs.
+ * @exception UnsupportedEncodingException if a character encoding is not
+ * supported.
+ */
+ public static void main(final String[] args)
+ throws NoPropertySetStreamException, MarkUnsupportedException,
+ UnsupportedEncodingException, IOException
+ {
+ String originalFileName = null;
+ String copyFileName = null;
+
+ /* Check the command-line arguments. */
+ if (args.length == 1) {
+ originalFileName = args[0];
+ File f = TempFile.createTempFile("CopyOfPOIFileSystem-", ".ole2");
+ f.deleteOnExit();
+ copyFileName = f.getAbsolutePath();
+ } else if (args.length == 2) {
+ originalFileName = args[0];
+ copyFileName = args[1];
+ } else {
+ System.err.println("Usage: " + CopyCompare.class.getName() +
+ "originPOIFS [copyPOIFS]");
+ System.exit(1);
+ }
+
+ /* Read the origin POIFS using the eventing API. The real work is done
+ * in the class CopyFile which is registered here as a POIFSReader. */
+ final POIFSReader r = new POIFSReader();
+ final CopyFile cf = new CopyFile(copyFileName);
+ r.registerListener(cf);
+ FileInputStream fis = new FileInputStream(originalFileName);
+ r.read(fis);
+ fis.close();
+
+ /* Write the new POIFS to disk. */
+ cf.close();
+
+ /* Read all documents from the original POI file system and compare them
+ * with the equivalent document from the copy. */
+ final POIFSFileSystem opfs = new POIFSFileSystem(new File(originalFileName));
+ final POIFSFileSystem cpfs = new POIFSFileSystem(new File(copyFileName));
+
+ final DirectoryEntry oRoot = opfs.getRoot();
+ final DirectoryEntry cRoot = cpfs.getRoot();
+ final StringBuffer messages = new StringBuffer();
+ if (equal(oRoot, cRoot, messages)) {
+ System.out.println("Equal");
+ } else {
+ System.out.println("Not equal: " + messages.toString());
+ }
+ cpfs.close();
+ opfs.close();
+ }
+
+
+
+ /**
+ *
Compares two {@link DirectoryEntry} instances of a POI file system.
+ * The directories must contain the same streams with the same names and
+ * contents.
+ *
+ * @param d1 The first directory.
+ * @param d2 The second directory.
+ * @param msg The method may append human-readable comparison messages to
+ * this string buffer.
+ * @return true if the directories are equal, else
+ * false.
+ * @exception MarkUnsupportedException if a POI document stream does not
+ * support the mark() operation.
+ * @exception NoPropertySetStreamException if the application tries to
+ * create a property set from a POI document stream that is not a property
+ * set stream.
+ * @throws UnsupportedEncodingException
+ * @exception IOException if any I/O exception occurs.
+ */
+ private static boolean equal(final DirectoryEntry d1,
+ final DirectoryEntry d2,
+ final StringBuffer msg)
+ throws NoPropertySetStreamException, MarkUnsupportedException,
+ UnsupportedEncodingException, IOException
+ {
+ boolean equal = true;
+ /* Iterate over d1 and compare each entry with its counterpart in d2. */
+ for (final Entry e1 : d1) {
+ final String n1 = e1.getName();
+ Entry e2 = null;
+ try {
+ e2 = d2.getEntry(n1);
+ } catch (FileNotFoundException ex) {
+ msg.append("Document \"" + e1 + "\" exists, document \"" +
+ e2 + "\" does not.\n");
+ equal = false;
+ break;
+ }
+
+ if (e1.isDirectoryEntry() && e2.isDirectoryEntry()) {
+ equal = equal((DirectoryEntry) e1, (DirectoryEntry) e2, msg);
+ } else if (e1.isDocumentEntry() && e2.isDocumentEntry()) {
+ equal = equal((DocumentEntry) e1, (DocumentEntry) e2, msg);
+ } else {
+ msg.append("One of \"" + e1 + "\" and \"" + e2 + "\" is a " +
+ "document while the other one is a directory.\n");
+ equal = false;
+ }
+ }
+
+ /* Iterate over d2 just to make sure that there are no entries in d2
+ * that are not in d1. */
+ for (final Entry e2 : d2) {
+ final String n2 = e2.getName();
+ Entry e1 = null;
+ try {
+ e1 = d1.getEntry(n2);
+ } catch (FileNotFoundException ex) {
+ msg.append("Document \"" + e2 + "\" exitsts, document \"" +
+ e1 + "\" does not.\n");
+ equal = false;
+ break;
+ }
+ }
+ return equal;
+ }
+
+
+
+ /**
+ *
Compares two {@link DocumentEntry} instances of a POI file system.
+ * Documents that are not property set streams must be bitwise identical.
+ * Property set streams must be logically equal.
+ *
+ * @param d1 The first document.
+ * @param d2 The second document.
+ * @param msg The method may append human-readable comparison messages to
+ * this string buffer.
+ * @return true if the documents are equal, else
+ * false.
+ * @exception MarkUnsupportedException if a POI document stream does not
+ * support the mark() operation.
+ * @exception NoPropertySetStreamException if the application tries to
+ * create a property set from a POI document stream that is not a property
+ * set stream.
+ * @throws UnsupportedEncodingException
+ * @exception IOException if any I/O exception occurs.
+ */
+ private static boolean equal(final DocumentEntry d1, final DocumentEntry d2,
+ final StringBuffer msg)
+ throws NoPropertySetStreamException, MarkUnsupportedException,
+ UnsupportedEncodingException, IOException
+ {
+ boolean equal = true;
+ final DocumentInputStream dis1 = new DocumentInputStream(d1);
+ final DocumentInputStream dis2 = new DocumentInputStream(d2);
+ try {
+ if (PropertySet.isPropertySetStream(dis1) &&
+ PropertySet.isPropertySetStream(dis2)) {
+ final PropertySet ps1 = PropertySetFactory.create(dis1);
+ final PropertySet ps2 = PropertySetFactory.create(dis2);
+ equal = ps1.equals(ps2);
+ if (!equal) {
+ msg.append("Property sets are not equal.\n");
+ return equal;
+ }
+ } else {
+ int i1;
+ int i2;
+ do {
+ i1 = dis1.read();
+ i2 = dis2.read();
+ if (i1 != i2) {
+ equal = false;
+ msg.append("Documents are not equal.\n");
+ break;
+ }
+ } while (equal && i1 == -1);
+ }
+ } finally {
+ dis2.close();
+ dis1.close();
+ }
+ return true;
+ }
+
+
+
+ /**
+ *
This class does all the work. Its method {@link
+ * #processPOIFSReaderEvent(POIFSReaderEvent)} is called for each file in
+ * the original POI file system. Except for property set streams it copies
+ * everything unmodified to the destination POI filesystem. Property set
+ * streams are copied by creating a new {@link PropertySet} from the
+ * original property set by using the {@link
+ * MutablePropertySet#MutablePropertySet(PropertySet)} constructor.
The constructor of a {@link CopyFile} instance creates the target
+ * POIFS. It also stores the name of the file the POIFS will be written
+ * to once it is complete.
+ *
+ * @param dstName The name of the disk file the destination POIFS is to
+ * be written to.
+ */
+ public CopyFile(final String dstName) {
+ this.dstName = dstName;
+ poiFs = new POIFSFileSystem();
+ }
+
+
+ /**
+ *
The method is called by POI's eventing API for each file in the
+ * origin POIFS.
+ */
+ public void processPOIFSReaderEvent(final POIFSReaderEvent event) {
+ /* The following declarations are shortcuts for accessing the
+ * "event" object. */
+ final POIFSDocumentPath path = event.getPath();
+ final String name = event.getName();
+ final DocumentInputStream stream = event.getStream();
+
+ Throwable t = null;
+
+ try {
+ /* Find out whether the current document is a property set
+ * stream or not. */
+ if (PropertySet.isPropertySetStream(stream)) {
+ /* Yes, the current document is a property set stream.
+ * Let's create a PropertySet instance from it. */
+ PropertySet ps = null;
+ try {
+ ps = PropertySetFactory.create(stream);
+ } catch (NoPropertySetStreamException ex) {
+ /* This exception will not be thrown because we already
+ * checked above. */
+ }
+
+ /* Copy the property set to the destination POI file
+ * system. */
+ copy(poiFs, path, name, ps);
+ } else {
+ /* No, the current document is not a property set stream. We
+ * copy it unmodified to the destination POIFS. */
+ copy(poiFs, event.getPath(), event.getName(), stream);
+ }
+ } catch (MarkUnsupportedException ex) {
+ t = ex;
+ } catch (IOException ex) {
+ t = ex;
+ } catch (WritingNotSupportedException ex) {
+ t = ex;
+ }
+
+ /* According to the definition of the processPOIFSReaderEvent method
+ * we cannot pass checked exceptions to the caller. The following
+ * lines check whether a checked exception occured and throws an
+ * unchecked exception. The message of that exception is that of
+ * the underlying checked exception. */
+ if (t != null) {
+ throw new HPSFRuntimeException
+ ("Could not read file \"" + path + "/" + name +
+ "\". Reason: " + Util.toString(t));
+ }
+ }
+
+
+
+ /**
+ *
Writes a {@link PropertySet} to a POI filesystem.
+ *
+ * @param poiFs The POI filesystem to write to.
+ * @param path The file's path in the POI filesystem.
+ * @param name The file's name in the POI filesystem.
+ * @param ps The property set to write.
+ * @throws WritingNotSupportedException
+ * @throws IOException
+ */
+ public void copy(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path,
+ final String name,
+ final PropertySet ps)
+ throws WritingNotSupportedException, IOException {
+ final DirectoryEntry de = getPath(poiFs, path);
+ final MutablePropertySet mps = new MutablePropertySet(ps);
+ de.createDocument(name, mps.toInputStream());
+ }
+
+
+
+ /**
+ *
Copies the bytes from a {@link DocumentInputStream} to a new
+ * stream in a POI filesystem.
+ *
+ * @param poiFs The POI filesystem to write to.
+ * @param path The source document's path.
+ * @param name The source document's name.
+ * @param stream The stream containing the source document.
+ * @throws IOException
+ */
+ public void copy(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path,
+ final String name,
+ final DocumentInputStream stream)
+ throws IOException {
+ final DirectoryEntry de = getPath(poiFs, path);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int c;
+ while ((c = stream.read()) != -1) {
+ out.write(c);
+ }
+ stream.close();
+ out.close();
+ final InputStream in =
+ new ByteArrayInputStream(out.toByteArray());
+ de.createDocument(name, in);
+ }
+
+
+ /**
+ *
Writes the POI file system to a disk file.
+ *
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void close() throws FileNotFoundException, IOException {
+ out = new FileOutputStream(dstName);
+ poiFs.writeFilesystem(out);
+ out.close();
+ }
+
+
+
+ /** Contains the directory paths that have already been created in the
+ * output POI filesystem and maps them to their corresponding
+ * {@link org.apache.poi.poifs.filesystem.DirectoryNode}s. */
+ private final Map paths = new HashMap();
+
+
+
+ /**
+ *
Ensures that the directory hierarchy for a document in a POI
+ * fileystem is in place. When a document is to be created somewhere in
+ * a POI filesystem its directory must be created first. This method
+ * creates all directories between the POI filesystem root and the
+ * directory the document should belong to which do not yet exist.
+ *
+ *
Unfortunately POI does not offer a simple method to interrogate
+ * the POIFS whether a certain child node (file or directory) exists in
+ * a directory. However, since we always start with an empty POIFS which
+ * contains the root directory only and since each directory in the
+ * POIFS is created by this method we can maintain the POIFS's directory
+ * hierarchy ourselves: The {@link DirectoryEntry} of each directory
+ * created is stored in a {@link Map}. The directories' path names map
+ * to the corresponding {@link DirectoryEntry} instances.
+ *
+ * @param poiFs The POI filesystem the directory hierarchy is created
+ * in, if needed.
+ * @param path The document's path. This method creates those directory
+ * components of this hierarchy which do not yet exist.
+ * @return The directory entry of the document path's parent. The caller
+ * should use this {@link DirectoryEntry} to create documents in it.
+ */
+ public DirectoryEntry getPath(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path) {
+ try {
+ /* Check whether this directory has already been created. */
+ final String s = path.toString();
+ DirectoryEntry de = paths.get(s);
+ if (de != null)
+ /* Yes: return the corresponding DirectoryEntry. */
+ return de;
+
+ /* No: We have to create the directory - or return the root's
+ * DirectoryEntry. */
+ int l = path.length();
+ if (l == 0) {
+ /* Get the root directory. It does not have to be created
+ * since it always exists in a POIFS. */
+ de = poiFs.getRoot();
+ } else {
+ /* Create a subordinate directory. The first step is to
+ * ensure that the parent directory exists: */
+ de = getPath(poiFs, path.getParent());
+ /* Now create the target directory: */
+ de = de.createDirectory(path.getComponent
+ (path.length() - 1));
+ }
+ paths.put(s, de);
+ return de;
+ } catch (IOException ex) {
+ /* This exception will be thrown if the directory already
+ * exists. However, since we have full control about directory
+ * creation we can ensure that this will never happen. */
+ ex.printStackTrace(System.err);
+ throw new RuntimeException(ex.toString());
+ /* FIXME (2): Replace the previous line by the following once we
+ * no longer need JDK 1.3 compatibility. */
+ // throw new RuntimeException(ex);
+ }
+ }
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hpsf/examples/ModifyDocumentSummaryInformation.java b/trunk/src/examples/src/org/apache/poi/hpsf/examples/ModifyDocumentSummaryInformation.java
new file mode 100644
index 000000000..81a9594e7
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hpsf/examples/ModifyDocumentSummaryInformation.java
@@ -0,0 +1,173 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.examples;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Date;
+
+import org.apache.poi.hpsf.CustomProperties;
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.MarkUnsupportedException;
+import org.apache.poi.hpsf.NoPropertySetStreamException;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.UnexpectedPropertySetTypeException;
+import org.apache.poi.hpsf.WritingNotSupportedException;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+
+/**
+ *
This is a sample application showing how to easily modify properties in
+ * the summary information and in the document summary information. The
+ * application reads the name of a POI filesystem from the command line and
+ * performs the following actions:
+ *
+ *
+ *
+ *
Open the POI filesystem.
+ *
+ *
Read the summary information.
+ *
+ *
Read and print the "author" property.
+ *
+ *
Change the author to "Rainer Klute".
+ *
+ *
Read the document summary information.
+ *
+ *
Read and print the "category" property.
+ *
+ *
Change the category to "POI example".
+ *
+ *
Read the custom properties (if available).
+ *
+ *
Insert a new custom property.
+ *
+ *
Write the custom properties back to the document summary
+ * information.
+ *
+ *
Write the summary information to the POI filesystem.
+ *
+ *
Write the document summary information to the POI filesystem.
+ *
+ *
Write the POI filesystem back to the original file.
+ *
+ * @param args The command-line parameters.
+ * @throws IOException
+ * @throws MarkUnsupportedException
+ * @throws NoPropertySetStreamException
+ * @throws UnexpectedPropertySetTypeException
+ * @throws WritingNotSupportedException
+ */
+ public static void main(final String[] args) throws IOException,
+ NoPropertySetStreamException, MarkUnsupportedException,
+ UnexpectedPropertySetTypeException, WritingNotSupportedException
+ {
+ /* Read the name of the POI filesystem to modify from the command line.
+ * For brevity to boundary check is performed on the command-line
+ * arguments. */
+ File summaryFile = new File(args[0]);
+
+ /* Open the POI filesystem. */
+ NPOIFSFileSystem poifs = new NPOIFSFileSystem(summaryFile, false);
+
+ /* Read the summary information. */
+ DirectoryEntry dir = poifs.getRoot();
+ SummaryInformation si;
+ try
+ {
+ si = (SummaryInformation)PropertySetFactory.create(
+ dir, SummaryInformation.DEFAULT_STREAM_NAME);
+ }
+ catch (FileNotFoundException ex)
+ {
+ // There is no summary information yet. We have to create a new one
+ si = PropertySetFactory.newSummaryInformation();
+ }
+
+ /* Change the author to "Rainer Klute". Any former author value will
+ * be lost. If there has been no author yet, it will be created. */
+ si.setAuthor("Rainer Klute");
+ System.out.println("Author changed to " + si.getAuthor() + ".");
+
+
+ /* Handling the document summary information is analogous to handling
+ * the summary information. An additional feature, however, are the
+ * custom properties. */
+
+ /* Read the document summary information. */
+ DocumentSummaryInformation dsi;
+ try
+ {
+ dsi = (DocumentSummaryInformation)PropertySetFactory.create(
+ dir, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+ }
+ catch (FileNotFoundException ex)
+ {
+ /* There is no document summary information yet. We have to create a
+ * new one. */
+ dsi = PropertySetFactory.newDocumentSummaryInformation();
+ }
+
+ /* Change the category to "POI example". Any former category value will
+ * be lost. If there has been no category yet, it will be created. */
+ dsi.setCategory("POI example");
+ System.out.println("Category changed to " + dsi.getCategory() + ".");
+
+ /* Read the custom properties. If there are no custom properties yet,
+ * the application has to create a new CustomProperties object. It will
+ * serve as a container for custom properties. */
+ CustomProperties customProperties = dsi.getCustomProperties();
+ if (customProperties == null)
+ customProperties = new CustomProperties();
+
+ /* Insert some custom properties into the container. */
+ customProperties.put("Key 1", "Value 1");
+ customProperties.put("Schl\u00fcssel 2", "Wert 2");
+ customProperties.put("Sample Number", new Integer(12345));
+ customProperties.put("Sample Boolean", Boolean.TRUE);
+ customProperties.put("Sample Date", new Date());
+
+ /* Read a custom property. */
+ Object value = customProperties.get("Sample Number");
+ System.out.println("Custom Sample Number is now " + value);
+
+ /* Write the custom properties back to the document summary
+ * information. */
+ dsi.setCustomProperties(customProperties);
+
+ /* Write the summary information and the document summary information
+ * to the POI filesystem. */
+ si.write(dir, SummaryInformation.DEFAULT_STREAM_NAME);
+ dsi.write(dir, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+
+ /* Write the POI filesystem back to the original file. Please note that
+ * in production code you should take care when write directly to the
+ * origin, to make sure you don't loose things on error */
+ poifs.writeFilesystem();
+ poifs.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hpsf/examples/ReadCustomPropertySets.java b/trunk/src/examples/src/org/apache/poi/hpsf/examples/ReadCustomPropertySets.java
new file mode 100644
index 000000000..222fff9ff
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hpsf/examples/ReadCustomPropertySets.java
@@ -0,0 +1,134 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.examples;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hpsf.NoPropertySetStreamException;
+import org.apache.poi.hpsf.Property;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.Section;
+import org.apache.poi.poifs.eventfilesystem.POIFSReader;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
+import org.apache.poi.util.HexDump;
+
+/**
+ *
Sample application showing how to read a document's custom property set.
+ * Call it with the document's file name as command-line parameter.
+ *
+ * @param args Command-line arguments (unused).
+ * @throws IOException if any I/O exception occurs.
+ */
+ public static void main(final String[] args)
+ throws IOException
+ {
+ final String filename = args[0];
+ POIFSReader r = new POIFSReader();
+
+ /* Register a listener for *all* documents. */
+ r.registerListener(new MyPOIFSReaderListener());
+ r.read(new FileInputStream(filename));
+ }
+
+
+ static class MyPOIFSReaderListener implements POIFSReaderListener
+ {
+ public void processPOIFSReaderEvent(final POIFSReaderEvent event)
+ {
+ PropertySet ps = null;
+ try
+ {
+ ps = PropertySetFactory.create(event.getStream());
+ }
+ catch (NoPropertySetStreamException ex)
+ {
+ out("No property set stream: \"" + event.getPath() +
+ event.getName() + "\"");
+ return;
+ }
+ catch (Exception ex)
+ {
+ throw new RuntimeException
+ ("Property set stream \"" +
+ event.getPath() + event.getName() + "\": " + ex);
+ }
+
+ /* Print the name of the property set stream: */
+ out("Property set stream \"" + event.getPath() +
+ event.getName() + "\":");
+
+ /* Print the number of sections: */
+ final long sectionCount = ps.getSectionCount();
+ out(" No. of sections: " + sectionCount);
+
+ /* Print the list of sections: */
+ List sections = ps.getSections();
+ int nr = 0;
+ for (Iterator i = sections.iterator(); i.hasNext();)
+ {
+ /* Print a single section: */
+ Section sec = i.next();
+ out(" Section " + nr++ + ":");
+ String s = hex(sec.getFormatID().getBytes());
+ s = s.substring(0, s.length() - 1);
+ out(" Format ID: " + s);
+
+ /* Print the number of properties in this section. */
+ int propertyCount = sec.getPropertyCount();
+ out(" No. of properties: " + propertyCount);
+
+ /* Print the properties: */
+ Property[] properties = sec.getProperties();
+ for (int i2 = 0; i2 < properties.length; i2++)
+ {
+ /* Print a single property: */
+ Property p = properties[i2];
+ long id = p.getID();
+ long type = p.getType();
+ Object value = p.getValue();
+ out(" Property ID: " + id + ", type: " + type +
+ ", value: " + value);
+ }
+ }
+ }
+ }
+
+ static void out(final String msg)
+ {
+ System.out.println(msg);
+ }
+
+ static String hex(final byte[] bytes)
+ {
+ return HexDump.dump(bytes, 0L, 0);
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hpsf/examples/ReadTitle.java b/trunk/src/examples/src/org/apache/poi/hpsf/examples/ReadTitle.java
new file mode 100644
index 000000000..201030ac2
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hpsf/examples/ReadTitle.java
@@ -0,0 +1,79 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.examples;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.poifs.eventfilesystem.POIFSReader;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
+
+/**
+ *
Sample application showing how to read a OLE 2 document's
+ * title. Call it with the document's file name as command line
+ * parameter.
+ *
+ *
Explanations can be found in the HPSF HOW-TO.
+ */
+public class ReadTitle
+{
+ /**
+ *
Runs the example program.
+ *
+ * @param args Command-line arguments. The first command-line argument must
+ * be the name of a POI filesystem to read.
+ * @throws IOException if any I/O exception occurs.
+ */
+ public static void main(final String[] args) throws IOException
+ {
+ final String filename = args[0];
+ POIFSReader r = new POIFSReader();
+ r.registerListener(new MyPOIFSReaderListener(),
+ "\005SummaryInformation");
+ r.read(new FileInputStream(filename));
+ }
+
+
+ static class MyPOIFSReaderListener implements POIFSReaderListener
+ {
+ public void processPOIFSReaderEvent(final POIFSReaderEvent event)
+ {
+ SummaryInformation si = null;
+ try
+ {
+ si = (SummaryInformation)
+ PropertySetFactory.create(event.getStream());
+ }
+ catch (Exception ex)
+ {
+ throw new RuntimeException
+ ("Property set stream \"" +
+ event.getPath() + event.getName() + "\": " + ex);
+ }
+ final String title = si.getTitle();
+ if (title != null)
+ System.out.println("Title: \"" + title + "\"");
+ else
+ System.out.println("Document has no title.");
+ }
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hpsf/examples/WriteAuthorAndTitle.java b/trunk/src/examples/src/org/apache/poi/hpsf/examples/WriteAuthorAndTitle.java
new file mode 100644
index 000000000..996aa9ff7
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hpsf/examples/WriteAuthorAndTitle.java
@@ -0,0 +1,419 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.hpsf.HPSFRuntimeException;
+import org.apache.poi.hpsf.MarkUnsupportedException;
+import org.apache.poi.hpsf.MutablePropertySet;
+import org.apache.poi.hpsf.MutableSection;
+import org.apache.poi.hpsf.NoPropertySetStreamException;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.Util;
+import org.apache.poi.hpsf.Variant;
+import org.apache.poi.hpsf.WritingNotSupportedException;
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.poifs.eventfilesystem.POIFSReader;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+/**
+ *
This class is a sample application which shows how to write or modify the
+ * author and title property of an OLE 2 document. This could be done in two
+ * different ways:
+ *
+ *
+ *
+ *
The first approach is to open the OLE 2 file as a POI filesystem
+ * (see class {@link POIFSFileSystem}), read the summary information property
+ * set (see classes {@link SummaryInformation} and {@link PropertySet}), write
+ * the author and title properties into it and write the property set back into
+ * the POI filesystem.
+ *
+ *
The second approach does not modify the original POI filesystem, but
+ * instead creates a new one. All documents from the original POIFS are copied
+ * to the destination POIFS, except for the summary information stream. The
+ * latter is modified by setting the author and title property before writing
+ * it to the destination POIFS. It there are several summary information streams
+ * in the original POIFS - e.g. in subordinate directories - they are modified
+ * just the same.
+ *
+ *
+ *
+ *
This sample application takes the second approach. It expects the name of
+ * the existing POI filesystem's name as its first command-line parameter and
+ * the name of the output POIFS as the second command-line argument. The
+ * program then works as described above: It copies nearly all documents
+ * unmodified from the input POI filesystem to the output POI filesystem. If it
+ * encounters a summary information stream it reads its properties. Then it sets
+ * the "author" and "title" properties to new values and writes the modified
+ * summary information stream into the output file.
+ *
+ *
Further explanations can be found in the HPSF HOW-TO.
+ */
+public class WriteAuthorAndTitle
+{
+ /**
+ *
Runs the example program.
+ *
+ * @param args Command-line arguments. The first command-line argument must
+ * be the name of a POI filesystem to read.
+ * @throws IOException if any I/O exception occurs.
+ */
+ public static void main(final String[] args) throws IOException
+ {
+ /* Check whether we have exactly two command-line arguments. */
+ if (args.length != 2)
+ {
+ System.err.println("Usage: " + WriteAuthorAndTitle.class.getName() +
+ " originPOIFS destinationPOIFS");
+ System.exit(1);
+ }
+
+ /* Read the names of the origin and destination POI filesystems. */
+ final String srcName = args[0];
+ final String dstName = args[1];
+
+ /* Read the origin POIFS using the eventing API. The real work is done
+ * in the class ModifySICopyTheRest which is registered here as a
+ * POIFSReader. */
+ final POIFSReader r = new POIFSReader();
+ final ModifySICopyTheRest msrl = new ModifySICopyTheRest(dstName);
+ r.registerListener(msrl);
+ FileInputStream fis = new FileInputStream(srcName);
+ r.read(fis);
+ fis.close();
+
+ /* Write the new POIFS to disk. */
+ msrl.close();
+ }
+
+
+
+ /**
+ *
This class does all the work. As its name implies it modifies a
+ * summary information property set and copies everything else unmodified
+ * to the destination POI filesystem. Since an instance of it is registered
+ * as a {@link POIFSReader} its method {@link
+ * #processPOIFSReaderEvent(POIFSReaderEvent)} is called for each document
+ * in the origin POIFS.
The constructor of a {@link ModifySICopyTheRest} instance creates
+ * the target POIFS. It also stores the name of the file the POIFS will
+ * be written to once it is complete.
+ *
+ * @param dstName The name of the disk file the destination POIFS is to
+ * be written to.
+ */
+ public ModifySICopyTheRest(final String dstName)
+ {
+ this.dstName = dstName;
+ poiFs = new POIFSFileSystem();
+ }
+
+
+ /**
+ *
The method is called by POI's eventing API for each file in the
+ * origin POIFS.
+ */
+ public void processPOIFSReaderEvent(final POIFSReaderEvent event)
+ {
+ /* The following declarations are shortcuts for accessing the
+ * "event" object. */
+ final POIFSDocumentPath path = event.getPath();
+ final String name = event.getName();
+ final DocumentInputStream stream = event.getStream();
+
+ Throwable t = null;
+
+ try
+ {
+ /* Find out whether the current document is a property set
+ * stream or not. */
+ if (PropertySet.isPropertySetStream(stream))
+ {
+ /* Yes, the current document is a property set stream.
+ * Let's create a PropertySet instance from it. */
+ PropertySet ps = null;
+ try
+ {
+ ps = PropertySetFactory.create(stream);
+ }
+ catch (NoPropertySetStreamException ex)
+ {
+ /* This exception will not be thrown because we already
+ * checked above. */
+ }
+
+ /* Now we know that we really have a property set. The next
+ * step is to find out whether it is a summary information
+ * or not. */
+ if (ps.isSummaryInformation())
+ /* Yes, it is a summary information. We will modify it
+ * and write the result to the destination POIFS. */
+ editSI(poiFs, path, name, ps);
+ else
+ /* No, it is not a summary information. We don't care
+ * about its internals and copy it unmodified to the
+ * destination POIFS. */
+ copy(poiFs, path, name, ps);
+ }
+ else
+ /* No, the current document is not a property set stream. We
+ * copy it unmodified to the destination POIFS. */
+ copy(poiFs, event.getPath(), event.getName(), stream);
+ }
+ catch (MarkUnsupportedException ex)
+ {
+ t = ex;
+ }
+ catch (IOException ex)
+ {
+ t = ex;
+ }
+ catch (WritingNotSupportedException ex)
+ {
+ t = ex;
+ }
+
+ /* According to the definition of the processPOIFSReaderEvent method
+ * we cannot pass checked exceptions to the caller. The following
+ * lines check whether a checked exception occured and throws an
+ * unchecked exception. The message of that exception is that of
+ * the underlying checked exception. */
+ if (t != null)
+ {
+ throw new HPSFRuntimeException
+ ("Could not read file \"" + path + "/" + name +
+ "\". Reason: " + Util.toString(t));
+ }
+ }
+
+
+ /**
+ *
Receives a summary information property set modifies (or creates)
+ * its "author" and "title" properties and writes the result under the
+ * same path and name as the origin to a destination POI filesystem.
+ *
+ * @param poiFs The POI filesystem to write to.
+ * @param path The original (and destination) stream's path.
+ * @param name The original (and destination) stream's name.
+ * @param si The property set. It should be a summary information
+ * property set.
+ * @throws IOException
+ * @throws WritingNotSupportedException
+ */
+ public void editSI(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path,
+ final String name,
+ final PropertySet si)
+ throws WritingNotSupportedException, IOException
+
+ {
+ /* Get the directory entry for the target stream. */
+ final DirectoryEntry de = getPath(poiFs, path);
+
+ /* Create a mutable property set as a copy of the original read-only
+ * property set. */
+ final MutablePropertySet mps = new MutablePropertySet(si);
+
+ /* Retrieve the section containing the properties to modify. A
+ * summary information property set contains exactly one section. */
+ final MutableSection s =
+ (MutableSection) mps.getSections().get(0);
+
+ /* Set the properties. */
+ s.setProperty(PropertyIDMap.PID_AUTHOR, Variant.VT_LPSTR,
+ "Rainer Klute");
+ s.setProperty(PropertyIDMap.PID_TITLE, Variant.VT_LPWSTR,
+ "Test");
+
+ /* Create an input stream containing the bytes the property set
+ * stream consists of. */
+ final InputStream pss = mps.toInputStream();
+
+ /* Write the property set stream to the POIFS. */
+ de.createDocument(name, pss);
+ }
+
+
+ /**
+ *
Writes a {@link PropertySet} to a POI filesystem. This method is
+ * simpler than {@link #editSI} because the origin property set has just
+ * to be copied.
+ *
+ * @param poiFs The POI filesystem to write to.
+ * @param path The file's path in the POI filesystem.
+ * @param name The file's name in the POI filesystem.
+ * @param ps The property set to write.
+ * @throws WritingNotSupportedException
+ * @throws IOException
+ */
+ public void copy(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path,
+ final String name,
+ final PropertySet ps)
+ throws WritingNotSupportedException, IOException
+ {
+ final DirectoryEntry de = getPath(poiFs, path);
+ final MutablePropertySet mps = new MutablePropertySet(ps);
+ de.createDocument(name, mps.toInputStream());
+ }
+
+
+
+ /**
+ *
Copies the bytes from a {@link DocumentInputStream} to a new
+ * stream in a POI filesystem.
+ *
+ * @param poiFs The POI filesystem to write to.
+ * @param path The source document's path.
+ * @param name The source document's name.
+ * @param stream The stream containing the source document.
+ * @throws IOException
+ */
+ public void copy(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path,
+ final String name,
+ final DocumentInputStream stream) throws IOException
+ {
+ final DirectoryEntry de = getPath(poiFs, path);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int c;
+ while ((c = stream.read()) != -1)
+ out.write(c);
+ stream.close();
+ out.close();
+ final InputStream in =
+ new ByteArrayInputStream(out.toByteArray());
+ de.createDocument(name, in);
+ }
+
+
+ /**
+ *
Writes the POI file system to a disk file.
+ *
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void close() throws FileNotFoundException, IOException
+ {
+ out = new FileOutputStream(dstName);
+ poiFs.writeFilesystem(out);
+ out.close();
+ }
+
+
+
+ /** Contains the directory paths that have already been created in the
+ * output POI filesystem and maps them to their corresponding
+ * {@link org.apache.poi.poifs.filesystem.DirectoryNode}s. */
+ private final Map paths = new HashMap();
+
+
+
+ /**
+ *
Ensures that the directory hierarchy for a document in a POI
+ * fileystem is in place. When a document is to be created somewhere in
+ * a POI filesystem its directory must be created first. This method
+ * creates all directories between the POI filesystem root and the
+ * directory the document should belong to which do not yet exist.
+ *
+ *
Unfortunately POI does not offer a simple method to interrogate
+ * the POIFS whether a certain child node (file or directory) exists in
+ * a directory. However, since we always start with an empty POIFS which
+ * contains the root directory only and since each directory in the
+ * POIFS is created by this method we can maintain the POIFS's directory
+ * hierarchy ourselves: The {@link DirectoryEntry} of each directory
+ * created is stored in a {@link Map}. The directories' path names map
+ * to the corresponding {@link DirectoryEntry} instances.
+ *
+ * @param poiFs The POI filesystem the directory hierarchy is created
+ * in, if needed.
+ * @param path The document's path. This method creates those directory
+ * components of this hierarchy which do not yet exist.
+ * @return The directory entry of the document path's parent. The caller
+ * should use this {@link DirectoryEntry} to create documents in it.
+ */
+ public DirectoryEntry getPath(final POIFSFileSystem poiFs,
+ final POIFSDocumentPath path)
+ {
+ try
+ {
+ /* Check whether this directory has already been created. */
+ final String s = path.toString();
+ DirectoryEntry de = paths.get(s);
+ if (de != null)
+ /* Yes: return the corresponding DirectoryEntry. */
+ return de;
+
+ /* No: We have to create the directory - or return the root's
+ * DirectoryEntry. */
+ int l = path.length();
+ if (l == 0)
+ /* Get the root directory. It does not have to be created
+ * since it always exists in a POIFS. */
+ de = poiFs.getRoot();
+ else
+ {
+ /* Create a subordinate directory. The first step is to
+ * ensure that the parent directory exists: */
+ de = getPath(poiFs, path.getParent());
+ /* Now create the target directory: */
+ de = de.createDirectory(path.getComponent
+ (path.length() - 1));
+ }
+ paths.put(s, de);
+ return de;
+ }
+ catch (IOException ex)
+ {
+ /* This exception will be thrown if the directory already
+ * exists. However, since we have full control about directory
+ * creation we can ensure that this will never happen. */
+ ex.printStackTrace(System.err);
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hpsf/examples/WriteTitle.java b/trunk/src/examples/src/org/apache/poi/hpsf/examples/WriteTitle.java
new file mode 100644
index 000000000..e57ef17b4
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hpsf/examples/WriteTitle.java
@@ -0,0 +1,103 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.hpsf.MutableProperty;
+import org.apache.poi.hpsf.MutablePropertySet;
+import org.apache.poi.hpsf.MutableSection;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.Variant;
+import org.apache.poi.hpsf.WritingNotSupportedException;
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.hpsf.wellknown.SectionIDMap;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+/**
+ *
This class is a simple sample application showing how to create a property
+ * set and write it to disk.
+ */
+public class WriteTitle
+{
+ /**
+ *
Runs the example program.
+ *
+ * @param args Command-line arguments. The first and only command-line
+ * argument is the name of the POI file system to create.
+ * @throws IOException if any I/O exception occurs.
+ * @throws WritingNotSupportedException if HPSF does not (yet) support
+ * writing a certain property type.
+ */
+ public static void main(final String[] args)
+ throws WritingNotSupportedException, IOException
+ {
+ /* Check whether we have exactly one command-line argument. */
+ if (args.length != 1)
+ {
+ System.err.println("Usage: " + WriteTitle.class.getName() +
+ "destinationPOIFS");
+ System.exit(1);
+ }
+
+ final String fileName = args[0];
+
+ /* Create a mutable property set. Initially it contains a single section
+ * with no properties. */
+ final MutablePropertySet mps = new MutablePropertySet();
+
+ /* Retrieve the section the property set already contains. */
+ final MutableSection ms = (MutableSection) mps.getSections().get(0);
+
+ /* Turn the property set into a summary information property. This is
+ * done by setting the format ID of its first section to
+ * SectionIDMap.SUMMARY_INFORMATION_ID. */
+ ms.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
+
+ /* Create an empty property. */
+ final MutableProperty p = new MutableProperty();
+
+ /* Fill the property with appropriate settings so that it specifies the
+ * document's title. */
+ p.setID(PropertyIDMap.PID_TITLE);
+ p.setType(Variant.VT_LPWSTR);
+ p.setValue("Sample title");
+
+ /* Place the property into the section. */
+ ms.setProperty(p);
+
+ /* Create the POI file system the property set is to be written to. */
+ final POIFSFileSystem poiFs = new POIFSFileSystem();
+
+ /* For writing the property set into a POI file system it has to be
+ * handed over to the POIFS.createDocument() method as an input stream
+ * which produces the bytes making out the property set stream. */
+ final InputStream is = mps.toInputStream();
+
+ /* Create the summary information property set in the POI file
+ * system. It is given the default name most (if not all) summary
+ * information property sets have. */
+ poiFs.createDocument(is, SummaryInformation.DEFAULT_STREAM_NAME);
+
+ /* Write the whole POI file system to a disk file. */
+ poiFs.writeFilesystem(new FileOutputStream(fileName));
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/ApacheconEU08.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/ApacheconEU08.java
new file mode 100644
index 000000000..578c3a679
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/ApacheconEU08.java
@@ -0,0 +1,456 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.sl.draw.DrawTableShape;
+import org.apache.poi.sl.draw.SLGraphics;
+import org.apache.poi.sl.usermodel.AutoShape;
+import org.apache.poi.sl.usermodel.GroupShape;
+import org.apache.poi.sl.usermodel.ShapeType;
+import org.apache.poi.sl.usermodel.Slide;
+import org.apache.poi.sl.usermodel.SlideShow;
+import org.apache.poi.sl.usermodel.TableCell;
+import org.apache.poi.sl.usermodel.TableShape;
+import org.apache.poi.sl.usermodel.TextBox;
+import org.apache.poi.sl.usermodel.TextParagraph;
+import org.apache.poi.sl.usermodel.TextRun;
+import org.apache.poi.sl.usermodel.TextShape.TextPlaceholder;
+import org.apache.poi.sl.usermodel.VerticalAlignment;
+
+/**
+ * Presentation for Fast Feather Track on ApacheconEU 2008
+ *
+ * @author Yegor Kozlov
+ */
+public final class ApacheconEU08 {
+
+ public static void main(String[] args) throws IOException {
+ SlideShow,?> ppt = new HSLFSlideShow();
+ // SlideShow,?> ppt = new XMLSlideShow();
+ ppt.setPageSize(new Dimension(720, 540));
+
+ slide1(ppt);
+ slide2(ppt);
+ slide3(ppt);
+ slide4(ppt);
+ slide5(ppt);
+ slide6(ppt);
+ slide7(ppt);
+ slide8(ppt);
+ slide9(ppt);
+ slide10(ppt);
+ slide11(ppt);
+ slide12(ppt);
+
+ String ext = ppt.getClass().getName().contains("HSLF") ? "ppt" : "pptx";
+ FileOutputStream out = new FileOutputStream("apachecon_eu_08."+ext);
+ ppt.write(out);
+ out.close();
+ ppt.close();
+ }
+
+ public static void slide1(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.CENTER_TITLE);
+ box1.setText("POI-HSLF");
+ box1.setAnchor(new Rectangle(54, 78, 612, 115));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.CENTER_BODY);
+ box2.setText("Java API To Access Microsoft PowerPoint Format Files");
+ box2.setAnchor(new Rectangle(108, 204, 504, 138));
+
+ TextBox,?> box3 = slide.createTextBox();
+ box3.getTextParagraphs().get(0).getTextRuns().get(0).setFontSize(32d);
+ box3.setText(
+ "Yegor Kozlov\r" +
+ "yegor - apache - org");
+ box3.setHorizontalCentered(true);
+ box3.setAnchor(new Rectangle(206, 348, 310, 84));
+ }
+
+ public static void slide2(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("What is HSLF?");
+ box1.setAnchor(new Rectangle(36, 21, 648, 90));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.BODY);
+ box2.setText("HorribleSLideshowFormat is the POI Project's pure Java implementation " +
+ "of the Powerpoint binary file format. \r" +
+ "POI sub-project since 2005\r" +
+ "Started by Nick Burch, Yegor Kozlov joined soon after");
+ box2.setAnchor(new Rectangle(36, 126, 648, 356));
+ }
+
+ public static void slide3(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("HSLF in a Nutshell");
+ box1.setAnchor(new Rectangle(36, 15, 648, 65));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.BODY);
+ box2.setText(
+ "HSLF provides a way to read, create and modify MS PowerPoint presentations\r" +
+ "Pure Java API - you don't need PowerPoint to read and write *.ppt files\r" +
+ "Comprehensive support of PowerPoint objects\r" +
+ "Rich text\r" +
+ "Tables\r" +
+ "Shapes\r" +
+ "Pictures\r" +
+ "Master slides\r" +
+ "Access to low level data structures"
+ );
+
+ List extends TextParagraph,?,?>> tp = box2.getTextParagraphs();
+ for (int i : new byte[]{0,1,2,8}) {
+ tp.get(i).getTextRuns().get(0).setFontSize(28d);
+ }
+ for (int i : new byte[]{3,4,5,6,7}) {
+ tp.get(i).getTextRuns().get(0).setFontSize(24d);
+ tp.get(i).setIndentLevel(1);
+ }
+ box2.setAnchor(new Rectangle(36, 80, 648, 400));
+ }
+
+ public static void slide4(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ String[][] txt1 = {
+ {"Note"},
+ {"This presentation was created programmatically using POI HSLF"}
+ };
+ TableShape,?> table1 = slide.createTable(2, 1);
+ for (int i = 0; i < txt1.length; i++) {
+ for (int j = 0; j < txt1[i].length; j++) {
+ TableCell,?> cell = table1.getCell(i, j);
+ cell.setText(txt1[i][j]);
+ TextRun rt = cell.getTextParagraphs().get(0).getTextRuns().get(0);
+ rt.setFontSize(10d);
+ rt.setFontFamily("Arial");
+ rt.setBold(true);
+ if(i == 0){
+ rt.setFontSize(32d);
+ rt.setFontColor(Color.white);
+ cell.setFillColor(new Color(0, 153, 204));
+ } else {
+ rt.setFontSize(28d);
+ cell.setFillColor(new Color(235, 239, 241));
+ }
+ cell.setVerticalAlignment(VerticalAlignment.MIDDLE);
+ }
+ }
+
+ DrawTableShape dts = new DrawTableShape(table1);
+ dts.setAllBorders(1.0, Color.black);
+ dts.setOutsideBorders(4.0);
+
+ table1.setColumnWidth(0, 450);
+ table1.setRowHeight(0, 50);
+ table1.setRowHeight(1, 80);
+
+ Dimension dim = ppt.getPageSize();
+ Rectangle2D oldAnchor = table1.getAnchor();
+ table1.setAnchor(new Rectangle2D.Double((dim.width-450)/2d, 100, oldAnchor.getWidth(), oldAnchor.getHeight()));
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setHorizontalCentered(true);
+ box1.getTextParagraphs().get(0).getTextRuns().get(0).setFontSize(24d);
+ box1.setText("The source code is available at\r" +
+ "http://people.apache.org/~yegor/apachecon_eu08/");
+ box1.setAnchor(new Rectangle(80, 356, 553, 65));
+ }
+
+ public static void slide5(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("HSLF in Action - 1\rData Extraction");
+ box1.setAnchor(new Rectangle(36, 21, 648, 100));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.BODY);
+ box2.setText(
+ "Text from slides and notes\r" +
+ "Images\r" +
+ "Shapes and their properties (type, position in the slide, color, font, etc.)");
+ box2.setAnchor(new Rectangle(36, 150, 648, 300));
+ }
+
+ public static void slide6(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("HSLF in Action - 2");
+ box1.setAnchor(new Rectangle(36, 20, 648, 90));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.getTextParagraphs().get(0).getTextRuns().get(0).setFontSize(18d);
+ box2.setText("Creating a simple presentation from scratch");
+ box2.setAnchor(new Rectangle(170, 100, 364, 30));
+
+ TextBox,?> box3 = slide.createTextBox();
+ TextRun rt3 = box3.getTextParagraphs().get(0).getTextRuns().get(0);
+ rt3.setFontFamily("Courier New");
+ rt3.setFontSize(8d);
+ box3.setText(
+ "SlideShow ppt = new SlideShow();\u000b" +
+ "Slide slide = ppt.createSlide();\u000b" +
+ "\u000b" +
+ "TextBox box2 = new TextBox();\u000b" +
+ "box2.setHorizontalAlignment(TextBox.AlignCenter);\u000b" +
+ "box2.setVerticalAlignment(TextBox.AnchorMiddle);\u000b" +
+ "box2.getTextRun().setText(\"Java Code\");\u000b" +
+ "box2.getFill().setForegroundColor(new Color(187, 224, 227));\u000b" +
+ "box2.setLineColor(Color.black);\u000b" +
+ "box2.setLineWidth(0.75);\u000b" +
+ "box2.setAnchor(new Rectangle(66, 243, 170, 170));\u000b" +
+ "slide.addShape(box2);\u000b" +
+ "\u000b" +
+ "TextBox box3 = new TextBox();\u000b" +
+ "box3.setHorizontalAlignment(TextBox.AlignCenter);\u000b" +
+ "box3.setVerticalAlignment(TextBox.AnchorMiddle);\u000b" +
+ "box3.getTextRun().setText(\"*.ppt file\");\u000b" +
+ "box3.setLineWidth(0.75);\u000b" +
+ "box3.setLineColor(Color.black);\u000b" +
+ "box3.getFill().setForegroundColor(new Color(187, 224, 227));\u000b" +
+ "box3.setAnchor(new Rectangle(473, 243, 170, 170));\u000b" +
+ "slide.addShape(box3);\u000b" +
+ "\u000b" +
+ "AutoShape box4 = new AutoShape(ShapeTypes.Arrow);\u000b" +
+ "box4.getFill().setForegroundColor(new Color(187, 224, 227));\u000b" +
+ "box4.setLineWidth(0.75);\u000b" +
+ "box4.setLineColor(Color.black);\u000b" +
+ "box4.setAnchor(new Rectangle(253, 288, 198, 85));\u000b" +
+ "slide.addShape(box4);\u000b" +
+ "\u000b" +
+ "FileOutputStream out = new FileOutputStream(\"hslf-demo.ppt\");\u000b" +
+ "ppt.write(out);\u000b" +
+ "out.close();");
+ box3.setAnchor(new Rectangle(30, 150, 618, 411));
+ box3.setHorizontalCentered(true);
+ }
+
+ public static void slide7(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setHorizontalCentered(true);
+ box2.setVerticalAlignment(VerticalAlignment.MIDDLE);
+ box2.setText("Java Code");
+ box2.setFillColor(new Color(187, 224, 227));
+ box2.setStrokeStyle(0.75, Color.black);
+ box2.setAnchor(new Rectangle(66, 243, 170, 170));
+
+ TextBox,?> box3 = slide.createTextBox();
+ box3.setHorizontalCentered(true);
+ box3.setVerticalAlignment(VerticalAlignment.MIDDLE);
+ box3.setText("*.ppt file");
+ box3.setFillColor(new Color(187, 224, 227));
+ box3.setStrokeStyle(0.75, Color.black);
+ box3.setAnchor(new Rectangle(473, 243, 170, 170));
+
+ AutoShape,?> box4 = slide.createAutoShape();
+ box4.setShapeType(ShapeType.RIGHT_ARROW);
+ box4.setFillColor(new Color(187, 224, 227));
+ box4.setStrokeStyle(0.75, Color.black);
+ box4.setAnchor(new Rectangle(253, 288, 198, 85));
+ }
+
+ public static void slide8(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("Wait, there is more!");
+ box1.setAnchor(new Rectangle(36, 21, 648, 90));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.BODY);
+ box2.setText(
+ "Rich text\r" +
+ "Tables\r" +
+ "Pictures (JPEG, PNG, BMP, WMF, PICT)\r" +
+ "Comprehensive formatting features");
+ box2.setAnchor(new Rectangle(36, 126, 648, 356));
+ }
+
+ public static void slide9(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("HSLF in Action - 3");
+ box1.setAnchor(new Rectangle(36, 20, 648, 50));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.getTextParagraphs().get(0).getTextRuns().get(0).setFontSize(18d);
+ box2.setText("PPGraphics2D: PowerPoint Graphics2D driver");
+ box2.setAnchor(new Rectangle(178, 70, 387, 30));
+
+ TextBox,?> box3 = slide.createTextBox();
+ TextRun rt3 = box3.getTextParagraphs().get(0).getTextRuns().get(0);
+ rt3.setFontFamily("Courier New");
+ rt3.setFontSize(8d);
+ box3.setText(
+ "//bar chart data. The first value is the bar color, the second is the width\u000b" +
+ "Object[] def = new Object[]{\u000b" +
+ " Color.yellow, new Integer(100),\u000b" +
+ " Color.green, new Integer(150),\u000b" +
+ " Color.gray, new Integer(75),\u000b" +
+ " Color.red, new Integer(200),\u000b" +
+ "};\u000b" +
+ "\u000b" +
+ "SlideShow ppt = new SlideShow();\u000b" +
+ "Slide slide = ppt.createSlide();\u000b" +
+ "\u000b" +
+ "ShapeGroup group = new ShapeGroup();\u000b" +
+ "//define position of the drawing in the slide\u000b" +
+ "Rectangle bounds = new java.awt.Rectangle(200, 100, 350, 300);\u000b" +
+ "group.setAnchor(bounds);\u000b" +
+ "slide.addShape(group);\u000b" +
+ "Graphics2D graphics = new PPGraphics2D(group);\u000b" +
+ "\u000b" +
+ "//draw a simple bar graph\u000b" +
+ "int x = bounds.x + 50, y = bounds.y + 50;\u000b" +
+ "graphics.setFont(new Font(\"Arial\", Font.BOLD, 10));\u000b" +
+ "for (int i = 0, idx = 1; i < def.length; i+=2, idx++) {\u000b" +
+ " graphics.setColor(Color.black);\u000b" +
+ " int width = ((Integer)def[i+1]).intValue();\u000b" +
+ " graphics.drawString(\"Q\" + idx, x-20, y+20);\u000b" +
+ " graphics.drawString(width + \"%\", x + width + 10, y + 20);\u000b" +
+ " graphics.setColor((Color)def[i]);\u000b" +
+ " graphics.fill(new Rectangle(x, y, width, 30));\u000b" +
+ " y += 40;\u000b" +
+ "}\u000b" +
+ "graphics.setColor(Color.black);\u000b" +
+ "graphics.setFont(new Font(\"Arial\", Font.BOLD, 14));\u000b" +
+ "graphics.draw(bounds);\u000b" +
+ "graphics.drawString(\"Performance\", x + 70, y + 40);\u000b" +
+ "\u000b" +
+ "FileOutputStream out = new FileOutputStream(\"hslf-demo.ppt\");\u000b" +
+ "ppt.write(out);\u000b" +
+ "out.close();");
+ box3.setAnchor(new Rectangle(96, 110, 499, 378));
+ box3.setHorizontalCentered(true);
+ }
+
+ public static void slide10(SlideShow,?> ppt) throws IOException {
+ //bar chart data. The first value is the bar color, the second is the width
+ Object[] def = new Object[]{
+ Color.yellow, 100,
+ Color.green, 150,
+ Color.gray, 75,
+ Color.red, 200,
+ };
+
+ Slide,?> slide = ppt.createSlide();
+
+ GroupShape,?> group = slide.createGroup();
+ //define position of the drawing in the slide
+ Rectangle bounds = new java.awt.Rectangle(200, 100, 350, 300);
+ group.setAnchor(bounds);
+ Graphics2D graphics = new SLGraphics(group);
+
+ //draw a simple bar graph
+ int x = bounds.x + 50, y = bounds.y + 50;
+ graphics.setFont(new Font("Arial", Font.BOLD, 10));
+ for (int i = 0, idx = 1; i < def.length; i+=2, idx++) {
+ graphics.setColor(Color.black);
+ int width = ((Integer)def[i+1]).intValue();
+ graphics.drawString("Q" + idx, x-20, y+20);
+ graphics.drawString(width + "%", x + width + 10, y + 20);
+ graphics.setColor((Color)def[i]);
+ graphics.fill(new Rectangle(x, y, width, 30));
+ y += 40;
+ }
+ graphics.setColor(Color.black);
+ graphics.setFont(new Font("Arial", Font.BOLD, 14));
+ graphics.draw(bounds);
+ graphics.drawString("Performance", x + 70, y + 40);
+
+ }
+
+ public static void slide11(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.TITLE);
+ box1.setText("HSLF Development Plans");
+ box1.setAnchor(new Rectangle(36, 21, 648, 90));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.BODY);
+ box2.setText(
+ "Support for more PowerPoint functionality\r" +
+ "Rendering slides into java.awt.Graphics2D\r" +
+ "A way to export slides into images or other formats\r" +
+ "Integration with Apache FOP - Formatting Objects Processor\r" +
+ "Transformation of XSL-FO into PPT\r" +
+ "PPT2PDF transcoder"
+ );
+
+ List extends TextParagraph,?,?>> tp = box2.getTextParagraphs();
+ for (int i : new byte[]{0,1,3}) {
+ tp.get(i).getTextRuns().get(0).setFontSize(28d);
+ }
+ for (int i : new byte[]{2,4,5}) {
+ tp.get(i).getTextRuns().get(0).setFontSize(24d);
+ tp.get(i).setIndentLevel(1);
+ }
+
+ box2.setAnchor(new Rectangle(36, 126, 648, 400));
+ }
+
+ public static void slide12(SlideShow,?> ppt) throws IOException {
+ Slide,?> slide = ppt.createSlide();
+
+ TextBox,?> box1 = slide.createTextBox();
+ box1.setTextPlaceholder(TextPlaceholder.CENTER_TITLE);
+ box1.setText("Questions?");
+ box1.setAnchor(new Rectangle(54, 167, 612, 115));
+
+ TextBox,?> box2 = slide.createTextBox();
+ box2.setTextPlaceholder(TextPlaceholder.CENTER_BODY);
+ box2.setText(
+ "http://poi.apache.org/hslf/\r" +
+ "http://people.apache.org/~yegor");
+ box2.setAnchor(new Rectangle(108, 306, 504, 138));
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java
new file mode 100644
index 000000000..3c706e3e7
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/BulletsDemo.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFTextBox;
+import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
+
+/**
+ * How to create a single-level bulleted list
+ * and change some of the bullet attributes
+ *
+ * @author Yegor Kozlov
+ */
+public final class BulletsDemo {
+
+ public static void main(String[] args) throws Exception {
+
+ HSLFSlideShow ppt = new HSLFSlideShow();
+
+ HSLFSlide slide = ppt.createSlide();
+
+ HSLFTextBox shape = new HSLFTextBox();
+ HSLFTextParagraph rt = shape.getTextParagraphs().get(0);
+ rt.getTextRuns().get(0).setFontSize(42d);
+ rt.setBullet(true);
+ rt.setIndent(0d); //bullet offset
+ rt.setLeftMargin(50d); //text offset (should be greater than bullet offset)
+ rt.setBulletChar('\u263A'); //bullet character
+ shape.setText(
+ "January\r" +
+ "February\r" +
+ "March\r" +
+ "April");
+ slide.addShape(shape);
+
+ shape.setAnchor(new java.awt.Rectangle(50, 50, 500, 300)); //position of the text box in the slide
+ slide.addShape(shape);
+
+ FileOutputStream out = new FileOutputStream("bullets.ppt");
+ ppt.write(out);
+ out.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java
new file mode 100644
index 000000000..fc25afae5
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/CreateHyperlink.java
@@ -0,0 +1,64 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hslf.usermodel.HSLFHyperlink;
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFTextBox;
+
+/**
+ * Demonstrates how to create hyperlinks in PowerPoint presentations
+ */
+public abstract class CreateHyperlink {
+
+ public static void main(String[] args) throws IOException {
+ HSLFSlideShow ppt = new HSLFSlideShow();
+
+ HSLFSlide slideA = ppt.createSlide();
+ ppt.createSlide();
+ HSLFSlide slideC = ppt.createSlide();
+
+ // link to a URL
+ HSLFTextBox textBox1 = slideA.createTextBox();
+ textBox1.setText("Apache POI");
+ textBox1.setAnchor(new Rectangle(100, 100, 200, 50));
+
+ HSLFHyperlink link1 = textBox1.getTextParagraphs().get(0).getTextRuns().get(0).createHyperlink();
+ link1.linkToUrl("http://www.apache.org");
+ link1.setLabel(textBox1.getText());
+
+ // link to another slide
+ HSLFTextBox textBox2 = slideA.createTextBox();
+ textBox2.setText("Go to slide #3");
+ textBox2.setAnchor(new Rectangle(100, 300, 200, 50));
+
+ HSLFHyperlink link2 = textBox2.getTextParagraphs().get(0).getTextRuns().get(0).createHyperlink();
+ link2.linkToSlide(slideC);
+
+ FileOutputStream out = new FileOutputStream("hyperlink.ppt");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/DataExtraction.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/DataExtraction.java
new file mode 100644
index 000000000..514c01ba0
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/DataExtraction.java
@@ -0,0 +1,127 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hslf.model.OLEShape;
+import org.apache.poi.hslf.usermodel.HSLFObjectData;
+import org.apache.poi.hslf.usermodel.HSLFPictureData;
+import org.apache.poi.hslf.usermodel.HSLFPictureShape;
+import org.apache.poi.hslf.usermodel.HSLFShape;
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFSoundData;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hwpf.HWPFDocument;
+import org.apache.poi.hwpf.usermodel.Paragraph;
+import org.apache.poi.hwpf.usermodel.Range;
+
+/**
+ * Demonstrates how you can extract misc embedded data from a ppt file
+ *
+ * @author Yegor Kozlov
+ */
+public final class DataExtraction {
+
+ public static void main(String args[]) throws Exception {
+
+ if (args.length == 0) {
+ usage();
+ return;
+ }
+
+ FileInputStream is = new FileInputStream(args[0]);
+ HSLFSlideShow ppt = new HSLFSlideShow(is);
+ is.close();
+
+ //extract all sound files embedded in this presentation
+ HSLFSoundData[] sound = ppt.getSoundData();
+ for (int i = 0; i < sound.length; i++) {
+ String type = sound[i].getSoundType(); //*.wav
+ String name = sound[i].getSoundName(); //typically file name
+ byte[] data = sound[i].getData(); //raw bytes
+
+ //save the sound on disk
+ FileOutputStream out = new FileOutputStream(name + type);
+ out.write(data);
+ out.close();
+ }
+
+ int oleIdx=-1, picIdx=-1;
+ for (HSLFSlide slide : ppt.getSlides()) {
+ //extract embedded OLE documents
+ for (HSLFShape shape : slide.getShapes()) {
+ if (shape instanceof OLEShape) {
+ oleIdx++;
+ OLEShape ole = (OLEShape) shape;
+ HSLFObjectData data = ole.getObjectData();
+ String name = ole.getInstanceName();
+ if ("Worksheet".equals(name)) {
+
+ //read xls
+ @SuppressWarnings({ "unused", "resource" })
+ HSSFWorkbook wb = new HSSFWorkbook(data.getData());
+
+ } else if ("Document".equals(name)) {
+ HWPFDocument doc = new HWPFDocument(data.getData());
+ //read the word document
+ Range r = doc.getRange();
+ for(int k = 0; k < r.numParagraphs(); k++) {
+ Paragraph p = r.getParagraph(k);
+ System.out.println(p.text());
+ }
+
+ //save on disk
+ FileOutputStream out = new FileOutputStream(name + "-("+(oleIdx)+").doc");
+ doc.write(out);
+ out.close();
+ } else {
+ FileOutputStream out = new FileOutputStream(ole.getProgID() + "-"+(oleIdx+1)+".dat");
+ InputStream dis = data.getData();
+ byte[] chunk = new byte[2048];
+ int count;
+ while ((count = dis.read(chunk)) >= 0) {
+ out.write(chunk,0,count);
+ }
+ is.close();
+ out.close();
+ }
+ }
+
+ //Pictures
+ else if (shape instanceof HSLFPictureShape) {
+ picIdx++;
+ HSLFPictureShape p = (HSLFPictureShape) shape;
+ HSLFPictureData data = p.getPictureData();
+ String ext = data.getType().extension;
+ FileOutputStream out = new FileOutputStream("pict-" + picIdx + ext);
+ out.write(data.getData());
+ out.close();
+ }
+ }
+
+ }
+ }
+
+ private static void usage(){
+ System.out.println("Usage: DataExtraction ppt");
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/Graphics2DDemo.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/Graphics2DDemo.java
new file mode 100644
index 000000000..53cd53edc
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/Graphics2DDemo.java
@@ -0,0 +1,85 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+
+import org.apache.poi.hslf.model.PPGraphics2D;
+import org.apache.poi.hslf.usermodel.HSLFGroupShape;
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+
+/**
+ * Demonstrates how to draw into a slide using the HSLF Graphics2D driver.
+ *
+ * @author Yegor Kozlov
+ */
+public final class Graphics2DDemo {
+
+ /**
+ * A simple bar chart demo
+ */
+ public static void main(String[] args) throws Exception {
+ HSLFSlideShow ppt = new HSLFSlideShow();
+
+ //bar chart data. The first value is the bar color, the second is the width
+ Object[] def = new Object[]{
+ Color.yellow, 40,
+ Color.green, 60,
+ Color.gray, 30,
+ Color.red, 80,
+ };
+
+ HSLFSlide slide = ppt.createSlide();
+
+ HSLFGroupShape group = new HSLFGroupShape();
+ //define position of the drawing in the slide
+ Rectangle bounds = new java.awt.Rectangle(200, 100, 350, 300);
+ group.setAnchor(bounds);
+ group.setInteriorAnchor(new java.awt.Rectangle(0, 0, 100, 100));
+ slide.addShape(group);
+ Graphics2D graphics = new PPGraphics2D(group);
+
+ //draw a simple bar graph
+ int x = 10, y = 10;
+ graphics.setFont(new Font("Arial", Font.BOLD, 10));
+ for (int i = 0, idx = 1; i < def.length; i+=2, idx++) {
+ graphics.setColor(Color.black);
+ int width = ((Integer)def[i+1]).intValue();
+ graphics.drawString("Q" + idx, x-5, y+10);
+ graphics.drawString(width + "%", x + width+3, y + 10);
+ graphics.setColor((Color)def[i]);
+ graphics.fill(new Rectangle(x, y, width, 10));
+ y += 15;
+ }
+ graphics.setColor(Color.black);
+ graphics.setFont(new Font("Arial", Font.BOLD, 14));
+ graphics.draw(group.getInteriorAnchor());
+ graphics.drawString("Performance", x + 30, y + 10);
+
+ FileOutputStream out = new FileOutputStream("hslf-graphics.ppt");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java
new file mode 100644
index 000000000..b37aed2df
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/HeadersFootersDemo.java
@@ -0,0 +1,49 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hslf.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hslf.model.HeadersFooters;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+
+/**
+ * Demonstrates how to set headers / footers
+ */
+public abstract class HeadersFootersDemo {
+ public static void main(String[] args) throws IOException {
+ HSLFSlideShow ppt = new HSLFSlideShow();
+
+ HeadersFooters slideHeaders = ppt.getSlideHeadersFooters();
+ slideHeaders.setFootersText("Created by POI-HSLF");
+ slideHeaders.setSlideNumberVisible(true);
+ slideHeaders.setDateTimeText("custom date time");
+
+ HeadersFooters notesHeaders = ppt.getNotesHeadersFooters();
+ notesHeaders.setFootersText("My notes footers");
+ notesHeaders.setHeaderText("My notes header");
+
+ ppt.createSlide();
+
+ FileOutputStream out = new FileOutputStream("headers_footers.ppt");
+ ppt.write(out);
+ out.close();
+
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java
new file mode 100644
index 000000000..812429295
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/Hyperlinks.java
@@ -0,0 +1,83 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.io.FileInputStream;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.poi.hslf.usermodel.HSLFHyperlink;
+import org.apache.poi.hslf.usermodel.HSLFShape;
+import org.apache.poi.hslf.usermodel.HSLFSimpleShape;
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFTextParagraph;
+import org.apache.poi.hslf.usermodel.HSLFTextRun;
+
+/**
+ * Demonstrates how to read hyperlinks from a presentation
+ *
+ * @author Yegor Kozlov
+ */
+public final class Hyperlinks {
+
+ public static void main(String[] args) throws Exception {
+ for (int i = 0; i < args.length; i++) {
+ FileInputStream is = new FileInputStream(args[i]);
+ HSLFSlideShow ppt = new HSLFSlideShow(is);
+ is.close();
+
+ for (HSLFSlide slide : ppt.getSlides()) {
+ System.out.println("\nslide " + slide.getSlideNumber());
+
+ // read hyperlinks from the slide's text runs
+ System.out.println("- reading hyperlinks from the text runs");
+ for (List paras : slide.getTextParagraphs()) {
+ for (HSLFTextParagraph para : paras) {
+ for (HSLFTextRun run : para) {
+ HSLFHyperlink link = run.getHyperlink();
+ if (link != null) {
+ System.out.println(toStr(link, run.getRawText()));
+ }
+ }
+ }
+ }
+
+ // in PowerPoint you can assign a hyperlink to a shape without text,
+ // for example to a Line object. The code below demonstrates how to
+ // read such hyperlinks
+ System.out.println("- reading hyperlinks from the slide's shapes");
+ for (HSLFShape sh : slide.getShapes()) {
+ if (sh instanceof HSLFSimpleShape) {
+ HSLFHyperlink link = ((HSLFSimpleShape)sh).getHyperlink();
+ if (link != null) {
+ System.out.println(toStr(link, null));
+ }
+ }
+ }
+ }
+ ppt.close();
+ }
+ }
+
+ static String toStr(HSLFHyperlink link, String rawText) {
+ //in ppt end index is inclusive
+ String formatStr = "title: %1$s, address: %2$s" + (rawText == null ? "" : ", start: %3$s, end: %4$s, substring: %5$s");
+ return String.format(Locale.ROOT, formatStr, link.getLabel(), link.getAddress(), link.getStartIndex(), link.getEndIndex(), rawText);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java
new file mode 100644
index 000000000..d8fe8f41f
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/PPT2PNG.java
@@ -0,0 +1,106 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+
+/**
+ * Demonstrates how you can use HSLF to convert each slide into a PNG image
+ *
+ * @author Yegor Kozlov
+ */
+public final class PPT2PNG {
+
+ public static void main(String args[]) throws Exception {
+
+ if (args.length == 0) {
+ usage();
+ return;
+ }
+
+ int slidenum = -1;
+ float scale = 1;
+ String file = null;
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].startsWith("-")) {
+ if ("-scale".equals(args[i])){
+ scale = Float.parseFloat(args[++i]);
+ } else if ("-slide".equals(args[i])) {
+ slidenum = Integer.parseInt(args[++i]);
+ }
+ } else {
+ file = args[i];
+ }
+ }
+ if(file == null){
+ usage();
+ return;
+ }
+
+ FileInputStream is = new FileInputStream(file);
+ HSLFSlideShow ppt = new HSLFSlideShow(is);
+ is.close();
+
+ Dimension pgsize = ppt.getPageSize();
+ int width = (int)(pgsize.width*scale);
+ int height = (int)(pgsize.height*scale);
+
+ for (HSLFSlide slide : ppt.getSlides()) {
+ if (slidenum != -1 && slidenum != slide.getSlideNumber()) continue;
+
+ String title = slide.getTitle();
+ System.out.println("Rendering slide "+slide.getSlideNumber() + (title == null ? "" : ": " + title));
+
+ BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics = img.createGraphics();
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+ graphics.setPaint(Color.white);
+ graphics.fill(new Rectangle2D.Float(0, 0, width, height));
+
+ graphics.scale((double)width/pgsize.width, (double)height/pgsize.height);
+
+ slide.draw(graphics);
+
+ String fname = file.replaceAll("\\.ppt", "-" + slide.getSlideNumber() + ".png");
+ FileOutputStream out = new FileOutputStream(fname);
+ ImageIO.write(img, "png", out);
+ out.close();
+ }
+ }
+
+ private static void usage(){
+ System.out.println("Usage: PPT2PNG [-scale -slide ] ppt");
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/SoundFinder.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/SoundFinder.java
new file mode 100644
index 000000000..ec0cec8dc
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/SoundFinder.java
@@ -0,0 +1,66 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hslf.examples;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.poi.hslf.record.InteractiveInfoAtom;
+import org.apache.poi.hslf.record.RecordTypes;
+import org.apache.poi.hslf.usermodel.HSLFShape;
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFSoundData;
+
+/**
+ * For each slide iterate over shapes and found associated sound data.
+ */
+public class SoundFinder {
+ public static void main(String[] args) throws IOException {
+ FileInputStream fis = new FileInputStream(args[0]);
+ HSLFSlideShow ppt = new HSLFSlideShow(fis);
+ HSLFSoundData[] sounds = ppt.getSoundData();
+
+ for (HSLFSlide slide : ppt.getSlides()) {
+ for (HSLFShape shape : slide.getShapes()) {
+ int soundRef = getSoundReference(shape);
+ if(soundRef == -1) continue;
+
+
+ System.out.println("Slide["+slide.getSlideNumber()+"], shape["+shape.getShapeId()+"], soundRef: "+soundRef);
+ System.out.println(" " + sounds[soundRef].getSoundName());
+ System.out.println(" " + sounds[soundRef].getSoundType());
+ }
+ }
+ ppt.close();
+ fis.close();
+ }
+
+ /**
+ * Check if a given shape is associated with a sound.
+ * @return 0-based reference to a sound in the sound collection
+ * or -1 if the shape is not associated with a sound
+ */
+ protected static int getSoundReference(HSLFShape shape){
+ int soundRef = -1;
+ //dive into the shape container and search for InteractiveInfoAtom
+ InteractiveInfoAtom info = shape.getClientDataRecord(RecordTypes.InteractiveInfo.typeID);
+ if (info != null && info.getAction() == InteractiveInfoAtom.ACTION_MEDIA) {
+ soundRef = info.getSoundRef();
+ }
+ return soundRef;
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hslf/examples/TableDemo.java b/trunk/src/examples/src/org/apache/poi/hslf/examples/TableDemo.java
new file mode 100644
index 000000000..e4ad98dc1
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hslf/examples/TableDemo.java
@@ -0,0 +1,130 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hslf.examples;
+
+import java.awt.Color;
+import java.io.FileOutputStream;
+
+import org.apache.poi.hslf.usermodel.HSLFSlide;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFTable;
+import org.apache.poi.hslf.usermodel.HSLFTableCell;
+import org.apache.poi.hslf.usermodel.HSLFTextRun;
+import org.apache.poi.sl.draw.DrawTableShape;
+import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;
+import org.apache.poi.sl.usermodel.VerticalAlignment;
+
+/**
+ * Demonstrates how to create tables
+ *
+ * @author Yegor Kozlov
+ */
+public final class TableDemo {
+
+ public static void main(String[] args) throws Exception {
+
+ //test data for the first taable
+ String[][] txt1 = {
+ {"INPUT FILE", "NUMBER OF RECORDS"},
+ {"Item File", "11,559"},
+ {"Vendor File", "502"},
+ {"Purchase History File - # of PO\u2019s\r(12/01/04 - 05/31/06)", "12,852"},
+ {"Purchase History File - # of PO Lines\r(12/01/04 - 05/31/06)", "53,523" },
+ {"Total PO History Spend", "$10,172,038"}
+ };
+
+ HSLFSlideShow ppt = new HSLFSlideShow();
+
+ HSLFSlide slide = ppt.createSlide();
+
+ //six rows, two columns
+ HSLFTable table1 = slide.createTable(6, 2);
+ for (int i = 0; i < txt1.length; i++) {
+ for (int j = 0; j < txt1[i].length; j++) {
+ HSLFTableCell cell = table1.getCell(i, j);
+ HSLFTextRun rt = cell.getTextParagraphs().get(0).getTextRuns().get(0);
+ rt.setFontFamily("Arial");
+ rt.setFontSize(10d);
+ if(i == 0){
+ cell.getFill().setForegroundColor(new Color(227, 227, 227));
+ } else {
+ rt.setBold(true);
+ }
+ cell.setVerticalAlignment(VerticalAlignment.MIDDLE);
+ cell.setHorizontalCentered(true);
+ cell.setText(txt1[i][j]);
+ }
+ }
+
+ DrawTableShape dts1 = new DrawTableShape(table1);
+ dts1.setAllBorders(1.0, Color.black);
+
+ table1.setColumnWidth(0, 300);
+ table1.setColumnWidth(1, 150);
+
+ int pgWidth = ppt.getPageSize().width;
+ table1.moveTo((pgWidth - table1.getAnchor().getWidth())/2., 100.);
+
+ //test data for the second taable
+ String[][] txt2 = {
+ {"Data Source"},
+ {"CAS Internal Metrics - Item Master Summary\r" +
+ "CAS Internal Metrics - Vendor Summary\r" +
+ "CAS Internal Metrics - PO History Summary"}
+ };
+
+ //two rows, one column
+ HSLFTable table2 = slide.createTable(2, 1);
+ for (int i = 0; i < txt2.length; i++) {
+ for (int j = 0; j < txt2[i].length; j++) {
+ HSLFTableCell cell = table2.getCell(i, j);
+ HSLFTextRun rt = cell.getTextParagraphs().get(0).getTextRuns().get(0);
+ rt.setFontSize(10d);
+ rt.setFontFamily("Arial");
+ if(i == 0){
+ cell.getFill().setForegroundColor(new Color(0, 51, 102));
+ rt.setFontColor(Color.white);
+ rt.setBold(true);
+ rt.setFontSize(14d);
+ cell.setHorizontalCentered(true);
+ } else {
+ rt.getTextParagraph().setBullet(true);
+ rt.setFontSize(12d);
+ rt.getTextParagraph().setTextAlign(TextAlign.LEFT);
+ cell.setHorizontalCentered(false);
+ }
+ cell.setVerticalAlignment(VerticalAlignment.MIDDLE);
+ cell.setText(txt2[i][j]);
+ }
+ }
+ table2.setColumnWidth(0, 300);
+ table2.setRowHeight(0, 30);
+ table2.setRowHeight(1, 70);
+
+ DrawTableShape dts2 = new DrawTableShape(table2);
+ dts2.setOutsideBorders(Color.black, 1.0);
+
+ table2.moveTo(200, 400);
+
+ FileOutputStream out = new FileOutputStream("hslf-table.ppt");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hsmf/examples/Msg2txt.java b/trunk/src/examples/src/org/apache/poi/hsmf/examples/Msg2txt.java
new file mode 100644
index 000000000..bf018a3e4
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hsmf/examples/Msg2txt.java
@@ -0,0 +1,171 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hsmf.examples;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import org.apache.poi.hsmf.MAPIMessage;
+import org.apache.poi.hsmf.datatypes.AttachmentChunks;
+import org.apache.poi.hsmf.exceptions.ChunkNotFoundException;
+
+/**
+ * Reads one or several Outlook MSG files and for each of them creates
+ * a text file from available chunks and a directory that contains
+ * attachments.
+ *
+ * @author Bruno Girin
+ */
+public class Msg2txt {
+
+ /**
+ * The stem used to create file names for the text file and the directory
+ * that contains the attachments.
+ */
+ private String fileNameStem;
+
+ /**
+ * The Outlook MSG file being processed.
+ */
+ private MAPIMessage msg;
+
+ public Msg2txt(String fileName) throws IOException {
+ fileNameStem = fileName;
+ if(fileNameStem.endsWith(".msg") || fileNameStem.endsWith(".MSG")) {
+ fileNameStem = fileNameStem.substring(0, fileNameStem.length() - 4);
+ }
+ msg = new MAPIMessage(fileName);
+ }
+
+ /**
+ * Processes the message.
+ *
+ * @throws IOException if an exception occurs while writing the message out
+ */
+ public void processMessage() throws IOException {
+ String txtFileName = fileNameStem + ".txt";
+ String attDirName = fileNameStem + "-att";
+ PrintWriter txtOut = null;
+ try {
+ txtOut = new PrintWriter(txtFileName);
+ try {
+ String displayFrom = msg.getDisplayFrom();
+ txtOut.println("From: "+displayFrom);
+ } catch (ChunkNotFoundException e) {
+ // ignore
+ }
+ try {
+ String displayTo = msg.getDisplayTo();
+ txtOut.println("To: "+displayTo);
+ } catch (ChunkNotFoundException e) {
+ // ignore
+ }
+ try {
+ String displayCC = msg.getDisplayCC();
+ txtOut.println("CC: "+displayCC);
+ } catch (ChunkNotFoundException e) {
+ // ignore
+ }
+ try {
+ String displayBCC = msg.getDisplayBCC();
+ txtOut.println("BCC: "+displayBCC);
+ } catch (ChunkNotFoundException e) {
+ // ignore
+ }
+ try {
+ String subject = msg.getSubject();
+ txtOut.println("Subject: "+subject);
+ } catch (ChunkNotFoundException e) {
+ // ignore
+ }
+ try {
+ String body = msg.getTextBody();
+ txtOut.println(body);
+ } catch (ChunkNotFoundException e) {
+ System.err.println("No message body");
+ }
+
+ AttachmentChunks[] attachments = msg.getAttachmentFiles();
+ if(attachments.length > 0) {
+ File d = new File(attDirName);
+ if(d.mkdir()) {
+ for(AttachmentChunks attachment : attachments) {
+ processAttachment(attachment, d);
+ }
+ } else {
+ System.err.println("Can't create directory "+attDirName);
+ }
+ }
+ } finally {
+ if(txtOut != null) {
+ txtOut.close();
+ }
+ }
+ }
+
+ /**
+ * Processes a single attachment: reads it from the Outlook MSG file and
+ * writes it to disk as an individual file.
+ *
+ * @param attachment the chunk group describing the attachment
+ * @param dir the directory in which to write the attachment file
+ * @throws IOException when any of the file operations fails
+ */
+ public void processAttachment(AttachmentChunks attachment,
+ File dir) throws IOException {
+ String fileName = attachment.attachFileName.toString();
+ if(attachment.attachLongFileName != null) {
+ fileName = attachment.attachLongFileName.toString();
+ }
+
+ File f = new File(dir, fileName);
+ OutputStream fileOut = null;
+ try {
+ fileOut = new FileOutputStream(f);
+ fileOut.write(attachment.attachData.getValue());
+ } finally {
+ if(fileOut != null) {
+ fileOut.close();
+ }
+ }
+ }
+
+ /**
+ * Processes the list of arguments as a list of names of Outlook MSG files.
+ *
+ * @param args the list of MSG files to process
+ */
+ public static void main(String[] args) {
+ if(args.length <= 0) {
+ System.err.println("No files names provided");
+ } else {
+ for(int i = 0; i < args.length; i++) {
+ try {
+ Msg2txt processor = new Msg2txt(args[i]);
+ processor.processMessage();
+ } catch (IOException e) {
+ System.err.println("Could not process "+args[i]+": "+e);
+ }
+ }
+ }
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java b/trunk/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java
new file mode 100644
index 000000000..8bab0a8e4
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java
@@ -0,0 +1,328 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.eventusermodel.examples;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;
+import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
+import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
+import org.apache.poi.hssf.eventusermodel.HSSFListener;
+import org.apache.poi.hssf.eventusermodel.HSSFRequest;
+import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener;
+import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
+import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
+import org.apache.poi.hssf.record.BOFRecord;
+import org.apache.poi.hssf.record.BlankRecord;
+import org.apache.poi.hssf.record.BoolErrRecord;
+import org.apache.poi.hssf.record.BoundSheetRecord;
+import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.LabelRecord;
+import org.apache.poi.hssf.record.LabelSSTRecord;
+import org.apache.poi.hssf.record.NoteRecord;
+import org.apache.poi.hssf.record.NumberRecord;
+import org.apache.poi.hssf.record.RKRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SSTRecord;
+import org.apache.poi.hssf.record.StringRecord;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+/**
+ * A XLS -> CSV processor, that uses the MissingRecordAware
+ * EventModel code to ensure it outputs all columns and rows.
+ * @author Nick Burch
+ */
+public class XLS2CSVmra implements HSSFListener {
+ private int minColumns;
+ private POIFSFileSystem fs;
+ private PrintStream output;
+
+ private int lastRowNumber;
+ private int lastColumnNumber;
+
+ /** Should we output the formula, or the value it has? */
+ private boolean outputFormulaValues = true;
+
+ /** For parsing Formulas */
+ private SheetRecordCollectingListener workbookBuildingListener;
+ private HSSFWorkbook stubWorkbook;
+
+ // Records we pick up as we process
+ private SSTRecord sstRecord;
+ private FormatTrackingHSSFListener formatListener;
+
+ /** So we known which sheet we're on */
+ private int sheetIndex = -1;
+ private BoundSheetRecord[] orderedBSRs;
+ private List boundSheetRecords = new ArrayList();
+
+ // For handling formulas with string results
+ private int nextRow;
+ private int nextColumn;
+ private boolean outputNextStringRecord;
+
+ /**
+ * Creates a new XLS -> CSV converter
+ * @param fs The POIFSFileSystem to process
+ * @param output The PrintStream to output the CSV to
+ * @param minColumns The minimum number of columns to output, or -1 for no minimum
+ */
+ public XLS2CSVmra(POIFSFileSystem fs, PrintStream output, int minColumns) {
+ this.fs = fs;
+ this.output = output;
+ this.minColumns = minColumns;
+ }
+
+ /**
+ * Creates a new XLS -> CSV converter
+ * @param filename The file to process
+ * @param minColumns The minimum number of columns to output, or -1 for no minimum
+ * @throws IOException
+ * @throws FileNotFoundException
+ */
+ public XLS2CSVmra(String filename, int minColumns) throws IOException, FileNotFoundException {
+ this(
+ new POIFSFileSystem(new FileInputStream(filename)),
+ System.out, minColumns
+ );
+ }
+
+ /**
+ * Initiates the processing of the XLS file to CSV
+ */
+ public void process() throws IOException {
+ MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this);
+ formatListener = new FormatTrackingHSSFListener(listener);
+
+ HSSFEventFactory factory = new HSSFEventFactory();
+ HSSFRequest request = new HSSFRequest();
+
+ if(outputFormulaValues) {
+ request.addListenerForAllRecords(formatListener);
+ } else {
+ workbookBuildingListener = new SheetRecordCollectingListener(formatListener);
+ request.addListenerForAllRecords(workbookBuildingListener);
+ }
+
+ factory.processWorkbookEvents(request, fs);
+ }
+
+ /**
+ * Main HSSFListener method, processes events, and outputs the
+ * CSV as the file is processed.
+ */
+ public void processRecord(Record record) {
+ int thisRow = -1;
+ int thisColumn = -1;
+ String thisStr = null;
+
+ switch (record.getSid())
+ {
+ case BoundSheetRecord.sid:
+ boundSheetRecords.add((BoundSheetRecord)record);
+ break;
+ case BOFRecord.sid:
+ BOFRecord br = (BOFRecord)record;
+ if(br.getType() == BOFRecord.TYPE_WORKSHEET) {
+ // Create sub workbook if required
+ if(workbookBuildingListener != null && stubWorkbook == null) {
+ stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook();
+ }
+
+ // Output the worksheet name
+ // Works by ordering the BSRs by the location of
+ // their BOFRecords, and then knowing that we
+ // process BOFRecords in byte offset order
+ sheetIndex++;
+ if(orderedBSRs == null) {
+ orderedBSRs = BoundSheetRecord.orderByBofPosition(boundSheetRecords);
+ }
+ output.println();
+ output.println(
+ orderedBSRs[sheetIndex].getSheetname() +
+ " [" + (sheetIndex+1) + "]:"
+ );
+ }
+ break;
+
+ case SSTRecord.sid:
+ sstRecord = (SSTRecord) record;
+ break;
+
+ case BlankRecord.sid:
+ BlankRecord brec = (BlankRecord) record;
+
+ thisRow = brec.getRow();
+ thisColumn = brec.getColumn();
+ thisStr = "";
+ break;
+ case BoolErrRecord.sid:
+ BoolErrRecord berec = (BoolErrRecord) record;
+
+ thisRow = berec.getRow();
+ thisColumn = berec.getColumn();
+ thisStr = "";
+ break;
+
+ case FormulaRecord.sid:
+ FormulaRecord frec = (FormulaRecord) record;
+
+ thisRow = frec.getRow();
+ thisColumn = frec.getColumn();
+
+ if(outputFormulaValues) {
+ if(Double.isNaN( frec.getValue() )) {
+ // Formula result is a string
+ // This is stored in the next record
+ outputNextStringRecord = true;
+ nextRow = frec.getRow();
+ nextColumn = frec.getColumn();
+ } else {
+ thisStr = formatListener.formatNumberDateCell(frec);
+ }
+ } else {
+ thisStr = '"' +
+ HSSFFormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression()) + '"';
+ }
+ break;
+ case StringRecord.sid:
+ if(outputNextStringRecord) {
+ // String for formula
+ StringRecord srec = (StringRecord)record;
+ thisStr = srec.getString();
+ thisRow = nextRow;
+ thisColumn = nextColumn;
+ outputNextStringRecord = false;
+ }
+ break;
+
+ case LabelRecord.sid:
+ LabelRecord lrec = (LabelRecord) record;
+
+ thisRow = lrec.getRow();
+ thisColumn = lrec.getColumn();
+ thisStr = '"' + lrec.getValue() + '"';
+ break;
+ case LabelSSTRecord.sid:
+ LabelSSTRecord lsrec = (LabelSSTRecord) record;
+
+ thisRow = lsrec.getRow();
+ thisColumn = lsrec.getColumn();
+ if(sstRecord == null) {
+ thisStr = '"' + "(No SST Record, can't identify string)" + '"';
+ } else {
+ thisStr = '"' + sstRecord.getString(lsrec.getSSTIndex()).toString() + '"';
+ }
+ break;
+ case NoteRecord.sid:
+ NoteRecord nrec = (NoteRecord) record;
+
+ thisRow = nrec.getRow();
+ thisColumn = nrec.getColumn();
+ // TODO: Find object to match nrec.getShapeId()
+ thisStr = '"' + "(TODO)" + '"';
+ break;
+ case NumberRecord.sid:
+ NumberRecord numrec = (NumberRecord) record;
+
+ thisRow = numrec.getRow();
+ thisColumn = numrec.getColumn();
+
+ // Format
+ thisStr = formatListener.formatNumberDateCell(numrec);
+ break;
+ case RKRecord.sid:
+ RKRecord rkrec = (RKRecord) record;
+
+ thisRow = rkrec.getRow();
+ thisColumn = rkrec.getColumn();
+ thisStr = '"' + "(TODO)" + '"';
+ break;
+ default:
+ break;
+ }
+
+ // Handle new row
+ if(thisRow != -1 && thisRow != lastRowNumber) {
+ lastColumnNumber = -1;
+ }
+
+ // Handle missing column
+ if(record instanceof MissingCellDummyRecord) {
+ MissingCellDummyRecord mc = (MissingCellDummyRecord)record;
+ thisRow = mc.getRow();
+ thisColumn = mc.getColumn();
+ thisStr = "";
+ }
+
+ // If we got something to print out, do so
+ if(thisStr != null) {
+ if(thisColumn > 0) {
+ output.print(',');
+ }
+ output.print(thisStr);
+ }
+
+ // Update column and row count
+ if(thisRow > -1)
+ lastRowNumber = thisRow;
+ if(thisColumn > -1)
+ lastColumnNumber = thisColumn;
+
+ // Handle end of row
+ if(record instanceof LastCellOfRowDummyRecord) {
+ // Print out any missing commas if needed
+ if(minColumns > 0) {
+ // Columns are 0 based
+ if(lastColumnNumber == -1) { lastColumnNumber = 0; }
+ for(int i=lastColumnNumber; i<(minColumns); i++) {
+ output.print(',');
+ }
+ }
+
+ // We're onto a new row
+ lastColumnNumber = -1;
+
+ // End the row
+ output.println();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if(args.length < 1) {
+ System.err.println("Use:");
+ System.err.println(" XLS2CSVmra [min columns]");
+ System.exit(1);
+ }
+
+ int minColumns = -1;
+ if(args.length >= 2) {
+ minColumns = Integer.parseInt(args[1]);
+ }
+
+ XLS2CSVmra xls2csv = new XLS2CSVmra(args[0], minColumns);
+ xls2csv.process();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/AddDimensionedImage.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/AddDimensionedImage.java
new file mode 100644
index 000000000..9b86cf779
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/AddDimensionedImage.java
@@ -0,0 +1,944 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
+import org.apache.poi.hssf.usermodel.HSSFPatriarch;
+import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
+
+
+/**
+ * Demonstrates how to add an image to a worksheet and set that image's size
+ * to a specific number of milimetres irrespective of the width of the columns
+ * or height of the rows. Overridden methods are provided so that the location
+ * of the image - the cells row and column co-ordinates that define the top
+ * left hand corners of the image - can be identified either in the familiar
+ * Excel manner - A1 for instance - or using POI's methodolody of a column and
+ * row index where 0, 0 would indicate cell A1.
+ *
+ * The best way to make use of these techniques is to delay adding the image to
+ * the sheet until all other work has been completed. That way, the sizes of
+ * all rows and columns will have been adjusted - assuming that step was
+ * necessary. Even though the anchors type is set to prevent the image moving
+ * or re-sizing, this setting does not have any effect until the sheet is being
+ * viewed using the Excel application.
+ *
+ * The key to the process is the HSSFClientAnchor class. It accepts eight
+ * parameters that define, in order;
+ *
+ * * How far - in terms of co-ordinate position - the image should be inset
+ * from the left hand border of a cell.
+ * * How far - in terms of co-ordinate positions - the image should be inset
+ * from the from the top of the cell.
+ * * How far - in terms of co-ordinate positions - the right hand edge of
+ * the image should protrude into a cell (measured from the cell's left hand
+ * edge to the image's right hand edge).
+ * * How far - in terms of co-ordinate positions - the bottm edge of the
+ * image should protrude into a row (measured from the cell's top edge to
+ * the image's bottom edge).
+ * * The index of the column that contains the cell whose top left hand
+ * corner should be aligned with the top left hand corner of the image.
+ * * The index of the row that contains the cell whose top left hand corner
+ * should be aligned with the image's top left hand corner.
+ * * The index of the column that contains the cell whose top left hand
+ * corner should be aligned with the image's bottom right hand corner
+ * * The index number of the row that contains the cell whose top left
+ * hand corner should be aligned with the images bottom right hand corner.
+ *
+ * It can be used to add an image into cell A1, for example, in the following
+ * manner;
+ *
+ * HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 0, 0,
+ * (short)0, 0, (short)1, 1);
+ *
+ * The final four parameters determine that the top left hand corner should be
+ * aligned with the top left hand corner of cell A1 and it's bottom right
+ * hand corner with the top left hand corner of cell B2. Think of the image as
+ * being stretched so that it's top left hand corner is aligned with the top
+ * left hand corner of cell A1 and it's bottom right hand corner is aligned with
+ * the top left hand corner of cell B1. Interestingly, this would also produce
+ * the same results;
+ *
+ * anchor = new HSSFClientAnchor(0, 0, 1023, 255,
+ * (short)0, 0, (short)0, 0);
+ *
+ * Note that the final four parameters all contain the same value and seem to
+ * indicate that the images top left hand corner is aligned with the top left
+ * hand corner of cell A1 and that it's bottom right hand corner is also
+ * aligned with the top left hand corner of cell A1. Yet, running this code
+ * would see the image fully occupying cell A1. That is the result of the
+ * values passed to parameters three and four; these I have referred to as
+ * determing the images co-ordinates within the cell. They indicate that the
+ * image should occupy - in order - the full width of the column and the full
+ * height of the row.
+ *
+ * The co-ordinate values shown are the maxima; and they are independent of
+ * row height/column width and of the font used. Passing 255 will always result
+ * in the image occupying the full height of the row and passing 1023 will
+ * always result in the image occupying the full width of the column. They help
+ * in situations where an image is larger than a column/row and must overlap
+ * into the next column/row. Using them does mean, however, that it is often
+ * necessary to perform conversions between Excel's characters units, points,
+ * pixels and millimetres in order to establish how many rows/columns an image
+ * should occupy and just what the varous insets ought to be.
+ *
+ * Note that the first two parameters of the HSSFClientAchor classes constructor
+ * are not made use of in the code that follows. It would be fairly trivial
+ * however to extend these example further and provide methods that would centre
+ * an image within a cell or allow the user to specify that a plain border a
+ * fixed number of millimetres wide should wrap around the image. Those first
+ * two parameters would make this sort of functionality perfectly possible.
+ *
+ * Owing to the various conversions used, the actual size of the image may vary
+ * from that required; testing has so far found this to be in the region of
+ * plus or minus two millimetres. Most likely by modifying the way the
+ * calculations are performed - possibly using double(s) throughout and
+ * rounding the values at the correct point - it is likely that these errors
+ * could be reduced or removed.
+ *
+ * A note concerning Excels' image resizing behaviour. The HSSFClientAnchor
+ * class contains a method called setAnchorType(int) which can be used to
+ * determine how Excel will resize an image in reponse to the user increasing
+ * or decreasing the dimensions of the cell containing the image. There are
+ * three values that can be passed to this method; 0 = To move and size the
+ * image with the cell, 2 = To move but don't size the image with the cell,
+ * 3 = To prevent the image from moving or being resized along with the cell. If
+ * an image is inserted using this class and placed into a single cell then if
+ * the setAnchorType(int) method is called and a value of either 0 or 2 passed
+ * to it, the resultant resizing behaviour may be a surprise. The image will not
+ * grow in size of the column is made wider or the row higher but it will shrink
+ * if the columns width or rows height are reduced.
+ *
+ * @author Mark Beardsley [msb at apache.org]
+ * @version 1.00 5th August 2009.
+ */
+public class AddDimensionedImage {
+
+ // Four constants that determine how - and indeed whether - the rows
+ // and columns an image may overlie should be expanded to accomodate that
+ // image.
+ // Passing EXPAND_ROW will result in the height of a row being increased
+ // to accomodate the image if it is not already larger. The image will
+ // be layed across one or more columns.
+ // Passing EXPAND_COLUMN will result in the width of the column being
+ // increased to accomodate the image if it is not already larger. The image
+ // will be layed across one or many rows.
+ // Passing EXPAND_ROW_AND_COLUMN will result in the height of the row
+ // bing increased along with the width of the column to accomdate the
+ // image if either is not already larger.
+ // Passing OVERLAY_ROW_AND_COLUMN will result in the image being layed
+ // over one or more rows and columns. No row or column will be resized,
+ // instead, code will determine how many rows and columns the image should
+ // overlie.
+ public static final int EXPAND_ROW = 1;
+ public static final int EXPAND_COLUMN = 2;
+ public static final int EXPAND_ROW_AND_COLUMN = 3;
+ public static final int OVERLAY_ROW_AND_COLUMN = 7;
+
+ /**
+ * Add an image to a worksheet.
+ *
+ * @param cellNumber A String that contains the location of the cell whose
+ * top left hand corner should be aligned with the top
+ * left hand corner of the image; for example "A1", "A2"
+ * etc. This is to support the familiar Excel syntax.
+ * Whilst images are are not actually inserted into cells
+ * this provides a convenient method of indicating where
+ * the image should be positioned on the sheet.
+ * @param sheet A reference to the sheet that contains the cell referenced
+ * above.
+ * @param imageFile A String that encapsulates the name of and path to
+ * the image that is to be 'inserted into' the sheet.
+ * @param reqImageWidthMM A primitive double that contains the required
+ * width of the image in millimetres.
+ * @param reqImageHeightMM A primitive double that contains the required
+ * height of the image in millimetres.
+ * @param resizeBehaviour A primitive int whose value will determine how
+ * the code should react if the image is larger than
+ * the cell referenced by the cellNumber parameter.
+ * Four constants are provided to determine what
+ * should happen;
+ * AddDimensionedImage.EXPAND_ROW
+ * AddDimensionedImage.EXPAND_COLUMN
+ * AddDimensionedImage.EXPAND_ROW_AND_COLUMN
+ * AddDimensionedImage.OVERLAY_ROW_AND_COLUMN
+ * @throws java.io.FileNotFoundException If the file containing the image
+ * cannot be located.
+ * @throws java.io.IOException If a problem occurs whilst reading the file
+ * of image data.
+ * @throws java.lang.IllegalArgumentException If an invalid value is passed
+ * to the resizeBehaviour
+ * parameter.
+ */
+ public void addImageToSheet(String cellNumber, HSSFSheet sheet,
+ String imageFile, double reqImageWidthMM, double reqImageHeightMM,
+ int resizeBehaviour) throws IOException, IllegalArgumentException {
+ // Convert the String into column and row indices then chain the
+ // call to the overridden addImageToSheet() method.
+ CellReference cellRef = new CellReference(cellNumber);
+ this.addImageToSheet(cellRef.getCol(), cellRef.getRow(), sheet,
+ imageFile, reqImageWidthMM, reqImageHeightMM,resizeBehaviour);
+ }
+
+ /**
+ * Add an image to a worksheet.
+ *
+ * @param colNumber A primitive int that contains the index number of a
+ * column on the worksheet; POI column indices are zero
+ * based. Together with the rowNumber parameter's value,
+ * this parameter identifies a cell on the worksheet. The
+ * image's top left hand corner will be aligned with the
+ * top left hand corner of this cell.
+ * @param rowNumber A primtive int that contains the index number of a row
+ * on the worksheet; POI row indices are zero based.
+ * Together with the rowNumber parameter's value, this
+ * parameter identifies a cell on the worksheet. The
+ * image's top left hand corner will be aligned with the
+ * top left hand corner of this cell.
+ * @param sheet A reference to the sheet that contains the cell identified
+ * by the two parameters above.
+ * @param imageFile A String that encapsulates the name of and path to
+ * the image that is to be 'inserted into' the sheet.
+ * @param reqImageWidthMM A primitive double that contains the required
+ * width of the image in millimetres.
+ * @param reqImageHeightMM A primitive double that contains the required
+ * height of the image in millimetres.
+ * @param resizeBehaviour A primitive int whose value will determine how
+ * the code should react if the image is larger than
+ * the cell referenced by the colNumber and
+ * rowNumber parameters. Four constants are provided
+ * to determine what should happen;
+ * AddDimensionedImage.EXPAND_ROW
+ * AddDimensionedImage.EXPAND_COLUMN
+ * AddDimensionedImage.EXPAND_ROW_AND_COLUMN
+ * AddDimensionedImage.OVERLAY_ROW_AND_COLUMN
+ * @throws java.io.FileNotFoundException If the file containing the image
+ * cannot be located.
+ * @throws java.io.IOException If a problem occurs whilst reading the file
+ * of image data.
+ * @throws java.lang.IllegalArgumentException If an invalid value is passed
+ * to the resizeBehaviour
+ * parameter.
+ */
+ private void addImageToSheet(int colNumber, int rowNumber, HSSFSheet sheet,
+ String imageFile, double reqImageWidthMM, double reqImageHeightMM,
+ int resizeBehaviour) throws FileNotFoundException, IOException,
+ IllegalArgumentException {
+ HSSFClientAnchor anchor = null;
+ HSSFPatriarch patriarch = null;
+ ClientAnchorDetail rowClientAnchorDetail = null;
+ ClientAnchorDetail colClientAnchorDetail = null;
+
+ // Validate the resizeBehaviour parameter.
+ if((resizeBehaviour != AddDimensionedImage.EXPAND_COLUMN) &&
+ (resizeBehaviour != AddDimensionedImage.EXPAND_ROW) &&
+ (resizeBehaviour != AddDimensionedImage.EXPAND_ROW_AND_COLUMN) &&
+ (resizeBehaviour != AddDimensionedImage.OVERLAY_ROW_AND_COLUMN)) {
+ throw new IllegalArgumentException("Invalid value passed to the " +
+ "resizeBehaviour parameter of AddDimensionedImage.addImageToSheet()");
+ }
+
+ // Call methods to calculate how the image and sheet should be
+ // manipulated to accomodate the image; columns and then rows.
+ colClientAnchorDetail = this.fitImageToColumns(sheet, colNumber,
+ reqImageWidthMM, resizeBehaviour);
+ rowClientAnchorDetail = this.fitImageToRows(sheet, rowNumber,
+ reqImageHeightMM, resizeBehaviour);
+
+ // Having determined if and how to resize the rows, columns and/or the
+ // image, create the HSSFClientAnchor object to position the image on
+ // the worksheet. Note how the two ClientAnchorDetail records are
+ // interrogated to recover the row/column co-ordinates and any insets.
+ // The first two parameters are not used currently but could be if the
+ // need arose to extend the functionality of this code by adding the
+ // ability to specify that a clear 'border' be placed around the image.
+ anchor = new HSSFClientAnchor(0,
+ 0,
+ colClientAnchorDetail.getInset(),
+ rowClientAnchorDetail.getInset(),
+ (short)colClientAnchorDetail.getFromIndex(),
+ rowClientAnchorDetail.getFromIndex(),
+ (short)colClientAnchorDetail.getToIndex(),
+ rowClientAnchorDetail.getToIndex());
+
+ // For now, set the anchor type to do not move or resize the
+ // image as the size of the row/column is adjusted. This could easilly
+ // become another parameter passed to the method.
+ //anchor.setAnchorType(HSSFClientAnchor.DONT_MOVE_AND_RESIZE);
+ anchor.setAnchorType(AnchorType.MOVE_AND_RESIZE);
+
+ // Now, add the picture to the workbook. Note that the type is assumed
+ // to be a JPEG/JPG, this could easily (and should) be parameterised
+ // however.
+ //int index = sheet.getWorkbook().addPicture(this.imageToBytes(imageFile),
+ // HSSFWorkbook.PICTURE_TYPE_JPEG);
+ int index = sheet.getWorkbook().addPicture(this.imageToBytes(imageFile), HSSFWorkbook.PICTURE_TYPE_PNG);
+
+ // Get the drawing patriarch and create the picture.
+ patriarch = sheet.createDrawingPatriarch();
+ patriarch.createPicture(anchor, index);
+ }
+
+ /**
+ * Determines whether the sheets columns should be re-sized to accomodate
+ * the image, adjusts the columns width if necessary and creates then
+ * returns a ClientAnchorDetail object that facilitates construction of
+ * an HSSFClientAnchor that will fix the image on the sheet and establish
+ * it's size.
+ *
+ * @param sheet A reference to the sheet that will 'contain' the image.
+ * @param colNumber A primtive int that contains the index number of a
+ * column on the sheet.
+ * @param reqImageWidthMM A primtive double that contains the required
+ * width of the image in millimetres
+ * @param resizeBehaviour A primitve int whose value will indicate how the
+ * width of the column should be adjusted if the
+ * required width of the image is greater than the
+ * width of the column.
+ * @return An instance of the ClientAnchorDetail class that will contain
+ * the index number of the column containing the cell whose top
+ * left hand corner also defines the top left hand corner of the
+ * image, the index number column containing the cell whose top
+ * left hand corner also defines the bottom right hand corner of
+ * the image and an inset that determines how far the right hand
+ * edge of the image can protrude into the next column - expressed
+ * as a specific number of co-ordinate positions.
+ */
+ private ClientAnchorDetail fitImageToColumns(HSSFSheet sheet, int colNumber,
+ double reqImageWidthMM, int resizeBehaviour) {
+
+ double colWidthMM = 0.0D;
+ double colCoordinatesPerMM = 0.0D;
+ int pictureWidthCoordinates = 0;
+ ClientAnchorDetail colClientAnchorDetail = null;
+
+ // Get the colum's width in millimetres
+ colWidthMM = ConvertImageUnits.widthUnits2Millimetres(
+ (short)sheet.getColumnWidth(colNumber));
+
+ // Check that the column's width will accomodate the image at the
+ // required dimension. If the width of the column is LESS than the
+ // required width of the image, decide how the application should
+ // respond - resize the column or overlay the image across one or more
+ // columns.
+ if(colWidthMM < reqImageWidthMM) {
+
+ // Should the column's width simply be expanded?
+ if((resizeBehaviour == AddDimensionedImage.EXPAND_COLUMN) ||
+ (resizeBehaviour == AddDimensionedImage.EXPAND_ROW_AND_COLUMN)) {
+ // Set the width of the column by converting the required image
+ // width from millimetres into Excel's column width units.
+ sheet.setColumnWidth(colNumber,
+ ConvertImageUnits.millimetres2WidthUnits(reqImageWidthMM));
+ // To make the image occupy the full width of the column, convert
+ // the required width of the image into co-ordinates. This value
+ // will become the inset for the ClientAnchorDetail class that
+ // is then instantiated.
+ colWidthMM = reqImageWidthMM;
+ colCoordinatesPerMM = ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS /
+ colWidthMM;
+ pictureWidthCoordinates = (int)(reqImageWidthMM * colCoordinatesPerMM);
+ colClientAnchorDetail = new ClientAnchorDetail(colNumber,
+ colNumber, pictureWidthCoordinates);
+ }
+ // If the user has chosen to overlay both rows and columns or just
+ // to expand ONLY the size of the rows, then calculate how to lay
+ // the image out across one or more columns.
+ else if ((resizeBehaviour == AddDimensionedImage.OVERLAY_ROW_AND_COLUMN) ||
+ (resizeBehaviour == AddDimensionedImage.EXPAND_ROW)) {
+ colClientAnchorDetail = this.calculateColumnLocation(sheet,
+ colNumber, reqImageWidthMM);
+ }
+ }
+ // If the column is wider than the image.
+ else {
+ // Mow many co-ordinate positions are there per millimetre?
+ colCoordinatesPerMM = ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS /
+ colWidthMM;
+ // Given the width of the image, what should be it's co-ordinate?
+ pictureWidthCoordinates = (int)(reqImageWidthMM * colCoordinatesPerMM);
+ colClientAnchorDetail = new ClientAnchorDetail(colNumber,
+ colNumber, pictureWidthCoordinates);
+ }
+ return(colClientAnchorDetail);
+ }
+
+ /**
+ * Determines whether the sheet's row should be re-sized to accomodate
+ * the image, adjusts the rows height if necessary and creates then
+ * returns a ClientAnchorDetail object that facilitates construction of
+ * an HSSFClientAnchor that will fix the image on the sheet and establish
+ * it's size.
+ *
+ * @param sheet A reference to the sheet that will 'contain' the image.
+ * @param rowNumber A primtive int that contains the index number of a
+ * row on the sheet.
+ * @param reqImageHeightMM A primtive double that contains the required
+ * height of the image in millimetres
+ * @param resizeBehaviour A primitve int whose value will indicate how the
+ * height of the row should be adjusted if the
+ * required height of the image is greater than the
+ * height of the row.
+ * @return An instance of the ClientAnchorDetail class that will contain
+ * the index number of the row containing the cell whose top
+ * left hand corner also defines the top left hand corner of the
+ * image, the index number of the row containing the cell whose
+ * top left hand corner also defines the bottom right hand
+ * corner of the image and an inset that determines how far the
+ * bottom edge of the image can protrude into the next (lower)
+ * row - expressed as a specific number of co-ordinate positions.
+ */
+ private ClientAnchorDetail fitImageToRows(HSSFSheet sheet, int rowNumber,
+ double reqImageHeightMM, int resizeBehaviour) {
+ HSSFRow row = null;
+ double rowHeightMM = 0.0D;
+ double rowCoordinatesPerMM = 0.0D;
+ int pictureHeightCoordinates = 0;
+ ClientAnchorDetail rowClientAnchorDetail = null;
+
+ // Get the row and it's height
+ row = sheet.getRow(rowNumber);
+ if(row == null) {
+ // Create row if it does not exist.
+ row = sheet.createRow(rowNumber);
+ }
+
+ // Get the row's height in millimetres
+ rowHeightMM = row.getHeightInPoints() / ConvertImageUnits.POINTS_PER_MILLIMETRE;
+
+ // Check that the row's height will accomodate the image at the required
+ // dimensions. If the height of the row is LESS than the required height
+ // of the image, decide how the application should respond - resize the
+ // row or overlay the image across a series of rows.
+ if(rowHeightMM < reqImageHeightMM) {
+ if((resizeBehaviour == AddDimensionedImage.EXPAND_ROW) ||
+ (resizeBehaviour == AddDimensionedImage.EXPAND_ROW_AND_COLUMN)) {
+ row.setHeightInPoints((float)(reqImageHeightMM *
+ ConvertImageUnits.POINTS_PER_MILLIMETRE));
+ rowHeightMM = reqImageHeightMM;
+ rowCoordinatesPerMM = ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS /
+ rowHeightMM;
+ pictureHeightCoordinates = (int)(reqImageHeightMM * rowCoordinatesPerMM);
+ rowClientAnchorDetail = new ClientAnchorDetail(rowNumber,
+ rowNumber, pictureHeightCoordinates);
+ }
+ // If the user has chosen to overlay both rows and columns or just
+ // to expand ONLY the size of the columns, then calculate how to lay
+ // the image out ver one or more rows.
+ else if((resizeBehaviour == AddDimensionedImage.OVERLAY_ROW_AND_COLUMN) ||
+ (resizeBehaviour == AddDimensionedImage.EXPAND_COLUMN)) {
+ rowClientAnchorDetail = this.calculateRowLocation(sheet,
+ rowNumber, reqImageHeightMM);
+ }
+ }
+ // Else, if the image is smaller than the space available
+ else {
+ rowCoordinatesPerMM = ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS /
+ rowHeightMM;
+ pictureHeightCoordinates = (int)(reqImageHeightMM * rowCoordinatesPerMM);
+ rowClientAnchorDetail = new ClientAnchorDetail(rowNumber,
+ rowNumber, pictureHeightCoordinates);
+ }
+ return(rowClientAnchorDetail);
+ }
+
+ /**
+ * If the image is to overlie more than one column, calculations need to be
+ * performed to determine how many columns and whether the image will
+ * overlie just a part of one column in order to be presented at the
+ * required size.
+ *
+ * @param sheet The sheet that will 'contain' the image.
+ * @param startingColumn A primitive int whose value is the index of the
+ * column that contains the cell whose top left hand
+ * corner should be aligned with the top left hand
+ * corner of the image.
+ * @param reqImageWidthMM A primitive double whose value will indicate the
+ * required width of the image in millimetres.
+ * @return An instance of the ClientAnchorDetail class that will contain
+ * the index number of the column containing the cell whose top
+ * left hand corner also defines the top left hand corner of the
+ * image, the index number column containing the cell whose top
+ * left hand corner also defines the bottom right hand corner of
+ * the image and an inset that determines how far the right hand
+ * edge of the image can protrude into the next column - expressed
+ * as a specific number of co-ordinate positions.
+ */
+ private ClientAnchorDetail calculateColumnLocation(HSSFSheet sheet,
+ int startingColumn,
+ double reqImageWidthMM) {
+ ClientAnchorDetail anchorDetail = null;
+ double totalWidthMM = 0.0D;
+ double colWidthMM = 0.0D;
+ double overlapMM = 0.0D;
+ double coordinatePositionsPerMM = 0.0D;
+ int toColumn = startingColumn;
+ int inset = 0;
+
+ // Calculate how many columns the image will have to
+ // span in order to be presented at the required size.
+ while(totalWidthMM < reqImageWidthMM) {
+ colWidthMM = ConvertImageUnits.widthUnits2Millimetres(
+ (short)(sheet.getColumnWidth(toColumn)));
+ // Note use of the cell border width constant. Testing with an image
+ // declared to fit exactly into one column demonstrated that it's
+ // width was greater than the width of the column the POI returned.
+ // Further, this difference was a constant value that I am assuming
+ // related to the cell's borders. Either way, that difference needs
+ // to be allowed for in this calculation.
+ totalWidthMM += (colWidthMM + ConvertImageUnits.CELL_BORDER_WIDTH_MILLIMETRES);
+ toColumn++;
+ }
+ // De-crement by one the last column value.
+ toColumn--;
+ // Highly unlikely that this will be true but, if the width of a series
+ // of columns is exactly equal to the required width of the image, then
+ // simply build a ClientAnchorDetail object with an inset equal to the
+ // total number of co-ordinate positions available in a column, a
+ // from column co-ordinate (top left hand corner) equal to the value
+ // of the startingColumn parameter and a to column co-ordinate equal
+ // to the toColumn variable.
+ //
+ // Convert both values to ints to perform the test.
+ if((int)totalWidthMM == (int)reqImageWidthMM) {
+ // A problem could occur if the image is sized to fit into one or
+ // more columns. If that occurs, the value in the toColumn variable
+ // will be in error. To overcome this, there are two options, to
+ // ibcrement the toColumn variable's value by one or to pass the
+ // total number of co-ordinate positions to the third paramater
+ // of the ClientAnchorDetail constructor. For no sepcific reason,
+ // the latter option is used below.
+ anchorDetail = new ClientAnchorDetail(startingColumn,
+ toColumn, ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS);
+ }
+ // In this case, the image will overlap part of another column and it is
+ // necessary to calculate just how much - this will become the inset
+ // for the ClientAnchorDetail object.
+ else {
+ // Firstly, claculate how much of the image should overlap into
+ // the next column.
+ overlapMM = reqImageWidthMM - (totalWidthMM - colWidthMM);
+
+ // When the required size is very close indded to the column size,
+ // the calcaulation above can produce a negative value. To prevent
+ // problems occuring in later caculations, this is simply removed
+ // be setting the overlapMM value to zero.
+ if(overlapMM < 0) {
+ overlapMM = 0.0D;
+ }
+
+ // Next, from the columns width, calculate how many co-ordinate
+ // positons there are per millimetre
+ coordinatePositionsPerMM = ConvertImageUnits.TOTAL_COLUMN_COORDINATE_POSITIONS /
+ colWidthMM;
+ // From this figure, determine how many co-ordinat positions to
+ // inset the left hand or bottom edge of the image.
+ inset = (int)(coordinatePositionsPerMM * overlapMM);
+
+ // Now create the ClientAnchorDetail object, setting the from and to
+ // columns and the inset.
+ anchorDetail = new ClientAnchorDetail(startingColumn, toColumn, inset);
+ }
+ return(anchorDetail);
+ }
+
+ /**
+ * If the image is to overlie more than one rows, calculations need to be
+ * performed to determine how many rows and whether the image will
+ * overlie just a part of one row in order to be presented at the
+ * required size.
+ *
+ * @param sheet The sheet that will 'contain' the image.
+ * @param startingRow A primitive int whose value is the index of the row
+ * that contains the cell whose top left hand corner
+ * should be aligned with the top left hand corner of
+ * the image.
+ * @param reqImageHeightMM A primitive double whose value will indicate the
+ * required height of the image in millimetres.
+ * @return An instance of the ClientAnchorDetail class that will contain
+ * the index number of the row containing the cell whose top
+ * left hand corner also defines the top left hand corner of the
+ * image, the index number of the row containing the cell whose top
+ * left hand corner also defines the bottom right hand corner of
+ * the image and an inset that determines how far the bottom edge
+ * can protrude into the next (lower) row - expressed as a specific
+ * number of co-ordinate positions.
+ */
+ private ClientAnchorDetail calculateRowLocation(HSSFSheet sheet,
+ int startingRow, double reqImageHeightMM) {
+ ClientAnchorDetail clientAnchorDetail = null;
+ HSSFRow row = null;
+ double rowHeightMM = 0.0D;
+ double totalRowHeightMM = 0.0D;
+ double overlapMM = 0.0D;
+ double rowCoordinatesPerMM = 0.0D;
+ int toRow = startingRow;
+ int inset = 0;
+
+ // Step through the rows in the sheet and accumulate a total of their
+ // heights.
+ while(totalRowHeightMM < reqImageHeightMM) {
+ row = sheet.getRow(toRow);
+ // Note, if the row does not already exist on the sheet then create
+ // it here.
+ if(row == null) {
+ row = sheet.createRow(toRow);
+ }
+ // Get the row's height in millimetres and add to the running total.
+ rowHeightMM = row.getHeightInPoints() /
+ ConvertImageUnits.POINTS_PER_MILLIMETRE;
+ totalRowHeightMM += rowHeightMM;
+ toRow++;
+ }
+ // Owing to the way the loop above works, the rowNumber will have been
+ // incremented one row too far. Undo that here.
+ toRow--;
+ // Check to see whether the image should occupy an exact number of
+ // rows. If so, build the ClientAnchorDetail record to point
+ // to those rows and with an inset of the total number of co-ordinate
+ // position in the row.
+ //
+ // To overcome problems that can occur with comparing double values for
+ // equality, cast both to int(s) to truncate the value; VERY crude and
+ // I do not really like it!!
+ if((int)totalRowHeightMM == (int)reqImageHeightMM) {
+ clientAnchorDetail = new ClientAnchorDetail(startingRow, toRow,
+ ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS);
+ }
+ else {
+ // Calculate how far the image will project into the next row. Note
+ // that the height of the last row assessed is subtracted from the
+ // total height of all rows assessed so far.
+ overlapMM = reqImageHeightMM - (totalRowHeightMM - rowHeightMM);
+
+ // To prevent an exception being thrown when the required width of
+ // the image is very close indeed to the column size.
+ if(overlapMM < 0) {
+ overlapMM = 0.0D;
+ }
+
+ rowCoordinatesPerMM = ConvertImageUnits.TOTAL_ROW_COORDINATE_POSITIONS /
+ rowHeightMM;
+ inset = (int)(overlapMM * rowCoordinatesPerMM);
+ clientAnchorDetail = new ClientAnchorDetail(startingRow,
+ toRow, inset);
+ }
+ return(clientAnchorDetail);
+ }
+
+ /**
+ * Loads - reads in and converts into an array of byte(s) - an image from
+ * a named file.
+ *
+ * Note: this method should be modified so that the type of the image may
+ * also be passed to it. Currently, it assumes that all images are
+ * JPG/JPEG(s).
+ *
+ * @param imageFilename A String that encapsulates the path to and name
+ * of the file that contains the image which is to be
+ * 'loaded'.
+ * @return An array of type byte that contains the raw data of the named
+ * image.
+ * @throws java.io.FileNotFoundException Thrown if it was not possible to
+ * open the specified file.
+ * @throws java.io.IOException Thrown if reading the file failed or was
+ * interrupted.
+ */
+ private byte[] imageToBytes(String imageFilename) throws IOException {
+ File imageFile = null;
+ FileInputStream fis = null;
+ ByteArrayOutputStream bos = null;
+ int read = 0;
+ try {
+ imageFile = new File(imageFilename);
+ fis = new FileInputStream(imageFile);
+ bos = new ByteArrayOutputStream();
+ while((read = fis.read()) != -1) {
+ bos.write(read);
+ }
+ return(bos.toByteArray());
+ }
+ finally {
+ if(fis != null) {
+ try {
+ fis.close();
+ fis = null;
+ }
+ catch(IOException ioEx) {
+ // Nothing to do here
+ }
+ }
+ }
+ }
+
+ /**
+ * The main entry point to the program. It contains code that demonstrates
+ * one way to use the program.
+ *
+ * Note, the code is not restricted to use on new workbooks only. If an
+ * image is to be inserted into an existing workbook. just open that
+ * workbook, gat a reference to a sheet and pass that;
+ *
+ * AddDimensionedImage addImage = new AddDimensionedImage();
+ *
+ * File file = new File("....... Existing Workbook .......");
+ * FileInputStream fis = new FileInputStream(file);
+ * HSSFWorkbook workbook = new HSSFWorkbook(fis);
+ * HSSFSheet sheet = workbook.getSheetAt(0);
+ * addImage.addImageToSheet("C3", sheet, "image.jpg", 30, 20,
+ * AddDimensionedImage.EXPAND.ROW);
+ *
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ String imageFile = null;
+ String outputFile = null;
+ FileOutputStream fos = null;
+ HSSFSheet sheet = null;
+ try {
+ if(args.length < 2){
+ System.err.println("Usage: AddDimensionedImage imageFile outputFile");
+ return;
+ }
+ imageFile = args[0];
+ outputFile = args[1];
+
+ HSSFWorkbook workbook = new HSSFWorkbook();
+ sheet = workbook.createSheet("Picture Test");
+ new AddDimensionedImage().addImageToSheet("A1", sheet,
+ imageFile, 125, 125,
+ AddDimensionedImage.EXPAND_ROW_AND_COLUMN);
+ fos = new FileOutputStream(outputFile);
+ workbook.write(fos);
+ workbook.close();
+ }
+ catch(FileNotFoundException fnfEx) {
+ System.out.println("Caught an: " + fnfEx.getClass().getName());
+ System.out.println("Message: " + fnfEx.getMessage());
+ System.out.println("Stacktrace follows...........");
+ fnfEx.printStackTrace(System.out);
+ }
+ catch(IOException ioEx) {
+ System.out.println("Caught an: " + ioEx.getClass().getName());
+ System.out.println("Message: " + ioEx.getMessage());
+ System.out.println("Stacktrace follows...........");
+ ioEx.printStackTrace(System.out);
+ }
+ finally {
+ try {
+ if(fos != null) {
+ fos.close();
+ fos = null;
+ }
+ } catch(IOException ioEx) {
+ // I G N O R E
+ }
+ }
+ }
+
+ /**
+ * The HSSFClientAnchor class accepts eight parameters. In order, these are;
+ *
+ * * How far the left hand edge of the image is inset from the left hand
+ * edge of the cell
+ * * How far the top edge of the image is inset from the top of the cell
+ * * How far the right hand edge of the image is inset from the left
+ * hand edge of the cell
+ * * How far the bottom edge of the image is inset from the top of the
+ * cell.
+ * * Together, parameters five and six determine the column and row
+ * co-ordinates of the cell whose top left hand corner will be aligned
+ * with the image's top left hand corner.
+ * * Together, parameter seven and eight determine the column and row
+ * co-ordinates of the cell whose top left hand corner will be aligned
+ * with the images bottom right hand corner.
+ *
+ * An instance of the ClientAnchorDetail class provides three of the eight
+ * parameters, one of the co-ordinates for the images top left hand corner,
+ * one of the co-ordinates for the images bottom right hand corner and
+ * either how far the image should be inset from the top or the left hand
+ * edge of the cell.
+ *
+ * @author Mark Beardsley [mas at apache.org]
+ * @version 1.00 5th August 2009.
+ */
+ public class ClientAnchorDetail {
+
+ public int fromIndex = 0;
+ public int toIndex = 0;
+ public int inset = 0;
+
+ /**
+ * Create a new instance of the ClientAnchorDetail class using the
+ * following parameters.
+ *
+ * @param fromIndex A primitive int that contains one of the
+ * co-ordinates (row or column index) for the top left
+ * hand corner of the image.
+ * @param toIndex A primitive int that contains one of the
+ * co-ordinates (row or column index) for the bottom
+ * right hand corner of the image.
+ * @param inset A primitive int that contains a value which indicates
+ * how far the image should be inset from the top or the
+ * left hand edge of a cell.
+ */
+ public ClientAnchorDetail(int fromIndex, int toIndex, int inset) {
+ this.fromIndex = fromIndex;
+ this.toIndex = toIndex;
+ this.inset = inset;
+ }
+
+ /**
+ * Get one of the number of the column or row that contains the cell
+ * whose top left hand corner will be aligned with the top left hand
+ * corner of the image.
+ *
+ * @return The value - row or column index - for one of the co-ordinates
+ * of the top left hand corner of the image.
+ */
+ public int getFromIndex() {
+ return(this.fromIndex);
+ }
+
+ /**
+ * Get one of the number of the column or row that contains the cell
+ * whose top left hand corner will be aligned with the bottom righ hand
+ * corner of the image.
+ *
+ * @return The value - row or column index - for one of the co-ordinates
+ * of the bottom right hand corner of the image.
+ */
+ public int getToIndex() {
+ return(this.toIndex);
+ }
+
+ /**
+ * Get the image's offset from the edge of a cell.
+ *
+ * @return How far either the right hand or bottom edge of the image is
+ * inset from the left hand or top edge of a cell.
+ */
+ public int getInset() {
+ return(this.inset);
+ }
+ }
+
+ /**
+ * Utility methods used to convert Excel's character based column and row
+ * size measurements into pixels and/or millimetres. The class also contains
+ * various constants that are required in other calculations.
+ *
+ * @author xio[darjino@hotmail.com]
+ * @version 1.01 30th July 2009.
+ * Added by Mark Beardsley [msb at apache.org].
+ * Additional constants.
+ * widthUnits2Millimetres() and millimetres2Units() methods.
+ */
+ public static class ConvertImageUnits {
+
+ // Each cell conatins a fixed number of co-ordinate points; this number
+ // does not vary with row height or column width or with font. These two
+ // constants are defined below.
+ public static final int TOTAL_COLUMN_COORDINATE_POSITIONS = 1023; // MB
+ public static final int TOTAL_ROW_COORDINATE_POSITIONS = 255; // MB
+ // The resoultion of an image can be expressed as a specific number
+ // of pixels per inch. Displays and printers differ but 96 pixels per
+ // inch is an acceptable standard to beging with.
+ public static final int PIXELS_PER_INCH = 96; // MB
+ // Cnstants that defines how many pixels and points there are in a
+ // millimetre. These values are required for the conversion algorithm.
+ public static final double PIXELS_PER_MILLIMETRES = 3.78; // MB
+ public static final double POINTS_PER_MILLIMETRE = 2.83; // MB
+ // The column width returned by HSSF and the width of a picture when
+ // positioned to exactly cover one cell are different by almost exactly
+ // 2mm - give or take rounding errors. This constant allows that
+ // additional amount to be accounted for when calculating how many
+ // celles the image ought to overlie.
+ public static final double CELL_BORDER_WIDTH_MILLIMETRES = 2.0D; // MB
+ public static final short EXCEL_COLUMN_WIDTH_FACTOR = 256;
+ public static final int UNIT_OFFSET_LENGTH = 7;
+ public static final int[] UNIT_OFFSET_MAP = new int[]
+ { 0, 36, 73, 109, 146, 182, 219 };
+
+ /**
+ * pixel units to excel width units(units of 1/256th of a character width)
+ * @param pxs
+ * @return
+ */
+ public static short pixel2WidthUnits(int pxs) {
+ short widthUnits = (short) (EXCEL_COLUMN_WIDTH_FACTOR *
+ (pxs / UNIT_OFFSET_LENGTH));
+ widthUnits += UNIT_OFFSET_MAP[(pxs % UNIT_OFFSET_LENGTH)];
+ return widthUnits;
+ }
+
+ /**
+ * excel width units(units of 1/256th of a character width) to pixel
+ * units.
+ *
+ * @param widthUnits
+ * @return
+ */
+ public static int widthUnits2Pixel(short widthUnits) {
+ int pixels = (widthUnits / EXCEL_COLUMN_WIDTH_FACTOR)
+ * UNIT_OFFSET_LENGTH;
+ int offsetWidthUnits = widthUnits % EXCEL_COLUMN_WIDTH_FACTOR;
+ pixels += Math.round(offsetWidthUnits /
+ ((float) EXCEL_COLUMN_WIDTH_FACTOR / UNIT_OFFSET_LENGTH));
+ return pixels;
+ }
+
+ /**
+ * Convert Excel's width units into millimetres.
+ *
+ * @param widthUnits The width of the column or the height of the
+ * row in Excel's units.
+ * @return A primitive double that contains the columns width or rows
+ * height in millimetres.
+ */
+ public static double widthUnits2Millimetres(short widthUnits) {
+ return(ConvertImageUnits.widthUnits2Pixel(widthUnits) /
+ ConvertImageUnits.PIXELS_PER_MILLIMETRES);
+ }
+
+ /**
+ * Convert into millimetres Excel's width units..
+ *
+ * @param millimetres A primitive double that contains the columns
+ * width or rows height in millimetres.
+ * @return A primitive int that contains the columns width or rows
+ * height in Excel's units.
+ */
+ public static int millimetres2WidthUnits(double millimetres) {
+ return(ConvertImageUnits.pixel2WidthUnits((int)(millimetres *
+ ConvertImageUnits.PIXELS_PER_MILLIMETRES)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Alignment.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Alignment.java
new file mode 100644
index 000000000..43fb75db1
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Alignment.java
@@ -0,0 +1,73 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+
+/**
+ * Shows how various alignment options work.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class Alignment {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+ HSSFRow row = sheet.createRow(2);
+ createCell(wb, row, 0, HorizontalAlignment.CENTER);
+ createCell(wb, row, 1, HorizontalAlignment.CENTER_SELECTION);
+ createCell(wb, row, 2, HorizontalAlignment.FILL);
+ createCell(wb, row, 3, HorizontalAlignment.GENERAL);
+ createCell(wb, row, 4, HorizontalAlignment.JUSTIFY);
+ createCell(wb, row, 5, HorizontalAlignment.LEFT);
+ createCell(wb, row, 6, HorizontalAlignment.RIGHT);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+
+ wb.close();
+ }
+
+ /**
+ * Creates a cell and aligns it a certain way.
+ *
+ * @param wb the workbook
+ * @param row the row to create the cell in
+ * @param column the column number to create the cell in
+ * @param align the alignment for the cell.
+ */
+ private static void createCell(HSSFWorkbook wb, HSSFRow row, int column, HorizontalAlignment align) {
+ HSSFCell cell = row.createCell(column);
+ cell.setCellValue("Align It");
+ HSSFCellStyle cellStyle = wb.createCellStyle();
+ cellStyle.setAlignment(align);
+ cell.setCellStyle(cellStyle);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/BigExample.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/BigExample.java
new file mode 100644
index 000000000..cf3775200
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/BigExample.java
@@ -0,0 +1,172 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Font;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Demonstrates many features of the user API at once. Used in the HOW-TO guide.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ * @author Andrew Oliver (acoliver at apache.org)
+ */
+public class BigExample {
+ public static void main(String[] args) throws IOException {
+ int rownum;
+
+ // create a new file
+ FileOutputStream out = new FileOutputStream("workbook.xls");
+ // create a new workbook
+ HSSFWorkbook wb = new HSSFWorkbook();
+ // create a new sheet
+ HSSFSheet s = wb.createSheet();
+ // declare a row object reference
+ HSSFRow r = null;
+ // declare a cell object reference
+ HSSFCell c = null;
+ // create 3 cell styles
+ HSSFCellStyle cs = wb.createCellStyle();
+ HSSFCellStyle cs2 = wb.createCellStyle();
+ HSSFCellStyle cs3 = wb.createCellStyle();
+ // create 2 fonts objects
+ HSSFFont f = wb.createFont();
+ HSSFFont f2 = wb.createFont();
+
+ //set font 1 to 12 point type
+ f.setFontHeightInPoints((short) 12);
+ //make it red
+ f.setColor(HSSFColor.RED.index);
+ // make it bold
+ //arial is the default font
+ f.setBoldweight(Font.BOLDWEIGHT_BOLD);
+
+ //set font 2 to 10 point type
+ f2.setFontHeightInPoints((short) 10);
+ //make it the color at palette index 0xf (white)
+ f2.setColor(HSSFColor.WHITE.index);
+ //make it bold
+ f2.setBoldweight(Font.BOLDWEIGHT_BOLD);
+
+ //set cell stlye
+ cs.setFont(f);
+ //set the cell format see HSSFDataFromat for a full list
+ cs.setDataFormat(HSSFDataFormat.getBuiltinFormat("($#,##0_);[Red]($#,##0)"));
+
+ //set a thin border
+ cs2.setBorderBottom(CellStyle.BORDER_THIN);
+ //fill w fg fill color
+ cs2.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
+ // set foreground fill to red
+ cs2.setFillForegroundColor(HSSFColor.RED.index);
+
+ // set the font
+ cs2.setFont(f2);
+
+ // set the sheet name to HSSF Test
+ wb.setSheetName(0, "HSSF Test");
+ // create a sheet with 300 rows (0-299)
+ for (rownum = 0; rownum < 300; rownum++)
+ {
+ // create a row
+ r = s.createRow(rownum);
+ // on every other row
+ if ((rownum % 2) == 0)
+ {
+ // make the row height bigger (in twips - 1/20 of a point)
+ r.setHeight((short) 0x249);
+ }
+
+ //r.setRowNum(( short ) rownum);
+ // create 50 cells (0-49) (the += 2 becomes apparent later
+ for (int cellnum = 0; cellnum < 50; cellnum += 2)
+ {
+ // create a numeric cell
+ c = r.createCell(cellnum);
+ // do some goofy math to demonstrate decimals
+ c.setCellValue(rownum * 10000 + cellnum
+ + (((double) rownum / 1000)
+ + ((double) cellnum / 10000)));
+
+ // on every other row
+ if ((rownum % 2) == 0)
+ {
+ // set this cell to the first cell style we defined
+ c.setCellStyle(cs);
+ }
+
+ // create a string cell (see why += 2 in the
+ c = r.createCell(cellnum + 1);
+
+ // set the cell's string value to "TEST"
+ c.setCellValue("TEST");
+ // make this column a bit wider
+ s.setColumnWidth(cellnum + 1, (int)((50 * 8) / ((double) 1 / 20)));
+
+ // on every other row
+ if ((rownum % 2) == 0)
+ {
+ // set this to the white on red cell style
+ // we defined above
+ c.setCellStyle(cs2);
+ }
+
+ }
+ }
+
+ //draw a thick black border on the row at the bottom using BLANKS
+ // advance 2 rows
+ rownum++;
+ rownum++;
+
+ r = s.createRow(rownum);
+
+ // define the third style to be the default
+ // except with a thick black border at the bottom
+ cs3.setBorderBottom(CellStyle.BORDER_THICK);
+
+ //create 50 cells
+ for (int cellnum =0; cellnum < 50; cellnum++) {
+ //create a blank type cell (no value)
+ c = r.createCell(cellnum);
+ // set it to the thick black border style
+ c.setCellStyle(cs3);
+ }
+
+ //end draw thick black border
+
+
+ // demonstrate adding/naming and deleting a sheet
+ // create a sheet, set its title then delete it
+ wb.createSheet();
+ wb.setSheetName(1, "DeletedSheet");
+ wb.removeSheetAt(1);
+ //end deleted sheet
+
+ // write the workbook to the output stream
+ // close our file (don't blow out our file handles
+ wb.write(out);
+ out.close();
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Borders.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Borders.java
new file mode 100644
index 000000000..d29635004
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Borders.java
@@ -0,0 +1,60 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.HSSFColor;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Demonstrates how to create borders around cells.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class Borders {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ HSSFRow row = sheet.createRow(1);
+
+ // Create a cell and put a value in it.
+ HSSFCell cell = row.createCell(1);
+ cell.setCellValue(4);
+
+ // Style the cell with borders all around.
+ HSSFCellStyle style = wb.createCellStyle();
+ style.setBorderBottom(HSSFCellStyle.BORDER_THIN);
+ style.setBottomBorderColor(HSSFColor.BLACK.index);
+ style.setBorderLeft(HSSFCellStyle.BORDER_THIN);
+ style.setLeftBorderColor(HSSFColor.GREEN.index);
+ style.setBorderRight(HSSFCellStyle.BORDER_THIN);
+ style.setRightBorderColor(HSSFColor.BLUE.index);
+ style.setBorderTop(HSSFCellStyle.BORDER_MEDIUM_DASHED);
+ style.setTopBorderColor(HSSFColor.ORANGE.index);
+ cell.setCellStyle(style);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java
new file mode 100644
index 000000000..c3a213d58
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java
@@ -0,0 +1,98 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.HSSFColor;
+
+import java.io.*;
+
+/**
+ * Demonstrates how to work with excel cell comments.
+ *
+ *
+ * Excel comment is a kind of a text shape,
+ * so inserting a comment is very similar to placing a text box in a worksheet
+ *
+ *
+ * @author Yegor Kozlov
+ */
+public class CellComments {
+
+ public static void main(String[] args) throws IOException {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("Cell comments in POI HSSF");
+
+ // Create the drawing patriarch. This is the top level container for all shapes including cell comments.
+ HSSFPatriarch patr = sheet.createDrawingPatriarch();
+
+ //create a cell in row 3
+ HSSFCell cell1 = sheet.createRow(3).createCell(1);
+ cell1.setCellValue(new HSSFRichTextString("Hello, World"));
+
+ //anchor defines size and position of the comment in worksheet
+ HSSFComment comment1 = patr.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short)4, 2, (short) 6, 5));
+
+ // set text in the comment
+ comment1.setString(new HSSFRichTextString("We can set comments in POI"));
+
+ //set comment author.
+ //you can see it in the status bar when moving mouse over the commented cell
+ comment1.setAuthor("Apache Software Foundation");
+
+ // The first way to assign comment to a cell is via HSSFCell.setCellComment method
+ cell1.setCellComment(comment1);
+
+ //create another cell in row 6
+ HSSFCell cell2 = sheet.createRow(6).createCell(1);
+ cell2.setCellValue(36.6);
+
+
+ HSSFComment comment2 = patr.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short)4, 8, (short) 6, 11));
+ //modify background color of the comment
+ comment2.setFillColor(204, 236, 255);
+
+ HSSFRichTextString string = new HSSFRichTextString("Normal body temperature");
+
+ //apply custom font to the text in the comment
+ HSSFFont font = wb.createFont();
+ font.setFontName("Arial");
+ font.setFontHeightInPoints((short)10);
+ font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
+ font.setColor(HSSFColor.RED.index);
+ string.applyFont(font);
+
+ comment2.setString(string);
+ comment2.setVisible(true); //by default comments are hidden. This one is always visible.
+
+ comment2.setAuthor("Bill Gates");
+
+ /**
+ * The second way to assign comment to a cell is to implicitly specify its row and column.
+ * Note, it is possible to set row and column of a non-existing cell.
+ * It works, the comment is visible.
+ */
+ comment2.setRow(6);
+ comment2.setColumn(1);
+
+ FileOutputStream out = new FileOutputStream("poi_comment.xls");
+ wb.write(out);
+ out.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellTypes.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellTypes.java
new file mode 100644
index 000000000..3e31d11f2
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellTypes.java
@@ -0,0 +1,45 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Date;
+
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.CellType;
+
+public class CellTypes {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+ HSSFRow row = sheet.createRow(2);
+ row.createCell(0).setCellValue(1.1);
+ row.createCell(1).setCellValue(new Date());
+ row.createCell(2).setCellValue("a string");
+ row.createCell(3).setCellValue(true);
+ row.createCell(4).setCellType(CellType.ERROR);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CreateCells.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CreateCells.java
new file mode 100644
index 000000000..d2141b050
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CreateCells.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Illustrates how to create cell values.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class CreateCells {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ HSSFRow row = sheet.createRow(0);
+ // Create a cell and put a value in it.
+ HSSFCell cell = row.createCell(0);
+ cell.setCellValue(1);
+
+ // Or do it on one line.
+ row.createCell(1).setCellValue(1.2);
+ row.createCell(2).setCellValue("This is a string");
+ row.createCell(3).setCellValue(true);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CreateDateCells.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CreateDateCells.java
new file mode 100644
index 000000000..746fd536b
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/CreateDateCells.java
@@ -0,0 +1,58 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * An example on how to cells with dates. The important thing to note
+ * about dates is that they are really normal numeric cells that are
+ * formatted specially.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class CreateDateCells {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ HSSFRow row = sheet.createRow(0);
+
+ // Create a cell and put a date value in it. The first cell is not styled as a date.
+ HSSFCell cell = row.createCell(0);
+ cell.setCellValue(new Date());
+
+ // we style the second cell as a date (and time). It is important to create a new cell style from the workbook
+ // otherwise you can end up modifying the built in style and effecting not only this cell but other cells.
+ HSSFCellStyle cellStyle = wb.createCellStyle();
+ cellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy h:mm"));
+ cell = row.createCell(1);
+ cell.setCellValue(new Date());
+ cell.setCellStyle(cellStyle);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/EmeddedObjects.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/EmeddedObjects.java
new file mode 100644
index 000000000..9af41063e
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/EmeddedObjects.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileInputStream;
+import java.util.Iterator;
+
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl;
+import org.apache.poi.hssf.usermodel.HSSFObjectData;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hwpf.HWPFDocument;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+/**
+ * Demonstrates how you can extract embedded data from a .xls file
+ */
+public class EmeddedObjects {
+ @SuppressWarnings("unused")
+ public static void main(String[] args) throws Exception {
+ POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(args[0]));
+ HSSFWorkbook workbook = new HSSFWorkbook(fs);
+ for (HSSFObjectData obj : workbook.getAllEmbeddedObjects()) {
+ //the OLE2 Class Name of the object
+ String oleName = obj.getOLE2ClassName();
+ if (oleName.equals("Worksheet")) {
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ HSSFWorkbook embeddedWorkbook = new HSSFWorkbook(dn, fs, false);
+ //System.out.println(entry.getName() + ": " + embeddedWorkbook.getNumberOfSheets());
+ embeddedWorkbook.close();
+ } else if (oleName.equals("Document")) {
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ HWPFDocument embeddedWordDocument = new HWPFDocument(dn);
+ //System.out.println(entry.getName() + ": " + embeddedWordDocument.getRange().text());
+ } else if (oleName.equals("Presentation")) {
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ HSLFSlideShow embeddedPowerPointDocument = new HSLFSlideShow(new HSLFSlideShowImpl(dn));
+ //System.out.println(entry.getName() + ": " + embeddedPowerPointDocument.getSlides().length);
+ } else {
+ if(obj.hasDirectoryEntry()){
+ // The DirectoryEntry is a DocumentNode. Examine its entries to find out what it is
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ for (Iterator entries = dn.getEntries(); entries.hasNext();) {
+ Entry entry = entries.next();
+ //System.out.println(oleName + "." + entry.getName());
+ }
+ } else {
+ // There is no DirectoryEntry
+ // Recover the object's data from the HSSFObjectData instance.
+ byte[] objectData = obj.getObjectData();
+ }
+ }
+ }
+ workbook.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/EventExample.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/EventExample.java
new file mode 100644
index 000000000..7dd5ee3ca
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/EventExample.java
@@ -0,0 +1,118 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
+import org.apache.poi.hssf.eventusermodel.HSSFListener;
+import org.apache.poi.hssf.eventusermodel.HSSFRequest;
+import org.apache.poi.hssf.record.*;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This example shows how to use the event API for reading a file.
+ */
+public class EventExample
+ implements HSSFListener
+{
+ private SSTRecord sstrec;
+
+ /**
+ * This method listens for incoming records and handles them as required.
+ * @param record The record that was found while reading.
+ */
+ public void processRecord(Record record)
+ {
+ switch (record.getSid())
+ {
+ // the BOFRecord can represent either the beginning of a sheet or the workbook
+ case BOFRecord.sid:
+ BOFRecord bof = (BOFRecord) record;
+ if (bof.getType() == BOFRecord.TYPE_WORKBOOK)
+ {
+ System.out.println("Encountered workbook");
+ // assigned to the class level member
+ } else if (bof.getType() == BOFRecord.TYPE_WORKSHEET)
+ {
+ System.out.println("Encountered sheet reference");
+ }
+ break;
+ case BoundSheetRecord.sid:
+ BoundSheetRecord bsr = (BoundSheetRecord) record;
+ System.out.println("New sheet named: " + bsr.getSheetname());
+ break;
+ case RowRecord.sid:
+ RowRecord rowrec = (RowRecord) record;
+ System.out.println("Row found, first column at "
+ + rowrec.getFirstCol() + " last column at " + rowrec.getLastCol());
+ break;
+ case NumberRecord.sid:
+ NumberRecord numrec = (NumberRecord) record;
+ System.out.println("Cell found with value " + numrec.getValue()
+ + " at row " + numrec.getRow() + " and column " + numrec.getColumn());
+ break;
+ // SSTRecords store a array of unique strings used in Excel.
+ case SSTRecord.sid:
+ sstrec = (SSTRecord) record;
+ for (int k = 0; k < sstrec.getNumUniqueStrings(); k++)
+ {
+ System.out.println("String table value " + k + " = " + sstrec.getString(k));
+ }
+ break;
+ case LabelSSTRecord.sid:
+ LabelSSTRecord lrec = (LabelSSTRecord) record;
+ System.out.println("String cell found with value "
+ + sstrec.getString(lrec.getSSTIndex()));
+ break;
+ }
+ }
+
+ /**
+ * Read an excel file and spit out what we find.
+ *
+ * @param args Expect one argument that is the file to read.
+ * @throws IOException When there is an error processing the file.
+ */
+ public static void main(String[] args) throws IOException
+ {
+ // create a new file input stream with the input file specified
+ // at the command line
+ FileInputStream fin = new FileInputStream(args[0]);
+ // create a new org.apache.poi.poifs.filesystem.Filesystem
+ POIFSFileSystem poifs = new POIFSFileSystem(fin);
+ // get the Workbook (excel part) stream in a InputStream
+ InputStream din = poifs.createDocumentInputStream("Workbook");
+ // construct out HSSFRequest object
+ HSSFRequest req = new HSSFRequest();
+ // lazy listen for ALL records with the listener shown above
+ req.addListenerForAllRecords(new EventExample());
+ // create our event factory
+ HSSFEventFactory factory = new HSSFEventFactory();
+ // process our events based on the document input stream
+ factory.processEvents(req, din);
+ // once all the events are processed close our file input stream
+ fin.close();
+ // and our document input stream (don't want to leak these!)
+ din.close();
+ System.out.println("done.");
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/FrillsAndFills.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/FrillsAndFills.java
new file mode 100644
index 000000000..02b7cb3e3
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/FrillsAndFills.java
@@ -0,0 +1,60 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.HSSFColor;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Shows how to use various fills.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class FrillsAndFills {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ HSSFRow row = sheet.createRow(1);
+
+ // Aqua background
+ HSSFCellStyle style = wb.createCellStyle();
+ style.setFillBackgroundColor(HSSFColor.AQUA.index);
+ style.setFillPattern(HSSFCellStyle.BIG_SPOTS);
+ HSSFCell cell = row.createCell(1);
+ cell.setCellValue("X");
+ cell.setCellStyle(style);
+
+ // Orange "foreground", foreground being the fill foreground not the font color.
+ style = wb.createCellStyle();
+ style.setFillForegroundColor(HSSFColor.ORANGE.index);
+ style.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
+ cell = row.createCell(2);
+ cell.setCellValue("X");
+ cell.setCellStyle(style);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HSSFReadWrite.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HSSFReadWrite.java
new file mode 100644
index 000000000..2f67f7f0e
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HSSFReadWrite.java
@@ -0,0 +1,256 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFDataFormat;
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFRichTextString;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+/**
+ * File for HSSF testing/examples
+ *
+ * THIS IS NOT THE MAIN HSSF FILE!! This is a utility for testing functionality.
+ * It does contain sample API usage that may be educational to regular API
+ * users.
+ */
+public final class HSSFReadWrite {
+
+ /**
+ * creates an {@link HSSFWorkbook} the specified OS filename.
+ */
+ private static HSSFWorkbook readFile(String filename) throws IOException {
+ FileInputStream fis = new FileInputStream(filename);
+ try {
+ return new HSSFWorkbook(fis);
+ } finally {
+ fis.close();
+ }
+ }
+
+ /**
+ * given a filename this outputs a sample sheet with just a set of
+ * rows/cells.
+ */
+ private static void testCreateSampleSheet(String outputFilename) throws IOException {
+ int rownum;
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet s = wb.createSheet();
+ HSSFCellStyle cs = wb.createCellStyle();
+ HSSFCellStyle cs2 = wb.createCellStyle();
+ HSSFCellStyle cs3 = wb.createCellStyle();
+ HSSFFont f = wb.createFont();
+ HSSFFont f2 = wb.createFont();
+
+ f.setFontHeightInPoints((short) 12);
+ f.setColor((short) 0xA);
+ f.setBold(true);
+ f2.setFontHeightInPoints((short) 10);
+ f2.setColor((short) 0xf);
+ f2.setBold(true);
+ cs.setFont(f);
+ cs.setDataFormat(HSSFDataFormat.getBuiltinFormat("($#,##0_);[Red]($#,##0)"));
+ cs2.setBorderBottom(BorderStyle.THIN);
+ cs2.setFillPattern((short) 1); // fill w fg
+ cs2.setFillForegroundColor((short) 0xA);
+ cs2.setFont(f2);
+ wb.setSheetName(0, "HSSF Test");
+ for (rownum = 0; rownum < 300; rownum++) {
+ HSSFRow r = s.createRow(rownum);
+ if ((rownum % 2) == 0) {
+ r.setHeight((short) 0x249);
+ }
+
+ for (int cellnum = 0; cellnum < 50; cellnum += 2) {
+ HSSFCell c = r.createCell(cellnum);
+ c.setCellValue(rownum * 10000 + cellnum
+ + (((double) rownum / 1000) + ((double) cellnum / 10000)));
+ if ((rownum % 2) == 0) {
+ c.setCellStyle(cs);
+ }
+ c = r.createCell(cellnum + 1);
+ c.setCellValue(new HSSFRichTextString("TEST"));
+ // 50 characters divided by 1/20th of a point
+ s.setColumnWidth(cellnum + 1, (int) (50 * 8 / 0.05));
+ if ((rownum % 2) == 0) {
+ c.setCellStyle(cs2);
+ }
+ }
+ }
+
+ // draw a thick black border on the row at the bottom using BLANKS
+ rownum++;
+ rownum++;
+ HSSFRow r = s.createRow(rownum);
+ cs3.setBorderBottom(BorderStyle.THICK);
+ for (int cellnum = 0; cellnum < 50; cellnum++) {
+ HSSFCell c = r.createCell(cellnum);
+ c.setCellStyle(cs3);
+ }
+ s.addMergedRegion(new CellRangeAddress(0, 3, 0, 3));
+ s.addMergedRegion(new CellRangeAddress(100, 110, 100, 110));
+
+ // end draw thick black border
+ // create a sheet, set its title then delete it
+ wb.createSheet();
+ wb.setSheetName(1, "DeletedSheet");
+ wb.removeSheetAt(1);
+
+ // end deleted sheet
+ FileOutputStream out = new FileOutputStream(outputFilename);
+ try {
+ wb.write(out);
+ } finally {
+ out.close();
+ }
+
+ wb.close();
+ }
+
+ /**
+ * Method main
+ *
+ * Given 1 argument takes that as the filename, inputs it and dumps the
+ * cell values/types out to sys.out.
+ *
+ * given 2 arguments where the second argument is the word "write" and the
+ * first is the filename - writes out a sample (test) spreadsheet
+ * see {@link HSSFReadWrite#testCreateSampleSheet(String)}.
+ *
+ * given 2 arguments where the first is an input filename and the second
+ * an output filename (not write), attempts to fully read in the
+ * spreadsheet and fully write it out.
+ *
+ * given 3 arguments where the first is an input filename and the second an
+ * output filename (not write) and the third is "modify1", attempts to read in the
+ * spreadsheet, deletes rows 0-24, 74-99. Changes cell at row 39, col 3 to
+ * "MODIFIED CELL" then writes it out. Hence this is "modify test 1". If you
+ * take the output from the write test, you'll have a valid scenario.
+ */
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("At least one argument expected");
+ return;
+ }
+
+ String fileName = args[0];
+ try {
+ if (args.length < 2) {
+
+ HSSFWorkbook wb = HSSFReadWrite.readFile(fileName);
+
+ System.out.println("Data dump:\n");
+
+ for (int k = 0; k < wb.getNumberOfSheets(); k++) {
+ HSSFSheet sheet = wb.getSheetAt(k);
+ int rows = sheet.getPhysicalNumberOfRows();
+ System.out.println("Sheet " + k + " \"" + wb.getSheetName(k) + "\" has " + rows
+ + " row(s).");
+ for (int r = 0; r < rows; r++) {
+ HSSFRow row = sheet.getRow(r);
+ if (row == null) {
+ continue;
+ }
+
+ int cells = row.getPhysicalNumberOfCells();
+ System.out.println("\nROW " + row.getRowNum() + " has " + cells
+ + " cell(s).");
+ for (int c = 0; c < cells; c++) {
+ HSSFCell cell = row.getCell(c);
+ String value = null;
+
+ switch (cell.getCellTypeEnum()) {
+
+ case FORMULA:
+ value = "FORMULA value=" + cell.getCellFormula();
+ break;
+
+ case NUMERIC:
+ value = "NUMERIC value=" + cell.getNumericCellValue();
+ break;
+
+ case STRING:
+ value = "STRING value=" + cell.getStringCellValue();
+ break;
+
+ default:
+ }
+ System.out.println("CELL col=" + cell.getColumnIndex() + " VALUE="
+ + value);
+ }
+ }
+ }
+ wb.close();
+ } else if (args.length == 2) {
+ if (args[1].toLowerCase(Locale.ROOT).equals("write")) {
+ System.out.println("Write mode");
+ long time = System.currentTimeMillis();
+ HSSFReadWrite.testCreateSampleSheet(fileName);
+
+ System.out.println("" + (System.currentTimeMillis() - time)
+ + " ms generation time");
+ } else {
+ System.out.println("readwrite test");
+ HSSFWorkbook wb = HSSFReadWrite.readFile(fileName);
+ FileOutputStream stream = new FileOutputStream(args[1]);
+
+ wb.write(stream);
+ stream.close();
+ wb.close();
+ }
+ } else if (args.length == 3 && args[2].toLowerCase(Locale.ROOT).equals("modify1")) {
+ // delete row 0-24, row 74 - 99 && change cell 3 on row 39 to string "MODIFIED CELL!!"
+
+ HSSFWorkbook wb = HSSFReadWrite.readFile(fileName);
+ FileOutputStream stream = new FileOutputStream(args[1]);
+ HSSFSheet sheet = wb.getSheetAt(0);
+
+ for (int k = 0; k < 25; k++) {
+ HSSFRow row = sheet.getRow(k);
+
+ sheet.removeRow(row);
+ }
+ for (int k = 74; k < 100; k++) {
+ HSSFRow row = sheet.getRow(k);
+
+ sheet.removeRow(row);
+ }
+ HSSFRow row = sheet.getRow(39);
+ HSSFCell cell = row.getCell(3);
+ cell.setCellValue("MODIFIED CELL!!!!!");
+
+ wb.write(stream);
+ stream.close();
+ wb.close();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HyperlinkFormula.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HyperlinkFormula.java
new file mode 100644
index 000000000..148b2a58b
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/HyperlinkFormula.java
@@ -0,0 +1,48 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.CellType;
+
+/**
+ * Test if hyperlink formula, with url that got more than 127 characters, works
+ *
+ * @author Bernard Chesnoy
+ */
+public class HyperlinkFormula {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+ HSSFRow row = sheet.createRow(0);
+
+ HSSFCell cell = row.createCell(0);
+ cell.setCellType(CellType.FORMULA);
+ cell.setCellFormula("HYPERLINK(\"http://127.0.0.1:8080/toto/truc/index.html?test=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", \"test\")");
+
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java
new file mode 100644
index 000000000..c1ccad910
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java
@@ -0,0 +1,97 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.common.usermodel.HyperlinkType;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFCreationHelper;
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFHyperlink;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+
+/**
+ * Demonstrates how to create hyperlinks.
+ *
+ * @author Yegor Kozlov (yegor at apach.org)
+ */
+public class Hyperlinks {
+
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFCreationHelper helper = wb.getCreationHelper();
+
+ //cell style for hyperlinks
+ //by default hyperlinks are blue and underlined
+ HSSFCellStyle hlink_style = wb.createCellStyle();
+ HSSFFont hlink_font = wb.createFont();
+ hlink_font.setUnderline(HSSFFont.U_SINGLE);
+ hlink_font.setColor(HSSFColor.BLUE.index);
+ hlink_style.setFont(hlink_font);
+
+ HSSFCell cell;
+ HSSFSheet sheet = wb.createSheet("Hyperlinks");
+
+ //URL
+ cell = sheet.createRow(0).createCell(0);
+ cell.setCellValue("URL Link");
+ HSSFHyperlink link = helper.createHyperlink(HyperlinkType.URL);
+ link.setAddress("http://poi.apache.org/");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //link to a file in the current directory
+ cell = sheet.createRow(1).createCell(0);
+ cell.setCellValue("File Link");
+ link = helper.createHyperlink(HyperlinkType.FILE);
+ link.setAddress("link1.xls");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //e-mail link
+ cell = sheet.createRow(2).createCell(0);
+ cell.setCellValue("Email Link");
+ link = helper.createHyperlink(HyperlinkType.EMAIL);
+ //note, if subject contains white spaces, make sure they are url-encoded
+ link.setAddress("mailto:poi@apache.org?subject=Hyperlinks");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //link to a place in this workbook
+
+ //create a target sheet and cell
+ HSSFSheet sheet2 = wb.createSheet("Target Sheet");
+ sheet2.createRow(0).createCell(0).setCellValue("Target Cell");
+
+ cell = sheet.createRow(3).createCell(0);
+ cell.setCellValue("Worksheet Link");
+ link = helper.createHyperlink(HyperlinkType.DOCUMENT);
+ link.setAddress("'Target Sheet'!A1");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ FileOutputStream out = new FileOutputStream("hssf-links.xls");
+ wb.write(out);
+ out.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/InCellLists.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/InCellLists.java
new file mode 100644
index 000000000..a666c6ab7
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/InCellLists.java
@@ -0,0 +1,565 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFDataFormat;
+import org.apache.poi.hssf.usermodel.HSSFRichTextString;
+
+/**
+ * This class contains code that demonstrates how to insert plain, numbered
+ * and bulleted lists into an Excel spreadsheet cell.
+ *
+ * Look at the code contained in the demonstrateMethodCalls() method. It calls
+ * other methods that create plain, numbered and bulleted single and
+ * multi-level lists. The demonstrateMethodCalls() method appears at the top
+ * of the class definition.
+ *
+ * Though different methods are provided to construct single and multi-level
+ * plain, numbered and bulleted lists, close examination will reveal that they
+ * are not strictly necessary. If the inputs to the listInCell() and
+ * multilLevelListInCell() methods are constructed to include the bullet
+ * character or the item numbers then these methods alone may be sufficient.
+ *
+ * @author Mark Beardsley [msb at apache.org]
+ */
+public class InCellLists {
+
+ // This character looks like a solid, black, loser case letter 'o'
+ // positioned up from the base line of the text.
+ private static final char BULLET_CHARACTER = '\u2022';
+
+ // The tab character - \t - cannot be used to create a tab space
+ // within a cell as it is rendered as a square. Therefore, four
+ // spaces are used to simulate that character.
+ private static final String TAB = " ";
+
+ /**
+ * Call each of the list creation methods.
+ *
+ * @param outputFilename A String that encapsulates the name of and path to
+ * the Excel spreadsheet file this code will create.
+ */
+ public void demonstrateMethodCalls(String outputFilename) throws IOException {
+ HSSFWorkbook workbook = null;
+ HSSFSheet sheet = null;
+ HSSFRow row = null;
+ HSSFCell cell = null;
+ ArrayList multiLevelListItems = null;
+ ArrayList listItems = null;
+ try {
+ workbook = new HSSFWorkbook();
+ sheet = workbook.createSheet("In Cell Lists");
+ row = sheet.createRow(0);
+
+ // Create a cell at A1 and insert a single, bulleted, item into
+ // that cell.
+ cell = row.createCell(0);
+ this.bulletedItemInCell(workbook, "List Item", cell);
+
+ // Create a cell at A2 and insert a plain list - that is one
+ // whose items are neither bulleted or numbered - into that cell.
+ row = sheet.createRow(1);
+ cell = row.createCell(0);
+ listItems = new ArrayList();
+ listItems.add("List Item One.");
+ listItems.add("List Item Two.");
+ listItems.add("List Item Three.");
+ listItems.add("List Item Four.");
+ this.listInCell(workbook, listItems, cell);
+ // The row height and cell width are set here to ensure that the
+ // list may be seen.
+ row.setHeight((short)1100);
+ sheet.setColumnWidth(0, 9500);
+
+ // Create a cell at A3 and insert a numbered list into that cell.
+ // Note that a couple of items have been added to the listItems
+ // ArrayList
+ row = sheet.createRow(2);
+ cell = row.createCell(0);
+ listItems.add("List Item Five.");
+ listItems.add("List Item Six.");
+ this.numberedListInCell(workbook, listItems, cell, 1, 2);
+ row.setHeight((short)1550);
+
+ // Create a cell at A4 and insert a numbered list into that cell.
+ // Note that a couple of items have been added to the listItems
+ // ArrayList
+ row = sheet.createRow(3);
+ cell = row.createCell(0);
+ listItems.add("List Item Seven.");
+ listItems.add("List Item Eight.");
+ listItems.add("List Item Nine.");
+ listItems.add("List Item Ten.");
+ this.bulletedListInCell(workbook, listItems, cell);
+ row.setHeight((short)2550);
+
+ // Insert a plain, multi-level list into cell A5. Note that
+ // the major difference here is that the list items are passed as
+ // an ArrayList of MultiLevelListItems. Note that an ArrayList
+ // of instances of an inner class was used here in preference to
+ // a Hashtable or HashMap as the ArrayList will preserve the
+ // ordering of the items added to it; the first item added will
+ // be the first item recovered and the last item added, the last
+ // item recovered. Alternatively, a LinkedHashMap could be used
+ // to preserve order.
+ row = sheet.createRow(4);
+ cell = row.createCell(0);
+ multiLevelListItems = new ArrayList();
+ listItems = new ArrayList();
+ listItems.add("ML List Item One - Sub Item One.");
+ listItems.add("ML List Item One - Sub Item Two.");
+ listItems.add("ML List Item One - Sub Item Three.");
+ listItems.add("ML List Item One - Sub Item Four.");
+ multiLevelListItems.add(new MultiLevelListItem("List Item One.", listItems));
+ // Passing either null or an empty ArrayList will signal that
+ // there are no lower level items associated with the top level
+ // item
+ multiLevelListItems.add(new MultiLevelListItem("List Item Two.", null));
+ multiLevelListItems.add(new MultiLevelListItem("List Item Three.", null));
+ listItems = new ArrayList();
+ listItems.add("ML List Item Four - Sub Item One.");
+ listItems.add("ML List Item Four - Sub Item Two.");
+ listItems.add("ML List Item Four - Sub Item Three.");
+ multiLevelListItems.add(new MultiLevelListItem("List Item Four.", listItems));
+ this.multiLevelListInCell(workbook, multiLevelListItems, cell);
+ row.setHeight((short)2800);
+
+ // Insert a numbered multi-level list into cell A6. Note that the
+ // same ArrayList as constructed for the above plain multi-level
+ // list example will be re-used
+ row = sheet.createRow(5);
+ cell = row.createCell(0);
+ this.multiLevelNumberedListInCell(workbook, multiLevelListItems,
+ cell, 1, 1, 1, 2);
+ row.setHeight((short)2800);
+
+ // Insert a numbered multi-level list into cell A7. Note that the
+ // same ArrayList as constructed for the plain multi-level list
+ // example will be re-used
+ row = sheet.createRow(6);
+ cell = row.createCell(0);
+ this.multiLevelBulletedListInCell(workbook, multiLevelListItems, cell);
+ row.setHeight((short)2800);
+
+ // Save the completed workbook
+ FileOutputStream fos = new FileOutputStream(new File(outputFilename));
+ try {
+ workbook.write(fos);
+ } finally {
+ fos.close();
+ }
+ }
+ catch(FileNotFoundException fnfEx) {
+ System.out.println("Caught a: " + fnfEx.getClass().getName());
+ System.out.println("Message: " + fnfEx.getMessage());
+ System.out.println("Stacktrace follows...........");
+ fnfEx.printStackTrace(System.out);
+ }
+ catch(IOException ioEx) {
+ System.out.println("Caught a: " + ioEx.getClass().getName());
+ System.out.println("Message: " + ioEx.getMessage());
+ System.out.println("Stacktrace follows...........");
+ ioEx.printStackTrace(System.out);
+ }
+ finally {
+ if (workbook != null) {
+ workbook.close();
+ }
+ }
+ }
+
+ /**
+ * Inserts a single bulleted item into a cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param listItem An instance of the String class encapsulating the
+ * items text.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list item
+ * will be written.
+ */
+ public void bulletedItemInCell(HSSFWorkbook workbook, String listItem, HSSFCell cell) {
+ // A format String must be built to ensure that the contents of the
+ // cell appear as a bulleted item.
+ HSSFDataFormat format = workbook.createDataFormat();
+ String formatString = InCellLists.BULLET_CHARACTER + " @";
+ int formatIndex = format.getFormat(formatString);
+
+ // Construct an HSSFCellStyle and set it's data formt to use the
+ // object created above.
+ HSSFCellStyle bulletStyle = workbook.createCellStyle();
+ bulletStyle.setDataFormat((short)formatIndex);
+
+ // Set the cells contents and style.
+ cell.setCellValue(new HSSFRichTextString(listItem));
+ cell.setCellStyle(bulletStyle);
+ }
+
+ /**
+ * Inserts a list of plain items - that is items that are neither
+ * numbered or bulleted - into a single cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param listItems An ArrayList whose elements encapsulate the text for
+ * the list's items.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list
+ * will be written.
+ */
+ public void listInCell(HSSFWorkbook workbook, ArrayList listItems, HSSFCell cell) {
+ StringBuffer buffer = new StringBuffer();
+ HSSFCellStyle wrapStyle = workbook.createCellStyle();
+ wrapStyle.setWrapText(true);
+ for(String listItem : listItems) {
+ buffer.append(listItem);
+ buffer.append("\n");
+ }
+ // The StringBuffer's contents are the source for the contents
+ // of the cell.
+ cell.setCellValue(new HSSFRichTextString(buffer.toString().trim()));
+ cell.setCellStyle(wrapStyle);
+ }
+
+ /**
+ * Inserts a numbered list into a single cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param listItems An ArrayList whose elements encapsulate the text for
+ * the lists items.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list
+ * will be written.
+ * @param startingValue A primitive int containing the number for the first
+ * item in the list.
+ * @param increment A primitive int containing the value that should be used
+ * to calculate subsequent item numbers.
+ */
+ public void numberedListInCell(HSSFWorkbook workbook,
+ ArrayList listItems,
+ HSSFCell cell,
+ int startingValue,
+ int increment) {
+ StringBuffer buffer = new StringBuffer();
+ int itemNumber = startingValue;
+ // Note that again, an HSSFCellStye object is required and that
+ // it's wrap text property should be set to 'true'
+ HSSFCellStyle wrapStyle = workbook.createCellStyle();
+ wrapStyle.setWrapText(true);
+ // Note that the basic method is identical to the listInCell() method
+ // with one difference; a number prefixed to the items text.
+ for(String listItem : listItems) {
+ buffer.append(String.valueOf(itemNumber) + ". ");
+ buffer.append(listItem);
+ buffer.append("\n");
+ itemNumber += increment;
+ }
+ // The StringBuffer's contents are the source for the contents
+ // of the cell.
+ cell.setCellValue(new HSSFRichTextString(buffer.toString().trim()));
+ cell.setCellStyle(wrapStyle);
+ }
+
+ /**
+ * Insert a bulleted list into a cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param listItems An ArrayList whose elements encapsulate the text for
+ * the lists items.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list
+ * will be written.
+ */
+ public void bulletedListInCell(HSSFWorkbook workbook,
+ ArrayList listItems,
+ HSSFCell cell) {
+ StringBuffer buffer = new StringBuffer();
+ // Note that again, an HSSFCellStye object is required and that
+ // it's wrap text property should be set to 'true'
+ HSSFCellStyle wrapStyle = workbook.createCellStyle();
+ wrapStyle.setWrapText(true);
+ // Note that the basic method is identical to the listInCell() method
+ // with one difference; the bullet character prefixed to the items text.
+ for(String listItem : listItems) {
+ buffer.append(InCellLists.BULLET_CHARACTER + " ");
+ buffer.append(listItem);
+ buffer.append("\n");
+ }
+ // The StringBuffer's contents are the source for the contents
+ // of the cell.
+ cell.setCellValue(new HSSFRichTextString(buffer.toString().trim()));
+ cell.setCellStyle(wrapStyle);
+ }
+
+ /**
+ * Insert a multi-level list into a cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param multiLevelListItems An ArrayList whose elements contain instances
+ * of the MultiLevelListItem class. Each element
+ * encapsulates the text for the high level item
+ * along with an ArrayList. Each element of this
+ * ArrayList encapsulates the text for a lower
+ * level item.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list
+ * will be written.
+ */
+ public void multiLevelListInCell(HSSFWorkbook workbook,
+ ArrayList multiLevelListItems,
+ HSSFCell cell) {
+ StringBuffer buffer = new StringBuffer();
+ ArrayList lowerLevelItems = null;
+ // Note that again, an HSSFCellStye object is required and that
+ // it's wrap text property should be set to 'true'
+ HSSFCellStyle wrapStyle = workbook.createCellStyle();
+ wrapStyle.setWrapText(true);
+ // Step through the ArrayList of MultilLevelListItem instances.
+ for(MultiLevelListItem multiLevelListItem : multiLevelListItems) {
+ // For each element in the ArrayList, get the text for the high
+ // level list item......
+ buffer.append(multiLevelListItem.getItemText());
+ buffer.append("\n");
+ // and then an ArrayList whose elements encapsulate the text
+ // for the lower level list items.
+ lowerLevelItems = multiLevelListItem.getLowerLevelItems();
+ if(!(lowerLevelItems == null) && !(lowerLevelItems.isEmpty())) {
+ for(String item : lowerLevelItems) {
+ buffer.append(InCellLists.TAB);
+ buffer.append(item);
+ buffer.append("\n");
+ }
+ }
+ }
+ // The StringBuffer's contents are the source for the contents
+ // of the cell.
+ cell.setCellValue(new HSSFRichTextString(buffer.toString().trim()));
+ cell.setCellStyle(wrapStyle);
+ }
+
+ /**
+ * Insert a multi-level list into a cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param multiLevelListItems An ArrayList whose elements contain instances
+ * of the MultiLevelListItem class. Each element
+ * encapsulates the text for the high level item
+ * along with an ArrayList. Each element of this
+ * ArrayList encapsulates the text for a lower
+ * level item.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list
+ * will be written.
+ * @param highLevelStartingValue A primitive int containing the number
+ * for the first high level item in the list.
+ * @param highLevelIncrement A primitive int containing the value that
+ * should be used to calculate the number of
+ * subsequent high level item.
+ * @param lowLevelStartingValue A primitive int will containing the number
+ * for the first low level item associated
+ * with a high level item.
+ * @param lowLevelIncrement A primitive int containing the value that
+ * should be used to calculate the number of
+ * subsequent low level item.
+ */
+ public void multiLevelNumberedListInCell(HSSFWorkbook workbook,
+ ArrayList multiLevelListItems,
+ HSSFCell cell,
+ int highLevelStartingValue,
+ int highLevelIncrement,
+ int lowLevelStartingValue,
+ int lowLevelIncrement) {
+ StringBuffer buffer = new StringBuffer();
+ int highLevelItemNumber = highLevelStartingValue;
+ int lowLevelItemNumber = 0;
+ ArrayList lowerLevelItems = null;
+ // Note that again, an HSSFCellStye object is required and that
+ // it's wrap text property should be set to 'true'
+ HSSFCellStyle wrapStyle = workbook.createCellStyle();
+ wrapStyle.setWrapText(true);
+ // Step through the ArrayList of MultilLevelListItem instances.
+ for(MultiLevelListItem multiLevelListItem : multiLevelListItems) {
+ // For each element in the ArrayList, get the text for the high
+ // level list item......
+ buffer.append(String.valueOf(highLevelItemNumber));
+ buffer.append(". ");
+ buffer.append(multiLevelListItem.getItemText());
+ buffer.append("\n");
+ // and then an ArrayList whose elements encapsulate the text
+ // for the lower level list items.
+ lowerLevelItems = multiLevelListItem.getLowerLevelItems();
+ if(!(lowerLevelItems == null) && !(lowerLevelItems.isEmpty())) {
+ lowLevelItemNumber = lowLevelStartingValue;
+ for(String item : lowerLevelItems) {
+ buffer.append(InCellLists.TAB);
+ buffer.append(String.valueOf(highLevelItemNumber));
+ buffer.append(".");
+ buffer.append(String.valueOf(lowLevelItemNumber));
+ buffer.append(" ");
+ buffer.append(item);
+ buffer.append("\n");
+ lowLevelItemNumber += lowLevelIncrement;
+ }
+ }
+ highLevelItemNumber += highLevelIncrement;
+ }
+ // The StringBuffer's contents are the source for the contents
+ // of the cell.
+ cell.setCellValue(new HSSFRichTextString(buffer.toString().trim()));
+ cell.setCellStyle(wrapStyle);
+ }
+
+ /**
+ * Insert a bulleted multi-level list into a cell.
+ *
+ * @param workbook A reference to the HSSFWorkbook that 'contains' the
+ * cell.
+ * @param multiLevelListItems An ArrayList whose elements contain instances
+ * of the MultiLevelListItem class. Each element
+ * encapsulates the text for the high level item
+ * along with an ArrayList. Each element of this
+ * ArrayList encapsulates the text for a lower
+ * level item.
+ * @param cell An instance of the HSSFCell class that encapsulates a
+ * reference to the spreadsheet cell into which the list
+ * will be written.
+ */
+ public void multiLevelBulletedListInCell(HSSFWorkbook workbook,
+ ArrayList multiLevelListItems,
+ HSSFCell cell) {
+ StringBuffer buffer = new StringBuffer();
+ ArrayList lowerLevelItems = null;
+ // Note that again, an HSSFCellStye object is required and that
+ // it's wrap text property should be set to 'true'
+ HSSFCellStyle wrapStyle = workbook.createCellStyle();
+ wrapStyle.setWrapText(true);
+ // Step through the ArrayList of MultilLevelListItem instances.
+ for(MultiLevelListItem multiLevelListItem : multiLevelListItems) {
+ // For each element in the ArrayList, get the text for the high
+ // level list item......
+ buffer.append(InCellLists.BULLET_CHARACTER);
+ buffer.append(" ");
+ buffer.append(multiLevelListItem.getItemText());
+ buffer.append("\n");
+ // and then an ArrayList whose elements encapsulate the text
+ // for the lower level list items.
+ lowerLevelItems = multiLevelListItem.getLowerLevelItems();
+ if(!(lowerLevelItems == null) && !(lowerLevelItems.isEmpty())) {
+ for(String item : lowerLevelItems) {
+ buffer.append(InCellLists.TAB);
+ buffer.append(InCellLists.BULLET_CHARACTER);
+ buffer.append(" ");
+ buffer.append(item);
+ buffer.append("\n");
+ }
+ }
+ }
+ // The StringBuffer's contents are the source for the contents
+ // of the cell.
+ cell.setCellValue(new HSSFRichTextString(buffer.toString().trim()));
+ cell.setCellStyle(wrapStyle);
+ }
+
+ /**
+ * The main entry point to the program. Demonstrates how to call the method
+ * that will create an Excel workbook containing many different sorts of
+ * lists.
+ *
+ * @param args the command line arguments.
+ */
+ public static void main(String[] args) throws IOException {
+ new InCellLists().demonstrateMethodCalls("Latest In Cell List.xls");
+ }
+
+ /**
+ * An instance of this inner class models an item or element in a
+ * multi-level list. Each multi-level list item consists of the text for the
+ * high level items and an ArrayList containing the text for each of the
+ * associated lower level items. When written into a cell, each multi-level
+ * list item will have this general appearance.
+ *
+ * Item One
+ * Sub Item One.
+ * Sub Item Two.
+ * Item Two
+ * Sub Item One.
+ * Sub Item Two.
+ * etc.
+ *
+ * It would be quite possible to modify this class to model much more
+ * complex list structures descending through two, three or even more
+ * levels.
+ */
+ public final class MultiLevelListItem {
+
+ private String itemText = null;
+ private ArrayList lowerLevelItems = null;
+
+ /**
+ * Create a new instance of the MultiLevelListItem class using the
+ * following parameters.
+ *
+ * @param itemText A String that encapsulates the text for the high
+ * level list item.
+ * @param lowerLevelItems An ArrayList whose elements encapsulate the
+ * text for the associated lower level list
+ * items.
+ */
+ public MultiLevelListItem(String itemText, ArrayList lowerLevelItems) {
+ this.itemText = itemText;
+ this.lowerLevelItems = lowerLevelItems;
+ }
+
+ /**
+ * Get the text for the high level list item.
+ *
+ * @return A String that encapsulates the text for the high level list
+ * item.
+ */
+ public String getItemText() {
+ return(this.itemText);
+ }
+
+ /**
+ * Get the text for the associated lower level list items.
+ *
+ * @return An ArrayList whose elements each encapsulate the text for a
+ * single associated lower level list item.
+ */
+ public ArrayList getLowerLevelItems() {
+ return(this.lowerLevelItems);
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/MergedCells.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/MergedCells.java
new file mode 100644
index 000000000..31e5215b8
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/MergedCells.java
@@ -0,0 +1,47 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+
+/**
+ * An example of how to merge regions of cells.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class MergedCells {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+
+ HSSFRow row = sheet.createRow(1);
+ HSSFCell cell = row.createCell(1);
+ cell.setCellValue("This is a test of merging");
+
+ sheet.addMergedRegion(new CellRangeAddress(1, 1, 1, 2));
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewLinesInCells.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewLinesInCells.java
new file mode 100644
index 000000000..018556f99
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewLinesInCells.java
@@ -0,0 +1,60 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ss.usermodel.CellType;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Demonstrates how to use newlines in cells.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ * @author Fauzia Lala
+ */
+public class NewLinesInCells {
+ public static void main( String[] args ) throws IOException {
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet s = wb.createSheet();
+ HSSFRow r = null;
+ HSSFCell c = null;
+ HSSFCellStyle cs = wb.createCellStyle();
+ HSSFFont f2 = wb.createFont();
+
+ cs = wb.createCellStyle();
+
+ cs.setFont(f2);
+ // Word Wrap MUST be turned on
+ cs.setWrapText(true);
+
+ r = s.createRow(2);
+ r.setHeight((short) 0x349);
+ c = r.createCell(2);
+ c.setCellType(CellType.STRING);
+ c.setCellValue("Use \n with word wrap on to create a new line");
+ c.setCellStyle(cs);
+ s.setColumnWidth(2, (int) ((50 * 8) / ((double) 1 / 20)));
+
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewSheet.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewSheet.java
new file mode 100644
index 000000000..495543e49
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewSheet.java
@@ -0,0 +1,43 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.util.WorkbookUtil;
+
+/**
+ * Creates a new workbook with a sheet that's been explicitly defined.
+ */
+public abstract class NewSheet {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("new sheet");
+ // create with default name
+ wb.createSheet();
+ final String name = "second sheet";
+ // setting sheet name later
+ wb.setSheetName(1, WorkbookUtil.createSafeSheetName(name));
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewWorkbook.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewWorkbook.java
new file mode 100644
index 000000000..c1614e6c1
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/NewWorkbook.java
@@ -0,0 +1,42 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This example creates a new blank workbook. This workbook will contain a single blank sheet.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class NewWorkbook
+{
+ public static void main(String[] args)
+ throws IOException
+ {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java
new file mode 100644
index 000000000..68aafbc7c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java
@@ -0,0 +1,322 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
+
+import java.io.*;
+
+/**
+ * Demonstrates how to use the office drawing capabilities of POI.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class OfficeDrawing {
+ public static void main(String[] args) throws IOException {
+ // Create the workbook and sheets.
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet("new sheet");
+ HSSFSheet sheet2 = wb.createSheet("second sheet");
+ HSSFSheet sheet3 = wb.createSheet("third sheet");
+ HSSFSheet sheet4 = wb.createSheet("fourth sheet");
+ HSSFSheet sheet5 = wb.createSheet("fifth sheet");
+
+ // Draw stuff in them
+ drawSheet1( sheet1 );
+ drawSheet2( sheet2 );
+ drawSheet3( sheet3 );
+ drawSheet4( sheet4, wb );
+ drawSheet5( sheet5, wb );
+
+ // Write the file out.
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+
+ private static void drawSheet1( HSSFSheet sheet1 )
+ {
+ // Create a row and size one of the cells reasonably large.
+ HSSFRow row = sheet1.createRow(2);
+ row.setHeight((short) 2800);
+ row.createCell(1);
+ sheet1.setColumnWidth(2, 9000);
+
+ // Create the drawing patriarch. This is the top level container for
+ // all shapes.
+ HSSFPatriarch patriarch = sheet1.createDrawingPatriarch();
+
+ // Draw some lines and an oval.
+ drawLinesToCenter( patriarch );
+ drawManyLines( patriarch );
+ drawOval( patriarch );
+ drawPolygon( patriarch );
+
+ // Draw a rectangle.
+ HSSFSimpleShape rect = patriarch.createSimpleShape( new HSSFClientAnchor(100, 100, 900, 200, (short)0, 0, (short)0, 0) );
+ rect.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE);
+ }
+
+ private static void drawSheet2( HSSFSheet sheet2 )
+ {
+ // Create a row and size one of the cells reasonably large.
+ HSSFRow row = sheet2.createRow(2);
+ row.createCell(1);
+ row.setHeightInPoints(240);
+ sheet2.setColumnWidth(2, 9000);
+
+ // Create the drawing patriarch. This is the top level container for
+ // all shapes. This will clear out any existing shapes for that sheet.
+ HSSFPatriarch patriarch = sheet2.createDrawingPatriarch();
+
+ // Draw a grid in one of the cells.
+ drawGrid( patriarch );
+ }
+
+ private static void drawSheet3( HSSFSheet sheet3 )
+ {
+ // Create a row and size one of the cells reasonably large
+ HSSFRow row = sheet3.createRow(2);
+ row.setHeightInPoints(140);
+ row.createCell(1);
+ sheet3.setColumnWidth(2, 9000);
+
+ // Create the drawing patriarch. This is the top level container for
+ // all shapes. This will clear out any existing shapes for that sheet.
+ HSSFPatriarch patriarch = sheet3.createDrawingPatriarch();
+
+ // Create a shape group.
+ HSSFShapeGroup group = patriarch.createGroup(
+ new HSSFClientAnchor(0,0,900,200,(short)2,2,(short)2,2));
+
+ // Create a couple of lines in the group.
+ HSSFSimpleShape shape1 = group.createShape(new HSSFChildAnchor(3,3,500,500));
+ shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+ ( (HSSFChildAnchor) shape1.getAnchor() ).setAnchor((short)3,3,500,500);
+ HSSFSimpleShape shape2 = group.createShape(new HSSFChildAnchor((short)1,200,400,600));
+ shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+
+ }
+
+ private static void drawSheet4( HSSFSheet sheet4, HSSFWorkbook wb )
+ {
+ // Create the drawing patriarch. This is the top level container for
+ // all shapes. This will clear out any existing shapes for that sheet.
+ HSSFPatriarch patriarch = sheet4.createDrawingPatriarch();
+
+ // Create a couple of textboxes
+ HSSFTextbox textbox1 = patriarch.createTextbox(
+ new HSSFClientAnchor(0,0,0,0,(short)1,1,(short)2,2));
+ textbox1.setString(new HSSFRichTextString("This is a test") );
+ HSSFTextbox textbox2 = patriarch.createTextbox(
+ new HSSFClientAnchor(0,0,900,100,(short)3,3,(short)3,4));
+ textbox2.setString(new HSSFRichTextString("Woo") );
+ textbox2.setFillColor(200,0,0);
+ textbox2.setLineStyle(HSSFSimpleShape.LINESTYLE_DOTGEL);
+
+ // Create third one with some fancy font styling.
+ HSSFTextbox textbox3 = patriarch.createTextbox(
+ new HSSFClientAnchor(0,0,900,100,(short)4,4,(short)5,4+1));
+ HSSFFont font = wb.createFont();
+ font.setItalic(true);
+ font.setUnderline(HSSFFont.U_DOUBLE);
+ HSSFRichTextString string = new HSSFRichTextString("Woo!!!");
+ string.applyFont(2,5,font);
+ textbox3.setString(string );
+ textbox3.setFillColor(0x08000030);
+ textbox3.setLineStyle(HSSFSimpleShape.LINESTYLE_NONE); // no line around the textbox.
+ textbox3.setNoFill(true); // make it transparent
+ }
+
+ private static void drawSheet5( HSSFSheet sheet5, HSSFWorkbook wb ) throws IOException
+ {
+
+ // Create the drawing patriarch. This is the top level container for
+ // all shapes. This will clear out any existing shapes for that sheet.
+ HSSFPatriarch patriarch = sheet5.createDrawingPatriarch();
+
+ HSSFClientAnchor anchor;
+ anchor = new HSSFClientAnchor(0,0,0,255,(short)2,2,(short)4,7);
+ anchor.setAnchorType( AnchorType.MOVE_DONT_RESIZE );
+ patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4.png", wb ));
+
+ anchor = new HSSFClientAnchor(0,0,0,255,(short)4,2,(short)5,7);
+ anchor.setAnchorType( AnchorType.MOVE_DONT_RESIZE );
+ patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4edited.png", wb ));
+
+ anchor = new HSSFClientAnchor(0,0,1023,255,(short)6,2,(short)8,7);
+ anchor.setAnchorType( AnchorType.MOVE_DONT_RESIZE );
+ HSSFPicture picture = patriarch.createPicture(anchor, loadPicture( "src/resources/logos/logoKarmokar4s.png", wb ));
+ //Reset the image to the original size.
+ picture.resize();
+ picture.setLineStyle( HSSFShape.LINESTYLE_DASHDOTGEL );
+
+ }
+
+ private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException
+ {
+ int pictureIndex;
+ FileInputStream fis = null;
+ ByteArrayOutputStream bos = null;
+ try
+ {
+ fis = new FileInputStream( path);
+ bos = new ByteArrayOutputStream( );
+ int c;
+ while ( (c = fis.read()) != -1)
+ bos.write( c );
+ pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG );
+ }
+ finally
+ {
+ if (fis != null)
+ fis.close();
+ if (bos != null)
+ bos.close();
+ }
+ return pictureIndex;
+ }
+
+ private static void drawOval( HSSFPatriarch patriarch )
+ {
+ // Create an oval and style to taste.
+ HSSFClientAnchor a = new HSSFClientAnchor();
+ a.setAnchor((short)2, 2, 20, 20, (short) 2, 2, 190, 80);
+ HSSFSimpleShape s = patriarch.createSimpleShape(a);
+ s.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL);
+ s.setLineStyleColor(10,10,10);
+ s.setFillColor(90,10,200);
+ s.setLineWidth(HSSFShape.LINEWIDTH_ONE_PT * 3);
+ s.setLineStyle(HSSFShape.LINESTYLE_DOTSYS);
+ }
+
+ private static void drawPolygon( HSSFPatriarch patriarch )
+ {
+ // HSSFClientAnchor a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 2, 2, (short) 3, 3 );
+ // HSSFPolygon p = patriarch.createPolygon(a);
+ // p.setPolygonDrawArea(100,100);
+ // p.setPoints( new int[]{30, 90, 50}, new int[]{88, 5, 44} );
+
+
+ HSSFClientAnchor a = new HSSFClientAnchor();
+ a.setAnchor( (short) 2, 2, 0, 0, (short) 3, 3, 1023, 255 );
+ HSSFShapeGroup g = patriarch.createGroup( a );
+ g.setCoordinates(0,0,200,200);
+ HSSFPolygon p1 = g.createPolygon( new HSSFChildAnchor( 0, 0, 200, 200 ) );
+ p1.setPolygonDrawArea( 100, 100 );
+ p1.setPoints( new int[]{0, 90, 50}, new int[]{5, 5, 44} );
+ p1.setFillColor( 0, 255, 0 );
+ HSSFPolygon p2 = g.createPolygon( new HSSFChildAnchor( 20, 20, 200, 200 ) );
+ p2.setPolygonDrawArea( 200, 200 );
+ p2.setPoints( new int[]{120, 20, 150}, new int[]{105, 30, 195} );
+ p2.setFillColor( 255, 0, 0 );
+ }
+
+ private static void drawManyLines( HSSFPatriarch patriarch )
+ {
+ // Draw bunch of lines
+ int x1 = 100;
+ int y1 = 100;
+ int x2 = 800;
+ int y2 = 200;
+ int color = 0;
+ for (int i = 0; i < 10; i++)
+ {
+ HSSFClientAnchor a2 = new HSSFClientAnchor();
+ a2.setAnchor((short) 2, 2, x1, y1, (short) 2, 2, x2, y2);
+ HSSFSimpleShape shape2 = patriarch.createSimpleShape(a2);
+ shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+ shape2.setLineStyleColor(color);
+ y1 -= 10;
+ y2 -= 10;
+ color += 30;
+ }
+ }
+
+ private static void drawGrid( HSSFPatriarch patriarch )
+ {
+ // This draws a grid of lines. Since the coordinates space fixed at
+ // 1024 by 256 we use a ratio to get a reasonably square grids.
+
+ double xRatio = 3.22;
+ double yRatio = 0.6711;
+
+ int x1 = 000;
+ int y1 = 000;
+ int x2 = 000;
+ int y2 = 200;
+ for (int i = 0; i < 20; i++)
+ {
+ HSSFClientAnchor a2 = new HSSFClientAnchor();
+ a2.setAnchor((short) 2, 2, (int) ( x1 * xRatio ), (int) ( y1 * yRatio ),
+ (short) 2, 2, (int) ( x2 * xRatio ), (int) ( y2 * yRatio ));
+ HSSFSimpleShape shape2 = patriarch.createSimpleShape(a2);
+ shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+
+ x1 += 10;
+ x2 += 10;
+ }
+
+ x1 = 000;
+ y1 = 000;
+ x2 = 200;
+ y2 = 000;
+ for (int i = 0; i < 20; i++)
+ {
+ HSSFClientAnchor a2 = new HSSFClientAnchor();
+ a2.setAnchor((short) 2, 2, (int) ( x1 * xRatio ), (int) ( y1 * yRatio ),
+ (short) 2, 2, (int) ( x2 * xRatio ), (int) ( y2 * yRatio ));
+ HSSFSimpleShape shape2 = patriarch.createSimpleShape(a2);
+ shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+
+ y1 += 10;
+ y2 += 10;
+ }
+ }
+
+ private static void drawLinesToCenter( HSSFPatriarch patriarch )
+ {
+ // Draw some lines from and to the corners
+ {
+ HSSFClientAnchor a1 = new HSSFClientAnchor();
+ a1.setAnchor( (short)2, 2, 0, 0, (short) 2, 2, 512, 128);
+ HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1);
+ shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+ }
+ {
+ HSSFClientAnchor a1 = new HSSFClientAnchor();
+ a1.setAnchor( (short)2, 2, 512, 128, (short) 2, 2, 1024, 0);
+ HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1);
+ shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+ }
+ {
+ HSSFClientAnchor a1 = new HSSFClientAnchor();
+ a1.setAnchor( (short)1, 1, 0, 0, (short) 1, 1, 512, 100);
+ HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1);
+ shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+ }
+ {
+ HSSFClientAnchor a1 = new HSSFClientAnchor();
+ a1.setAnchor( (short)1, 1, 512, 100, (short) 1, 1, 1024, 0);
+ HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1);
+ shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE);
+ }
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawingWithGraphics.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawingWithGraphics.java
new file mode 100644
index 000000000..4de1207f4
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawingWithGraphics.java
@@ -0,0 +1,103 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+
+import java.awt.*;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Demonstrates the use of the EscherGraphics2d library.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class OfficeDrawingWithGraphics {
+ public static void main( String[] args ) throws IOException {
+ // Create a workbook with one sheet and size the first three somewhat
+ // larger so we can fit the chemical structure diagram in.
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet( "my drawing" );
+ sheet.setColumnWidth(1, 256 * 27);
+ HSSFRow row1 = sheet.createRow(0);
+ row1.setHeightInPoints(10 * 15);
+ HSSFRow row2 = sheet.createRow(1);
+ row2.setHeightInPoints(5 * 15);
+ HSSFRow row3 = sheet.createRow(2);
+ row3.setHeightInPoints(10 * 15);
+
+ // Add some cells so we can test that the anchoring works when we
+ // sort them.
+ row1.createCell(0).setCellValue("C");
+ row2.createCell(0).setCellValue("A");
+ row3.createCell(0).setCellValue("B");
+
+ // Create the top level drawing patriarch.
+ HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
+
+ HSSFClientAnchor a;
+ HSSFShapeGroup group;
+ EscherGraphics g;
+ EscherGraphics2d g2d;
+ // Anchor entirely within one cell.
+ a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 1, 0, (short) 1, 0 );
+ group = patriarch.createGroup( a );
+ group.setCoordinates( 0, 0, 320, 276 );
+ float verticalPointsPerPixel = a.getAnchorHeightInPoints(sheet) / Math.abs(group.getY2() - group.getY1());
+ g = new EscherGraphics( group, wb, Color.black, verticalPointsPerPixel );
+ g2d = new EscherGraphics2d( g );
+ drawStar( g2d );
+
+ a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 1, 1, (short) 1, 1 );
+ group = patriarch.createGroup( a );
+ group.setCoordinates( 0, 0, 640, 276 );
+ verticalPointsPerPixel = a.getAnchorHeightInPoints(sheet) / Math.abs(group.getY2() - group.getY1());
+// verticalPixelsPerPoint = (float)Math.abs(group.getY2() - group.getY1()) / a.getAnchorHeightInPoints(sheet);
+ g = new EscherGraphics( group, wb, Color.black, verticalPointsPerPixel );
+ g2d = new EscherGraphics2d( g );
+ drawStar( g2d );
+
+ FileOutputStream out = new FileOutputStream("workbook.xls");
+ wb.write(out);
+ out.close();
+
+ }
+
+ private static void drawStar( EscherGraphics2d g2d )
+ {
+ g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
+ for (double i = 0; i < Math.PI; i += 0.1)
+ {
+ g2d.setColor( new Color((int)(i * 5343062d) ) );
+ int x1 = (int) ( Math.cos(i) * 160.0 ) + 160;
+ int y1 = (int) ( Math.sin(i) * 138.0 ) + 138;
+ int x2 = (int) ( -Math.cos(i) * 160.0 ) + 160;
+ int y2 = (int) ( -Math.sin(i) * 138.0 ) + 138;
+ g2d.setStroke(new BasicStroke(2));
+ g2d.drawLine(x1,y1,x2,y2);
+ }
+ g2d.setFont(new Font("SansSerif",Font.BOLD | Font.ITALIC, 20));
+ g2d.drawString("EscherGraphics2d",70,100);
+ g2d.setColor(Color.yellow);
+ g2d.fillOval( 160-20,138-20,40,40);
+ g2d.setColor(Color.black);
+ g2d.fillPolygon(new int[] {-10+160,0+160,10+160,0+160}, new int[] {0+138,10+138,0+138,-10+138}, 4);
+ g2d.drawPolygon(new int[] {-160+160,0+160,160+160,0+160}, new int[] {0+138,138+138,0+138,-138+138}, 4);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Outlines.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Outlines.java
new file mode 100644
index 000000000..125d60d77
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/Outlines.java
@@ -0,0 +1,186 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.Closeable;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * Creates outlines.
+ */
+public class Outlines implements Closeable {
+ public static void main(String[] args)
+ throws IOException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+ POILogger LOGGER = POILogFactory.getLogger(Outlines.class);
+ for (int i=1; i<=13; i++) {
+ Outlines o = new Outlines();
+ try {
+ String log = (String)Outlines.class.getDeclaredMethod("test"+i).invoke(o);
+ String filename = "outline"+i+".xls";
+ o.writeOut(filename);
+ LOGGER.log(POILogger.INFO, filename+" written. "+log);
+ } finally {
+ o.close();
+ }
+ }
+ }
+
+ private final HSSFWorkbook wb = new HSSFWorkbook();
+ private final HSSFSheet sheet1 = wb.createSheet("new sheet");
+
+ public void writeOut(String filename) throws IOException {
+ FileOutputStream fileOut = new FileOutputStream(filename);
+ try {
+ wb.write(fileOut);
+ } finally {
+ fileOut.close();
+ }
+ }
+
+ public void close() throws IOException {
+ wb.close();
+ }
+
+ public String test1() {
+ sheet1.groupColumn(4, 7);
+
+ for (int row = 0; row < 200; row++) {
+ HSSFRow r = sheet1.createRow(row);
+ for (int column = 0; column < 200; column++) {
+ HSSFCell c = r.createCell(column);
+ c.setCellValue(column);
+ }
+ }
+ return "Two expanded groups.";
+ }
+
+ public String test2() {
+ sheet1.groupColumn(2, 10);
+ sheet1.groupColumn(4, 7);
+ sheet1.setColumnGroupCollapsed(4, true);
+ return "Two groups. Inner group collapsed.";
+ }
+
+ public String test3() {
+ sheet1.groupColumn(2, 10);
+ sheet1.groupColumn(4, 7);
+ sheet1.setColumnGroupCollapsed(4, true);
+ sheet1.setColumnGroupCollapsed(2, true);
+ return "Two groups. Both collapsed.";
+ }
+
+ public String test4() {
+ sheet1.groupColumn(2, 10);
+ sheet1.groupColumn(4, 7);
+ sheet1.setColumnGroupCollapsed(4, true);
+ sheet1.setColumnGroupCollapsed(2, true);
+
+ sheet1.setColumnGroupCollapsed(4, false);
+ return "Two groups. Collapsed then inner group expanded.";
+ }
+
+ public String test5() {
+ sheet1.groupColumn(2, 10);
+ sheet1.groupColumn(4, 7);
+ sheet1.setColumnGroupCollapsed(4, true);
+ sheet1.setColumnGroupCollapsed(2, true);
+
+ sheet1.setColumnGroupCollapsed(4, false);
+ sheet1.setColumnGroupCollapsed(3, false);
+ return "Two groups. Collapsed then reexpanded.";
+ }
+
+ public String test6() {
+ sheet1.groupColumn(2, 10);
+ sheet1.groupColumn(4, 10);
+ sheet1.setColumnGroupCollapsed(4, true);
+ sheet1.setColumnGroupCollapsed(2, true);
+
+ sheet1.setColumnGroupCollapsed(3, false);
+ return "Two groups with matching end points. Second group collapsed.";
+ }
+
+ public String test7() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 10);
+ return "Row outlines.";
+ }
+
+ public String test8() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 10);
+ sheet1.setRowGroupCollapsed(7, true);
+ return "Row outlines. Inner group collapsed.";
+ }
+
+ public String test9() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 10);
+ sheet1.setRowGroupCollapsed(7, true);
+ sheet1.setRowGroupCollapsed(5, true);
+ return "Row outlines. Both collapsed.";
+ }
+
+ public String test10() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 10);
+ sheet1.setRowGroupCollapsed(7, true);
+ sheet1.setRowGroupCollapsed(5, true);
+ sheet1.setRowGroupCollapsed(8, false);
+ return "Row outlines. Collapsed then inner group expanded.";
+ }
+
+ public String test11() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 10);
+ sheet1.setRowGroupCollapsed(7, true);
+ sheet1.setRowGroupCollapsed(5, true);
+ sheet1.setRowGroupCollapsed(8, false);
+ sheet1.setRowGroupCollapsed(14, false);
+ return "Row outlines. Collapsed then expanded.";
+ }
+
+ public String test12() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 14);
+ sheet1.setRowGroupCollapsed(7, true);
+ sheet1.setRowGroupCollapsed(5, true);
+ sheet1.setRowGroupCollapsed(6, false);
+ return "Row outlines. Two row groups with matching end points. Second group collapsed.";
+ }
+
+ public String test13() {
+ sheet1.groupRow(5, 14);
+ sheet1.groupRow(7, 14);
+ sheet1.groupRow(16, 19);
+
+ sheet1.groupColumn(4, 7);
+ sheet1.groupColumn(9, 12);
+ sheet1.groupColumn(10, 11);
+ return "Mixed bag.";
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/ReadWriteWorkbook.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/ReadWriteWorkbook.java
new file mode 100644
index 000000000..2b3553f76
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/ReadWriteWorkbook.java
@@ -0,0 +1,67 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.usermodel.CellType;
+
+/**
+ * This example demonstrates opening a workbook, modifying it and writing
+ * the results back out.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class ReadWriteWorkbook {
+ public static void main(String[] args) throws IOException {
+ FileInputStream fileIn = null;
+ FileOutputStream fileOut = null;
+
+ try
+ {
+ fileIn = new FileInputStream("workbook.xls");
+ POIFSFileSystem fs = new POIFSFileSystem(fileIn);
+ HSSFWorkbook wb = new HSSFWorkbook(fs);
+ HSSFSheet sheet = wb.getSheetAt(0);
+ HSSFRow row = sheet.getRow(2);
+ if (row == null)
+ row = sheet.createRow(2);
+ HSSFCell cell = row.getCell(3);
+ if (cell == null)
+ cell = row.createCell(3);
+ cell.setCellType(CellType.STRING);
+ cell.setCellValue("a test");
+
+ // Write the output to a file
+ fileOut = new FileOutputStream("workbookout.xls");
+ wb.write(fileOut);
+ } finally {
+ if (fileOut != null)
+ fileOut.close();
+ if (fileIn != null)
+ fileIn.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/RepeatingRowsAndColumns.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/RepeatingRowsAndColumns.java
new file mode 100644
index 000000000..6ceee7c26
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/RepeatingRowsAndColumns.java
@@ -0,0 +1,64 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+public class RepeatingRowsAndColumns {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet("first sheet");
+ HSSFSheet sheet2 = wb.createSheet("second sheet");
+ HSSFSheet sheet3 = wb.createSheet("third sheet");
+
+ HSSFFont boldFont = wb.createFont();
+ boldFont.setFontHeightInPoints((short)22);
+ boldFont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
+
+ HSSFCellStyle boldStyle = wb.createCellStyle();
+ boldStyle.setFont(boldFont);
+
+ HSSFRow row = sheet1.createRow(1);
+ HSSFCell cell = row.createCell(0);
+ cell.setCellValue("This quick brown fox");
+ cell.setCellStyle(boldStyle);
+
+ // Set the columns to repeat from column 0 to 2 on the first sheet
+ sheet1.setRepeatingColumns(CellRangeAddress.valueOf("A:C"));
+ // Set the rows to repeat from row 0 to 2 on the second sheet.
+ sheet2.setRepeatingRows(CellRangeAddress.valueOf("1:3"));
+ // Set the the repeating rows and columns on the third sheet.
+ CellRangeAddress cra = CellRangeAddress.valueOf("D1:E2");
+ sheet3.setRepeatingColumns(cra);
+ sheet3.setRepeatingRows(cra);
+
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/SplitAndFreezePanes.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/SplitAndFreezePanes.java
new file mode 100644
index 000000000..6e93a317f
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/SplitAndFreezePanes.java
@@ -0,0 +1,55 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+
+/**
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class SplitAndFreezePanes
+{
+ public static void main(String[] args)
+ throws IOException
+ {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet("new sheet");
+ HSSFSheet sheet2 = wb.createSheet("second sheet");
+ HSSFSheet sheet3 = wb.createSheet("third sheet");
+ HSSFSheet sheet4 = wb.createSheet("fourth sheet");
+
+ // Freeze just one row
+ sheet1.createFreezePane( 0, 1, 0, 1 );
+ // Freeze just one column
+ sheet2.createFreezePane( 1, 0, 1, 0 );
+ // Freeze the columns and rows (forget about scrolling position of the lower right quadrant).
+ sheet3.createFreezePane( 2, 2 );
+ // Create a split with the lower left side being the active quadrant
+ sheet4.createSplitPane( 2000, 2000, 0, 0, HSSFSheet.PANE_LOWER_LEFT );
+
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/WorkingWithFonts.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/WorkingWithFonts.java
new file mode 100644
index 000000000..6cb15941f
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/WorkingWithFonts.java
@@ -0,0 +1,59 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Demonstrates how to create and use fonts.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class WorkingWithFonts {
+ public static void main(String[] args) throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ HSSFRow row = sheet.createRow(1);
+
+ // Create a new font and alter it.
+ HSSFFont font = wb.createFont();
+ font.setFontHeightInPoints((short)24);
+ font.setFontName("Courier New");
+ font.setItalic(true);
+ font.setStrikeout(true);
+
+ // Fonts are set into a style so create a new one to use.
+ HSSFCellStyle style = wb.createCellStyle();
+ style.setFont(font);
+
+ // Create a cell and put a value in it.
+ HSSFCell cell = row.createCell(1);
+ cell.setCellValue("This is a test of fonts");
+ cell.setCellStyle(style);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/ZoomSheet.java b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/ZoomSheet.java
new file mode 100644
index 000000000..f83b5900e
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/usermodel/examples/ZoomSheet.java
@@ -0,0 +1,46 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.usermodel.examples;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+
+/**
+ * Sets the zoom magnication for a sheet.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class ZoomSheet
+{
+ public static void main(String[] args)
+ throws IOException
+ {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet("new sheet");
+ sheet1.setZoom(75); // 75 percent magnification
+ FileOutputStream fileOut = new FileOutputStream("workbook.xls");
+ wb.write(fileOut);
+ fileOut.close();
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVBorder.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVBorder.java
new file mode 100644
index 000000000..97a468966
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVBorder.java
@@ -0,0 +1,552 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.view;
+
+import java.awt.*;
+
+import javax.swing.border.AbstractBorder;
+
+import org.apache.poi.ss.usermodel.BorderStyle;
+
+/**
+ * This is an attempt to implement Excel style borders for the SheetViewer.
+ * Mostly just overrides stuff so the javadoc won't appear here but will
+ * appear in the generated stuff.
+ *
+ * @author Andrew C. Oliver (acoliver at apache dot org)
+ * @author Jason Height
+ */
+public class SVBorder extends AbstractBorder {
+ private Color northColor = null;
+ private Color eastColor = null;
+ private Color southColor = null;
+ private Color westColor = null;
+ private BorderStyle northBorderType = BorderStyle.NONE;
+ private BorderStyle eastBorderType = BorderStyle.NONE;
+ private BorderStyle southBorderType = BorderStyle.NONE;
+ private BorderStyle westBorderType = BorderStyle.NONE;
+ private boolean northBorder=false;
+ private boolean eastBorder=false;
+ private boolean southBorder=false;
+ private boolean westBorder=false;
+ private boolean selected = false;
+
+ public void setBorder(Color northColor, Color eastColor,
+ Color southColor, Color westColor,
+ BorderStyle northBorderType, BorderStyle eastBorderType,
+ BorderStyle southBorderType, BorderStyle westBorderType,
+ boolean selected) {
+ this.eastColor = eastColor;
+ this.southColor = southColor;
+ this.westColor = westColor;
+ this.northBorderType = northBorderType;
+ this.eastBorderType = eastBorderType;
+ this.southBorderType = southBorderType;
+ this.westBorderType = westBorderType;
+ this.northBorder=northBorderType != BorderStyle.NONE;
+ this.eastBorder=eastBorderType != BorderStyle.NONE;
+ this.southBorder=southBorderType != BorderStyle.NONE;
+ this.westBorder=westBorderType != BorderStyle.NONE;
+ this.selected = selected;
+ }
+
+ public void paintBorder(Component c, Graphics g, int x, int y, int width,
+ int height) {
+ Color oldColor = g.getColor();
+
+
+ paintSelectedBorder(g, x, y, width, height);
+ paintNormalBorders(g, x, y, width, height);
+ paintDottedBorders(g, x, y, width, height);
+ paintDashedBorders(g, x, y, width, height);
+ paintDoubleBorders(g, x, y, width, height);
+ paintDashDotDotBorders(g, x, y, width, height);
+
+
+ g.setColor(oldColor);
+ }
+
+ /**
+ * Called by paintBorder to paint the border of a selected cell.
+ * The paramaters are the Graphics object, location and dimensions of the
+ * cell.
+ */
+ private void paintSelectedBorder(Graphics g, int x, int y, int width,
+ int height) {
+ if (selected) {
+ //Need to setup thickness of 2
+ g.setColor(Color.black);
+ //paint the border
+ g.drawRect(x,y,width-1,height-1);
+
+ //paint the filled rectangle at the bottom left hand position
+ g.fillRect(x+width-5, y+height-5, 5, 5);
+ }
+ }
+
+
+ /**
+ * Called by paintBorder to paint the various versions of normal line
+ * borders for a cell.
+ */
+ private void paintNormalBorders(Graphics g, int x, int y, int width,
+ int height) {
+
+ if (northBorder &&
+ ((northBorderType == BorderStyle.THIN) ||
+ (northBorderType == BorderStyle.MEDIUM) ||
+ (northBorderType == BorderStyle.THICK)
+ )
+ ) {
+
+ int thickness = getThickness(northBorderType);
+
+ g.setColor(northColor);
+
+ for (int k=0; k < thickness; k++) {
+ g.drawLine(x,y+k,width,y+k);
+ }
+ }
+
+ if (eastBorder &&
+ ((eastBorderType == BorderStyle.THIN) ||
+ (eastBorderType == BorderStyle.MEDIUM) ||
+ (eastBorderType == BorderStyle.THICK)
+ )
+ ) {
+
+ int thickness = getThickness(eastBorderType);
+
+ g.setColor(eastColor);
+
+ for (int k=0; k < thickness; k++) {
+ g.drawLine(width-k,y,width-k,height);
+ }
+ }
+
+ if (southBorder &&
+ ((southBorderType == BorderStyle.THIN) ||
+ (southBorderType == BorderStyle.MEDIUM) ||
+ (southBorderType == BorderStyle.THICK)
+ )
+ ) {
+
+ int thickness = getThickness(southBorderType);
+
+ g.setColor(southColor);
+ for (int k=0; k < thickness; k++) {
+ g.drawLine(x,height - k,width,height - k);
+ }
+ }
+
+ if (westBorder &&
+ ((westBorderType == BorderStyle.THIN) ||
+ (westBorderType == BorderStyle.MEDIUM) ||
+ (westBorderType == BorderStyle.THICK)
+ )
+ ) {
+
+ int thickness = getThickness(westBorderType);
+
+ g.setColor(westColor);
+
+ for (int k=0; k < thickness; k++) {
+ g.drawLine(x+k,y,x+k,height);
+ }
+ }
+ }
+
+ /**
+ * Called by paintBorder to paint the dotted line
+ * borders for a cell.
+ */
+ private void paintDottedBorders(Graphics g, int x, int y, int width,
+ int height) {
+ if (northBorder &&
+ northBorderType == BorderStyle.DOTTED) {
+ int thickness = getThickness(northBorderType);
+
+ g.setColor(northColor);
+
+ for (int k=0; k < thickness; k++) {
+ for (int xc = x; xc < width; xc=xc+2) {
+ g.drawLine(xc,y+k,xc,y+k);
+ }
+ }
+ }
+
+ if (eastBorder &&
+ eastBorderType == BorderStyle.DOTTED
+ ) {
+
+ int thickness = getThickness(eastBorderType);
+ thickness++; //need for dotted borders to show up east
+
+ g.setColor(eastColor);
+
+ for (int k=0; k < thickness; k++) {
+ for (int yc=y;yc < height; yc=yc+2) {
+ g.drawLine(width-k,yc,width-k,yc);
+ }
+ }
+ }
+
+ if (southBorder &&
+ southBorderType == BorderStyle.DOTTED
+ ) {
+
+ int thickness = getThickness(southBorderType);
+ thickness++;
+ g.setColor(southColor);
+ for (int k=0; k < thickness; k++) {
+ for (int xc = x; xc < width; xc=xc+2) {
+ g.drawLine(xc,height-k,xc,height-k);
+ }
+ }
+ }
+
+ if (westBorder &&
+ westBorderType == BorderStyle.DOTTED
+ ) {
+
+ int thickness = getThickness(westBorderType);
+// thickness++;
+
+ g.setColor(westColor);
+
+ for (int k=0; k < thickness; k++) {
+ for (int yc=y;yc < height; yc=yc+2) {
+ g.drawLine(x+k,yc,x+k,yc);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by paintBorder to paint the various versions of dotted line
+ * borders for a cell.
+ */
+ private void paintDashedBorders(Graphics g, int x, int y, int width,
+ int height) {
+ if (northBorder &&
+ ((northBorderType == BorderStyle.DASHED) ||
+ (northBorderType == BorderStyle.HAIR))
+ ) {
+ int thickness = getThickness(northBorderType);
+
+ int dashlength = 1;
+
+ if (northBorderType == BorderStyle.DASHED)
+ dashlength = 2;
+
+ g.setColor(northColor);
+
+ for (int k=0; k < thickness; k++) {
+ for (int xc = x; xc < width; xc=xc+5) {
+ g.drawLine(xc,y+k,xc+dashlength,y+k);
+ }
+ }
+ }
+
+ if (eastBorder &&
+ ((eastBorderType == BorderStyle.DASHED) ||
+ (eastBorderType == BorderStyle.HAIR))
+ ) {
+
+ int thickness = getThickness(eastBorderType);
+ thickness++; //need for dotted borders to show up east
+
+
+ int dashlength = 1;
+
+ if (eastBorderType == BorderStyle.DASHED)
+ dashlength = 2;
+
+ g.setColor(eastColor);
+
+ for (int k=0; k < thickness; k++) {
+ for (int yc=y;yc < height; yc=yc+5) {
+ g.drawLine(width-k,yc,width-k,yc+dashlength);
+ }
+ }
+ }
+
+ if (southBorder &&
+ ((southBorderType == BorderStyle.DASHED) ||
+ (southBorderType == BorderStyle.HAIR))
+ ) {
+
+ int thickness = getThickness(southBorderType);
+ thickness++;
+
+ int dashlength = 1;
+
+ if (southBorderType == BorderStyle.DASHED)
+ dashlength = 2;
+
+ g.setColor(southColor);
+ for (int k=0; k < thickness; k++) {
+ for (int xc = x; xc < width; xc=xc+5) {
+ g.drawLine(xc,height-k,xc+dashlength,height-k);
+ }
+ }
+ }
+
+ if (westBorder &&
+ ((westBorderType == BorderStyle.DASHED) ||
+ (westBorderType == BorderStyle.HAIR))
+ ) {
+
+ int thickness = getThickness(westBorderType);
+// thickness++;
+
+ int dashlength = 1;
+
+ if (westBorderType == BorderStyle.DASHED)
+ dashlength = 2;
+
+ g.setColor(westColor);
+
+ for (int k=0; k < thickness; k++) {
+ for (int yc=y;yc < height; yc=yc+5) {
+ g.drawLine(x+k,yc,x+k,yc+dashlength);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by paintBorder to paint the double line
+ * borders for a cell.
+ */
+ private void paintDoubleBorders(Graphics g, int x, int y, int width,
+ int height) {
+ if (northBorder &&
+ northBorderType == BorderStyle.DOUBLE) {
+
+ g.setColor(northColor);
+
+ int leftx=x;
+ int rightx=width;
+
+ // if there are borders on the west or east then
+ // the second line shouldn't cross them
+ if (westBorder)
+ leftx = x+3;
+
+ if (eastBorder)
+ rightx = width-3;
+
+ g.drawLine(x,y,width,y);
+ g.drawLine(leftx,y+2,rightx,y+2);
+ }
+
+ if (eastBorder &&
+ eastBorderType == BorderStyle.DOUBLE
+ ) {
+
+ int thickness = getThickness(eastBorderType);
+ thickness++; //need for dotted borders to show up east
+
+ g.setColor(eastColor);
+
+ int topy=y;
+ int bottomy=height;
+
+ if (northBorder)
+ topy=y+3;
+
+ if (southBorder)
+ bottomy=height-3;
+
+ g.drawLine(width-1,y,width-1,height);
+ g.drawLine(width-3,topy,width-3,bottomy);
+ }
+
+ if (southBorder &&
+ southBorderType == BorderStyle.DOUBLE
+ ) {
+
+ g.setColor(southColor);
+
+ int leftx=y;
+ int rightx=width;
+
+ if (westBorder)
+ leftx=x+3;
+
+ if (eastBorder)
+ rightx=width-3;
+
+
+ g.drawLine(x,height - 1,width,height - 1);
+ g.drawLine(leftx,height - 3,rightx,height - 3);
+ }
+
+ if (westBorder &&
+ westBorderType == BorderStyle.DOUBLE
+ ) {
+
+ int thickness = getThickness(westBorderType);
+// thickness++;
+
+ g.setColor(westColor);
+
+ int topy=y;
+ int bottomy=height-3;
+
+ if (northBorder)
+ topy=y+2;
+
+ if (southBorder)
+ bottomy=height-3;
+
+ g.drawLine(x,y,x,height);
+ g.drawLine(x+2,topy,x+2,bottomy);
+ }
+ }
+
+ /**
+ * Called by paintBorder to paint the various versions of dash dot dot line
+ * borders for a cell.
+ */
+ private void paintDashDotDotBorders(Graphics g, int x, int y, int width,
+ int height) {
+ if (northBorder &&
+ ((northBorderType == BorderStyle.DASH_DOT_DOT) ||
+ (northBorderType == BorderStyle.MEDIUM_DASH_DOT_DOT))
+ ) {
+ int thickness = getThickness(northBorderType);
+
+ g.setColor(northColor);
+ for (int l=x; l < width;) {
+ l=l+drawDashDotDot(g, l, y, thickness, true, true);
+ }
+
+ }
+
+ if (eastBorder &&
+ ((eastBorderType == BorderStyle.DASH_DOT_DOT) ||
+ (eastBorderType == BorderStyle.MEDIUM_DASH_DOT_DOT))
+ ) {
+
+ int thickness = getThickness(eastBorderType);
+
+ g.setColor(eastColor);
+
+ for (int l=y;l < height;) {
+ //System.err.println("drawing east");
+ l=l+drawDashDotDot(g,width-1,l,thickness,false,false);
+ }
+ }
+
+ if (southBorder &&
+ ((southBorderType == BorderStyle.DASH_DOT_DOT) ||
+ (southBorderType == BorderStyle.MEDIUM_DASH_DOT_DOT))
+ ) {
+
+ int thickness = getThickness(southBorderType);
+
+ g.setColor(southColor);
+
+ for (int l=x; l < width;) {
+ //System.err.println("drawing south");
+ l=l+drawDashDotDot(g, l, height-1, thickness, true, false);
+ }
+ }
+
+ if (westBorder &&
+ ((westBorderType == BorderStyle.DASH_DOT_DOT) ||
+ (westBorderType == BorderStyle.MEDIUM_DASH_DOT_DOT))
+ ) {
+
+ int thickness = getThickness(westBorderType);
+
+ g.setColor(westColor);
+
+ for (int l=y;l < height;) {
+ //System.err.println("drawing west");
+ l=l+drawDashDotDot(g,x,l,thickness,false,true);
+ }
+
+ }
+ }
+
+ /**
+ * Draws one dash dot dot horizontally or vertically with thickness drawn
+ * incrementally to either the right or left.
+ *
+ * @param g graphics object for drawing with
+ * @param x the x origin of the line
+ * @param y the y origin of the line
+ * @param thickness the thickness of the line
+ * @param horizontal or vertical (true for horizontal)
+ * @param right/bottom or left/top thickness (true for right or top),
+ * if true then the x or y origin will be incremented to provide
+ * thickness, if false, they'll be decremented. For vertical
+ * borders, x is incremented or decremented, for horizontal its y.
+ * Just set to true for north and west, and false for east and
+ * south.
+ * @returns length - returns the length of the line.
+ */
+ private int drawDashDotDot(Graphics g,int x, int y, int thickness,
+ boolean horizontal,
+ boolean rightBottom) {
+
+ for (int t=0; t < thickness; t++) {
+ if (!rightBottom) {
+ t = 0 - t; //add negative thickness so we go the other way
+ //then we'll decrement instead of increment.
+ }
+ if (horizontal) {
+ g.drawLine(x,y+t,x+5,y+t);
+ g.drawLine(x+8,y+t,x+10,y+t);
+ g.drawLine(x+13,y+t,x+15,y+t);
+ } else {
+ g.drawLine(x+t,y,x+t,y+5);
+ g.drawLine(x+t,y+8,x+t,y+10);
+ g.drawLine(x+t,y+13,x+t,y+15);
+ }
+ }
+ return 18;
+ }
+
+ /**
+ * @returns the line thickness for a border based on border type
+ */
+ private int getThickness(BorderStyle thickness) {
+ switch (thickness) {
+ case DASH_DOT_DOT:
+ case DASHED:
+ case HAIR:
+ return 1;
+ case THIN:
+ return 2;
+ case MEDIUM:
+ case MEDIUM_DASH_DOT_DOT:
+ return 3;
+ case THICK:
+ return 4;
+ default:
+ return 1;
+ }
+ }
+
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVFractionalFormat.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVFractionalFormat.java
new file mode 100644
index 000000000..cd6ff6ea7
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVFractionalFormat.java
@@ -0,0 +1,220 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.view;
+
+import java.text.*;
+
+/**
+ * This class is used to format cells into their fractional format.
+ *
+ * I cant be 100% sure that the same fractional value will be displayed as in
+ * excel but then again it is a lossy formating mode anyway
+ *
+ * @author Jason Height
+ * @since 15 July 2002
+ */
+public class SVFractionalFormat extends Format {
+ private short ONE_DIGIT = 1;
+ private short TWO_DIGIT = 2;
+ private short THREE_DIGIT = 3;
+ private short UNITS = 4;
+ private int units = 1;
+ private short mode = -1;
+
+ /** Constructs a new FractionalFormatter
+ *
+ * The formatStr defines how the number will be formatted
+ * # ?/? Up to one digit
+ * # ??/?? Up to two digits
+ * # ???/??? Up to three digits
+ * # ?/2 In halves
+ * # ?/4 In quarters
+ * # ?/8 In eighths
+ * # ?/16 In sixteenths
+ * # ?/10 In tenths
+ * # ?/100 In hundredths
+ */
+ public SVFractionalFormat(String formatStr) {
+ if ("# ?/?".equals(formatStr))
+ mode = ONE_DIGIT;
+ else if ("# ??/??".equals(formatStr))
+ mode = TWO_DIGIT;
+ else if ("# ???/???".equals(formatStr))
+ mode = THREE_DIGIT;
+ else if ("# ?/2".equals(formatStr)) {
+ mode = UNITS;
+ units = 2;
+ } else if ("# ?/4".equals(formatStr)) {
+ mode = UNITS;
+ units = 4;
+ } else if ("# ?/8".equals(formatStr)) {
+ mode = UNITS;
+ units = 8;
+ } else if ("# ?/16".equals(formatStr)) {
+ mode = UNITS;
+ units = 16;
+ } else if ("# ?/10".equals(formatStr)) {
+ mode = UNITS;
+ units = 10;
+ } else if ("# ?/100".equals(formatStr)) {
+ mode = UNITS;
+ units = 100;
+ }
+ }
+
+ /**
+ * Returns a fractional string representation of a double to a maximum denominator size
+ *
+ * This code has been translated to java from the following web page.
+ * http://www.codeproject.com/cpp/fraction.asp
+ * Originally coded in c++ By Dean Wyant dwyant@mindspring.com
+ * The code on the web page is freely available.
+ *
+ * @param f Description of the Parameter
+ * @param MaxDen Description of the Parameter
+ * @return Description of the Return Value
+ */
+ private String format(final double f, final int MaxDen) {
+ long Whole = (long)f;
+ int sign = 1;
+ if (f < 0) {
+ sign = -1;
+ }
+ double Precision = 0.00001;
+ double AllowedError = Precision;
+ double d = Math.abs(f);
+ d -= Whole;
+ double Frac = d;
+ double Diff = Frac;
+ long Num = 1;
+ long Den = 0;
+ long A = 0;
+ long B = 0;
+ long i = 0;
+ if (Frac > Precision) {
+ while (true) {
+ d = 1.0 / d;
+ i = (long) (d + Precision);
+ d -= i;
+ if (A > 0) {
+ Num = i * Num + B;
+ }
+ Den = (long) (Num / Frac + 0.5);
+ Diff = Math.abs((double) Num / Den - Frac);
+ if (Den > MaxDen) {
+ if (A > 0) {
+ Num = A;
+ Den = (long) (Num / Frac + 0.5);
+ Diff = Math.abs((double) Num / Den - Frac);
+ } else {
+ Den = MaxDen;
+ Num = 1;
+ Diff = Math.abs((double) Num / Den - Frac);
+ if (Diff > Frac) {
+ Num = 0;
+ Den = 1;
+ // Keeps final check below from adding 1 and keeps Den from being 0
+ Diff = Frac;
+ }
+ }
+ break;
+ }
+ if ((Diff <= AllowedError) || (d < Precision)) {
+ break;
+ }
+ Precision = AllowedError / Diff;
+ // This calcualtion of Precision does not always provide results within
+ // Allowed Error. It compensates for loss of significant digits that occurs.
+ // It helps to round the inprecise reciprocal values to i.
+ B = A;
+ A = Num;
+ }
+ }
+ if (Num == Den) {
+ Whole++;
+ Num = 0;
+ Den = 0;
+ } else if (Den == 0) {
+ Num = 0;
+ }
+ if (sign < 0) {
+ if (Whole == 0) {
+ Num = -Num;
+ } else {
+ Whole = -Whole;
+ }
+ }
+ return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(Den).toString();
+ }
+
+ /** This method formats the double in the units specified.
+ * The usints could be any number but in this current implementation it is
+ * halves (2), quaters (4), eigths (8) etc
+ */
+ private String formatUnit(double f, int units) {
+ long Whole = (long)f;
+ f -= Whole;
+ long Num = Math.round(f * units);
+
+ return new StringBuffer().append(Whole).append(" ").append(Num).append("/").append(units).toString();
+ }
+
+ public final String format(double val) {
+ if (mode == ONE_DIGIT) {
+ return format(val, 9);
+ } else if (mode == TWO_DIGIT) {
+ return format(val, 99);
+ } else if (mode == THREE_DIGIT) {
+ return format(val, 999);
+ } else if (mode == UNITS) {
+ return formatUnit(val , units);
+ }
+ throw new RuntimeException("Unexpected Case");
+ }
+
+ public StringBuffer format(Object obj,
+ StringBuffer toAppendTo,
+ FieldPosition pos) {
+ if (obj instanceof Number) {
+ toAppendTo.append(format(((Number)obj).doubleValue()));
+ return toAppendTo;
+ }
+ throw new IllegalArgumentException("Can only handle Numbers");
+ }
+
+ public Object parseObject(String source,
+ ParsePosition status) {
+ //JMH TBD
+ return null;
+ }
+
+ public Object parseObject(String source)
+ throws ParseException {
+ //JMH TBD
+ return null;
+ }
+
+ public Object clone() {
+ //JMH TBD
+ return null;
+ }
+
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVRowHeader.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVRowHeader.java
new file mode 100644
index 000000000..c6db2f71a
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVRowHeader.java
@@ -0,0 +1,96 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hssf.view;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.*;
+
+/**
+ * This class presents the row header to the table.
+ *
+ *
+ * @author Jason Height
+ */
+public class SVRowHeader extends JList {
+ /** This model simply returns an integer number up to the number of rows
+ * that are present in the sheet.
+ *
+ */
+ private class SVRowHeaderModel extends AbstractListModel {
+ private HSSFSheet sheet;
+
+ public SVRowHeaderModel(HSSFSheet sheet) {
+ this.sheet = sheet;
+ }
+
+ public int getSize() {
+ return sheet.getLastRowNum() + 1;
+ }
+ public Object getElementAt(int index) {
+ return Integer.toString(index+1);
+ }
+ }
+
+ /** Renderes the row number*/
+ private class RowHeaderRenderer extends JLabel implements ListCellRenderer {
+ private HSSFSheet sheet;
+ private int extraHeight;
+
+ RowHeaderRenderer(HSSFSheet sheet, JTable table, int extraHeight) {
+ this.sheet = sheet;
+ this.extraHeight = extraHeight;
+ JTableHeader header = table.getTableHeader();
+ setOpaque(true);
+ setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+ setHorizontalAlignment(CENTER);
+ setForeground(header.getForeground());
+ setBackground(header.getBackground());
+ setFont(header.getFont());
+ }
+
+ public Component getListCellRendererComponent( JList list,
+ Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ Dimension d = getPreferredSize();
+ HSSFRow row = sheet.getRow(index);
+ int rowHeight;
+ if(row == null) {
+ rowHeight = (int)sheet.getDefaultRowHeightInPoints();
+ } else {
+ rowHeight = (int)row.getHeightInPoints();
+ }
+ d.height = rowHeight+extraHeight;
+ setPreferredSize(d);
+ setText((value == null) ? "" : value.toString());
+ return this;
+ }
+ }
+
+ public SVRowHeader(HSSFSheet sheet, JTable table, int extraHeight) {
+ ListModel lm = new SVRowHeaderModel(sheet);
+ this.setModel(lm);
+
+ setFixedCellWidth(50);
+ setCellRenderer(new RowHeaderRenderer(sheet, table, extraHeight));
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVSheetTable.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVSheetTable.java
new file mode 100644
index 000000000..8f95e03fb
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVSheetTable.java
@@ -0,0 +1,258 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.view;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JViewport;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.table.TableModel;
+import javax.swing.text.JTextComponent;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.view.brush.PendingPaintings;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Row;
+
+/**
+ * This class is a table that represents the values in a single worksheet.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class SVSheetTable extends JTable {
+ private final HSSFSheet sheet;
+ private final PendingPaintings pendingPaintings;
+ private FormulaDisplayListener formulaListener;
+ private JScrollPane scroll;
+
+ private static final Color HEADER_BACKGROUND = new Color(235, 235, 235);
+
+ /**
+ * This field is the magic number to convert from a Character width to a java
+ * pixel width.
+ *
+ * When the "normal" font size in a workbook changes, this effects all of the
+ * heights and widths. Unfortunately there is no way to retrieve this
+ * information, hence the MAGIC number.
+ *
+ * This number may only work for the normal style font size of Arial size 10.
+ */
+ private static final int magicCharFactor = 7;
+
+ private class HeaderCell extends JLabel {
+ private final int row;
+
+ public HeaderCell(Object value, int row) {
+ super(value.toString(), CENTER);
+ this.row = row;
+ setBackground(HEADER_BACKGROUND);
+ setOpaque(true);
+ setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
+ setRowSelectionAllowed(false);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension d = super.getPreferredSize();
+ if (row >= 0) {
+ d.height = getRowHeight(row);
+ }
+ return d;
+ }
+
+ @Override
+ public Dimension getMaximumSize() {
+ Dimension d = super.getMaximumSize();
+ if (row >= 0) {
+ d.height = getRowHeight(row);
+ }
+ return d;
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ Dimension d = super.getMinimumSize();
+ if (row >= 0) {
+ d.height = getRowHeight(row);
+ }
+ return d;
+ }
+ }
+
+ private class HeaderCellRenderer implements TableCellRenderer {
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column) {
+
+ return new HeaderCell(value, row);
+ }
+ }
+
+ private class FormulaDisplayListener implements ListSelectionListener {
+ private final JTextComponent formulaDisplay;
+
+ public FormulaDisplayListener(JTextComponent formulaDisplay) {
+ this.formulaDisplay = formulaDisplay;
+ }
+
+ public void valueChanged(ListSelectionEvent e) {
+ int row = getSelectedRow();
+ int col = getSelectedColumn();
+ if (row < 0 || col < 0) {
+ return;
+ }
+
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+
+ HSSFCell cell = (HSSFCell) getValueAt(row, col);
+ String formula = "";
+ if (cell != null) {
+ if (cell.getCellTypeEnum() == CellType.FORMULA) {
+ formula = cell.getCellFormula();
+ } else {
+ formula = cell.toString();
+ }
+ if (formula == null)
+ formula = "";
+ }
+ formulaDisplay.setText(formula);
+ }
+ }
+
+ public SVSheetTable(HSSFSheet sheet) {
+ super(new SVTableModel(sheet));
+ this.sheet = sheet;
+
+ setIntercellSpacing(new Dimension(0, 0));
+ setAutoResizeMode(AUTO_RESIZE_OFF);
+ JTableHeader header = getTableHeader();
+ header.setDefaultRenderer(new HeaderCellRenderer());
+ pendingPaintings = new PendingPaintings(this);
+
+ //Set the columns the correct size
+ TableColumnModel columns = getColumnModel();
+ for (int i = 0; i < columns.getColumnCount(); i++) {
+ TableColumn column = columns.getColumn(i);
+ int width = sheet.getColumnWidth(i);
+ //256 is because the width is in 256ths of a character
+ column.setPreferredWidth(width / 256 * magicCharFactor);
+ }
+
+ Toolkit t = getToolkit();
+ int res = t.getScreenResolution();
+ TableModel model = getModel();
+ for (int i = 0; i < model.getRowCount(); i++) {
+ Row row = sheet.getRow(i - sheet.getFirstRowNum());
+ if (row != null) {
+ short h = row.getHeight();
+ int height = (int)Math.round(Math.max(1., h / (res / 70. * 20.) + 3.));
+ System.out.printf("%d: %d (%d @ %d)%n", i, height, h, res);
+ setRowHeight(i, height);
+ }
+ }
+
+ addHierarchyListener(new HierarchyListener() {
+ public void hierarchyChanged(HierarchyEvent e) {
+ if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) != 0) {
+ Container changedParent = e.getChangedParent();
+ if (changedParent instanceof JViewport) {
+ Container grandparent = changedParent.getParent();
+ if (grandparent instanceof JScrollPane) {
+ JScrollPane jScrollPane = (JScrollPane) grandparent;
+ setupScroll(jScrollPane);
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public void setupScroll(JScrollPane scroll) {
+ if (scroll == this.scroll)
+ return;
+
+ this.scroll = scroll;
+ if (scroll == null)
+ return;
+
+ SVRowHeader rowHeader = new SVRowHeader(sheet, this, 0);
+ scroll.setRowHeaderView(rowHeader);
+ scroll.setCorner(JScrollPane.UPPER_LEADING_CORNER, headerCell("?"));
+ }
+
+ public void setFormulaDisplay(JTextComponent formulaDisplay) {
+ ListSelectionModel rowSelMod = getSelectionModel();
+ ListSelectionModel colSelMod = getColumnModel().getSelectionModel();
+
+ if (formulaDisplay == null) {
+ rowSelMod.removeListSelectionListener(formulaListener);
+ colSelMod.removeListSelectionListener(formulaListener);
+ formulaListener = null;
+ }
+
+ if (formulaDisplay != null) {
+ formulaListener = new FormulaDisplayListener(formulaDisplay);
+ rowSelMod.addListSelectionListener(formulaListener);
+ colSelMod.addListSelectionListener(formulaListener);
+ }
+ }
+
+ public JTextComponent getFormulaDisplay() {
+ if (formulaListener == null)
+ return null;
+ else
+ return formulaListener.formulaDisplay;
+ }
+
+ public Component headerCell(String text) {
+ return new HeaderCell(text, -1);
+ }
+
+ @Override
+ public void paintComponent(Graphics g1) {
+ Graphics2D g = (Graphics2D) g1;
+
+ pendingPaintings.clear();
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ super.paintComponent(g);
+
+ pendingPaintings.paint(g);
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableCellEditor.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableCellEditor.java
new file mode 100644
index 000000000..6c1552e54
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableCellEditor.java
@@ -0,0 +1,212 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.view;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.util.EventObject;
+import java.util.Map;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+import javax.swing.table.TableCellEditor;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.FillPatternType;
+
+/**
+ * Sheet Viewer Table Cell Editor -- not commented via javadoc as it
+ * nearly completely consists of overridden methods.
+ *
+ * @author Jason Height
+ */
+public class SVTableCellEditor extends AbstractCellEditor implements TableCellEditor, ActionListener {
+ private static final Color black = getAWTColor(new HSSFColor.BLACK());
+ private static final Color white = getAWTColor(new HSSFColor.WHITE());
+ private Map colors = HSSFColor.getIndexHash();
+
+
+ private HSSFWorkbook wb;
+ private JTextField editor;
+
+ public SVTableCellEditor(HSSFWorkbook wb) {
+ this.wb = wb;
+ this.editor = new JTextField();
+ }
+
+
+ /**
+ * Gets the cellEditable attribute of the SVTableCellEditor object
+ *
+ * @return The cellEditable value
+ */
+ public boolean isCellEditable(java.util.EventObject e) {
+ if (e instanceof MouseEvent) {
+ return ((MouseEvent) e).getClickCount() >= 2;
+ }
+ return false;
+ }
+
+
+ public boolean shouldSelectCell(EventObject anEvent) {
+ return true;
+ }
+
+
+ public boolean startCellEditing(EventObject anEvent) {
+ System.out.println("Start Cell Editing");
+ return true;
+ }
+
+
+ public boolean stopCellEditing() {
+ System.out.println("Stop Cell Editing");
+ fireEditingStopped();
+ return true;
+ }
+
+
+ public void cancelCellEditing() {
+ System.out.println("Cancel Cell Editing");
+ fireEditingCanceled();
+ }
+
+
+ public void actionPerformed(ActionEvent e) {
+ System.out.println("Action performed");
+ stopCellEditing();
+ }
+
+
+ /**
+ * Gets the cellEditorValue attribute of the SVTableCellEditor object
+ *
+ * @return The cellEditorValue value
+ */
+ public Object getCellEditorValue() {
+ System.out.println("GetCellEditorValue");
+ //JMH Look at when this method is called. Should it return a HSSFCell?
+ return editor.getText();
+ }
+
+
+ /**
+ * Gets the tableCellEditorComponent attribute of the SVTableCellEditor object
+ *
+ * @return The tableCellEditorComponent value
+ */
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected,
+ int row,
+ int column) {
+ System.out.println("GetTableCellEditorComponent");
+ HSSFCell cell = (HSSFCell) value;
+ if (cell != null) {
+ HSSFCellStyle style = cell.getCellStyle();
+ HSSFFont f = wb.getFontAt(style.getFontIndex());
+ boolean isbold = f.getBold();
+ boolean isitalics = f.getItalic();
+
+ int fontstyle = Font.PLAIN;
+
+ if (isbold) fontstyle = Font.BOLD;
+ if (isitalics) fontstyle = fontstyle | Font.ITALIC;
+
+ int fontheight = f.getFontHeightInPoints();
+ if (fontheight == 9) fontheight = 10; //fix for stupid ol Windows
+
+ Font font = new Font(f.getFontName(),fontstyle,fontheight);
+ editor.setFont(font);
+
+ if (style.getFillPatternEnum() == FillPatternType.SOLID_FOREGROUND) {
+ editor.setBackground(getAWTColor(style.getFillForegroundColor(), white));
+ } else editor.setBackground(white);
+
+ editor.setForeground(getAWTColor(f.getColor(), black));
+
+
+ //Set the value that is rendered for the cell
+ switch (cell.getCellTypeEnum()) {
+ case BLANK:
+ editor.setText("");
+ break;
+ case BOOLEAN:
+ if (cell.getBooleanCellValue()) {
+ editor.setText("true");
+ } else {
+ editor.setText("false");
+ }
+ break;
+ case NUMERIC:
+ editor.setText(Double.toString(cell.getNumericCellValue()));
+ break;
+ case STRING:
+ editor.setText(cell.getRichStringCellValue().getString());
+ break;
+ case FORMULA:
+ default:
+ editor.setText("?");
+ }
+ switch (style.getAlignmentEnum()) {
+ case LEFT:
+ case JUSTIFY:
+ case FILL:
+ editor.setHorizontalAlignment(SwingConstants.LEFT);
+ break;
+ case CENTER:
+ case CENTER_SELECTION:
+ editor.setHorizontalAlignment(SwingConstants.CENTER);
+ break;
+ case GENERAL:
+ case RIGHT:
+ editor.setHorizontalAlignment(SwingConstants.RIGHT);
+ break;
+ default:
+ editor.setHorizontalAlignment(SwingConstants.LEFT);
+ break;
+ }
+
+ }
+ return editor;
+ }
+
+ /** This method retrieves the AWT Color representation from the colour hash table
+ *
+ */
+ private final Color getAWTColor(int index, Color deflt) {
+ HSSFColor clr = colors.get(index);
+ if (clr == null) return deflt;
+ return getAWTColor(clr);
+ }
+
+ private static final Color getAWTColor(HSSFColor clr) {
+ short[] rgb = clr.getTriplet();
+ return new Color(rgb[0],rgb[1],rgb[2]);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableCellRenderer.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableCellRenderer.java
new file mode 100644
index 000000000..ac0075b16
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableCellRenderer.java
@@ -0,0 +1,276 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.view;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.io.Serializable;
+import java.text.DecimalFormat;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.table.TableCellRenderer;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFFont;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.FillPatternType;
+
+
+/**
+ * Sheet Viewer Table Cell Render -- not commented via javadoc as it
+ * nearly completely consists of overridden methods.
+ *
+ * @author Andrew C. Oliver
+ */
+public class SVTableCellRenderer extends JLabel
+ implements TableCellRenderer, Serializable
+{
+ protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
+ protected SVBorder cellBorder = new SVBorder();
+
+
+ private HSSFWorkbook wb = null;
+
+ /** This class holds the references to the predefined cell formats.
+ */
+ private class CellFormatter {
+ private Format[] textFormatter;
+
+ private DecimalFormat generalNumberFormat = new DecimalFormat("0");
+
+ public CellFormatter() {
+ textFormatter = new Format[0x31];
+
+ textFormatter[0x01] = new DecimalFormat("0");
+ textFormatter[0x02] = new DecimalFormat("0.00");
+ textFormatter[0x03] = new DecimalFormat("#,##0");
+ textFormatter[0x04] = new DecimalFormat("#,##0.00");
+ textFormatter[0x05] = new DecimalFormat("$#,##0;$#,##0");
+ textFormatter[0x06] = new DecimalFormat("$#,##0;$#,##0");
+ textFormatter[0x07] = new DecimalFormat("$#,##0.00;$#,##0.00");
+ textFormatter[0x08] = new DecimalFormat("$#,##0.00;$#,##0.00");
+ textFormatter[0x09] = new DecimalFormat("0%");
+ textFormatter[0x0A] = new DecimalFormat("0.00%");
+ textFormatter[0x0B] = new DecimalFormat("0.00E0");
+ textFormatter[0x0C] = new SVFractionalFormat("# ?/?");
+ textFormatter[0x0D] = new SVFractionalFormat("# ??/??");
+ textFormatter[0x0E] = new SimpleDateFormat("M/d/yy");
+ textFormatter[0x0F] = new SimpleDateFormat("d-MMM-yy");
+ textFormatter[0x10] = new SimpleDateFormat("d-MMM");
+ textFormatter[0x11] = new SimpleDateFormat("MMM-yy");
+ textFormatter[0x12] = new SimpleDateFormat("h:mm a");
+ textFormatter[0x13] = new SimpleDateFormat("h:mm:ss a");
+ textFormatter[0x14] = new SimpleDateFormat("h:mm");
+ textFormatter[0x15] = new SimpleDateFormat("h:mm:ss");
+ textFormatter[0x16] = new SimpleDateFormat("M/d/yy h:mm");
+ // 0x17 - 0x24 reserved for international and undocumented 0x25, "(#,##0_);(#,##0)"
+ //start at 0x26
+ //jmh need to do colour
+ //"(#,##0_);[Red](#,##0)"
+ textFormatter[0x26] = new DecimalFormat("#,##0;#,##0");
+ //jmh need to do colour
+ //(#,##0.00_);(#,##0.00)
+ textFormatter[0x27] = new DecimalFormat("#,##0.00;#,##0.00");
+ textFormatter[0x28] = new DecimalFormat("#,##0.00;#,##0.00");
+//?? textFormatter[0x29] = new DecimalFormat("_(*#,##0_);_(*(#,##0);_(* \"-\"_);_(@_)");
+//?? textFormatter[0x2A] = new DecimalFormat("_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)");
+//?? textFormatter[0x2B] = new DecimalFormat("_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)");
+//?? textFormatter[0x2C] = new DecimalFormat("_($*#,##0.00_);_($*(#,##0.00);_($*\"-\"??_);_(@_)");
+ textFormatter[0x2D] = new SimpleDateFormat("mm:ss");
+//?? textFormatter[0x2E] = new SimpleDateFormat("[h]:mm:ss");
+ textFormatter[0x2F] = new SimpleDateFormat("mm:ss.0");
+ textFormatter[0x30] = new DecimalFormat("##0.0E0");
+ }
+
+ public String format(short index, double value) {
+ if ( index <= 0 )
+ return generalNumberFormat.format(value);
+ if (textFormatter[index] == null)
+ throw new RuntimeException("Sorry. I cant handle the format code :"+Integer.toHexString(index));
+ if (textFormatter[index] instanceof DecimalFormat) {
+ return ((DecimalFormat)textFormatter[index]).format(value);
+ }
+ if (textFormatter[index] instanceof SVFractionalFormat) {
+ return ((SVFractionalFormat)textFormatter[index]).format(value);
+ }
+ throw new RuntimeException("Sorry. I cant handle a non decimal formatter for a decimal value :"+Integer.toHexString(index));
+ }
+
+ public boolean useRedColor(short index, double value) {
+ return (((index == 0x06)||(index == 0x08)||(index == 0x26) || (index == 0x27)) && (value < 0));
+ }
+ }
+
+ private final CellFormatter cellFormatter = new CellFormatter();
+
+ public SVTableCellRenderer(HSSFWorkbook wb) {
+ super();
+ setOpaque(true);
+ setBorder(noFocusBorder);
+ this.wb = wb;
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column) {
+ boolean isBorderSet = false;
+
+ //If the JTables default cell renderer has been setup correctly the
+ //value will be the HSSFCell that we are trying to render
+ HSSFCell c = (HSSFCell)value;
+
+ if (c != null) {
+ HSSFCellStyle s = c.getCellStyle();
+ HSSFFont f = wb.getFontAt(s.getFontIndex());
+ setFont(SVTableUtils.makeFont(f));
+
+ if (s.getFillPatternEnum() == FillPatternType.SOLID_FOREGROUND) {
+ setBackground(SVTableUtils.getAWTColor(s.getFillForegroundColor(), SVTableUtils.white));
+ } else setBackground(SVTableUtils.white);
+
+ setForeground(SVTableUtils.getAWTColor(f.getColor(), SVTableUtils.black));
+
+ cellBorder.setBorder(SVTableUtils.getAWTColor(s.getTopBorderColor(), SVTableUtils.black),
+ SVTableUtils.getAWTColor(s.getRightBorderColor(), SVTableUtils.black),
+ SVTableUtils.getAWTColor(s.getBottomBorderColor(), SVTableUtils.black),
+ SVTableUtils.getAWTColor(s.getLeftBorderColor(), SVTableUtils.black),
+ s.getBorderTop(), s.getBorderRight(),
+ s.getBorderBottom(), s.getBorderLeft(),
+ hasFocus);
+ setBorder(cellBorder);
+ isBorderSet=true;
+
+ //Set the value that is rendered for the cell
+ switch (c.getCellTypeEnum()) {
+ case BLANK:
+ setValue("");
+ break;
+ case BOOLEAN:
+ if (c.getBooleanCellValue()) {
+ setValue("true");
+ } else {
+ setValue("false");
+ }
+ break;
+ case NUMERIC:
+ short format = s.getDataFormat();
+ double numericValue = c.getNumericCellValue();
+ if (cellFormatter.useRedColor(format, numericValue))
+ setForeground(Color.red);
+ else setForeground(null);
+ setValue(cellFormatter.format(format, c.getNumericCellValue()));
+ break;
+ case STRING:
+ setValue(c.getRichStringCellValue().getString());
+ break;
+ case FORMULA:
+ default:
+ setValue("?");
+ }
+ //Set the text alignment of the cell
+ switch (s.getAlignmentEnum()) {
+ case LEFT:
+ case JUSTIFY:
+ case FILL:
+ setHorizontalAlignment(SwingConstants.LEFT);
+ break;
+ case CENTER:
+ case CENTER_SELECTION:
+ setHorizontalAlignment(SwingConstants.CENTER);
+ break;
+ case GENERAL:
+ case RIGHT:
+ setHorizontalAlignment(SwingConstants.RIGHT);
+ break;
+ default:
+ setHorizontalAlignment(SwingConstants.LEFT);
+ break;
+ }
+ } else {
+ setValue("");
+ setBackground(SVTableUtils.white);
+ }
+
+
+ if (hasFocus) {
+ if (!isBorderSet) {
+ //This is the border to paint when there is no border
+ //and the cell has focus
+ cellBorder.setBorder(SVTableUtils.black,
+ SVTableUtils.black,
+ SVTableUtils.black,
+ SVTableUtils.black,
+ BorderStyle.NONE,
+ BorderStyle.NONE,
+ BorderStyle.NONE,
+ BorderStyle.NONE,
+ isSelected);
+ setBorder(cellBorder);
+ }
+ if (table.isCellEditable(row, column)) {
+ setForeground( UIManager.getColor("Table.focusCellForeground") );
+ setBackground( UIManager.getColor("Table.focusCellBackground") );
+ }
+ } else if (!isBorderSet) {
+ setBorder(noFocusBorder);
+ }
+
+ // ---- begin optimization to avoid painting background ----
+ Color back = getBackground();
+ boolean colorMatch = (back != null) && ( back.equals(table.getBackground()) ) && table.isOpaque();
+ setOpaque(!colorMatch);
+ // ---- end optimization to aviod painting background ----
+ return this;
+ }
+
+ public void validate() {}
+
+ public void revalidate() {}
+
+ public void repaint(long tm, int x, int y, int width, int height) {}
+
+ public void repaint(Rectangle r) { }
+
+ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
+ // Strings get interned...
+ if (propertyName=="text") {
+ super.firePropertyChange(propertyName, oldValue, newValue);
+ }
+ }
+
+ public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }
+
+ /**
+ * Sets the string to either the value or "" if the value is null.
+ *
+ */
+ protected void setValue(Object value) {
+ setText((value == null) ? "" : value.toString());
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableModel.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableModel.java
new file mode 100644
index 000000000..5868a175d
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableModel.java
@@ -0,0 +1,88 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hssf.view;
+
+import java.util.Iterator;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+
+/**
+ * Sheet Viewer Table Model - The model for the Sheet Viewer just overrides things.
+ * @author Andrew C. Oliver
+ */
+
+public class SVTableModel extends AbstractTableModel {
+ private HSSFSheet st = null;
+ int maxcol = 0;
+
+ public SVTableModel(HSSFSheet st, int maxcol) {
+ this.st = st;
+ this.maxcol=maxcol;
+ }
+
+ public SVTableModel(HSSFSheet st) {
+ this.st = st;
+ Iterator i = st.rowIterator();
+
+ while (i.hasNext()) {
+ HSSFRow row = (HSSFRow)i.next();
+ if (maxcol < (row.getLastCellNum()+1)) {
+ this.maxcol = row.getLastCellNum();
+ }
+ }
+ }
+
+
+ public int getColumnCount() {
+ return this.maxcol+1;
+ }
+ public Object getValueAt(int row, int col) {
+ HSSFRow r = st.getRow(row);
+ HSSFCell c = null;
+ if (r != null) {
+ c = r.getCell(col);
+ }
+ return c;
+ }
+ public int getRowCount() {
+ return st.getLastRowNum() + 1;
+ }
+
+ public Class> getColumnClass(int c) {
+ return HSSFCell.class;
+ }
+
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return true;
+ }
+
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ if (aValue != null)
+ System.out.println("SVTableModel.setValueAt. value type = "+aValue.getClass().getName());
+ else System.out.println("SVTableModel.setValueAt. value type = null");
+ }
+
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableUtils.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableUtils.java
new file mode 100644
index 000000000..395f1cdb8
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SVTableUtils.java
@@ -0,0 +1,93 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.view;
+
+import java.util.*;
+import java.awt.*;
+import javax.swing.border.*;
+
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.hssf.util.*;
+
+/**
+ * SVTableCell Editor and Renderer helper functions.
+ *
+ * @author Jason Height
+ */
+public class SVTableUtils {
+ private final static Map colors = HSSFColor.getIndexHash();
+ /** Description of the Field */
+ public final static Color black = getAWTColor(new HSSFColor.BLACK());
+ /** Description of the Field */
+ public final static Color white = getAWTColor(new HSSFColor.WHITE());
+ /** Description of the Field */
+ public static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
+
+
+ /**
+ * Creates a new font for a specific cell style
+ */
+ public static Font makeFont(HSSFFont font) {
+ boolean isbold = font.getBoldweight() > HSSFFont.BOLDWEIGHT_NORMAL;
+ boolean isitalics = font.getItalic();
+ int fontstyle = Font.PLAIN;
+ if (isbold) {
+ fontstyle = Font.BOLD;
+ }
+ if (isitalics) {
+ fontstyle = fontstyle | Font.ITALIC;
+ }
+
+ int fontheight = font.getFontHeightInPoints();
+ if (fontheight == 9) {
+ //fix for stupid ol Windows
+ fontheight = 10;
+ }
+
+ return new Font(font.getFontName(), fontstyle, fontheight);
+ }
+
+
+ /**
+ * This method retrieves the AWT Color representation from the colour hash table
+ *
+ * @param index Description of the Parameter
+ * @param deflt Description of the Parameter
+ * @return The aWTColor value
+ */
+ public final static Color getAWTColor(int index, Color deflt) {
+ HSSFColor clr = colors.get(index);
+ if (clr == null) {
+ return deflt;
+ }
+ return getAWTColor(clr);
+ }
+
+
+ /**
+ * Gets the aWTColor attribute of the SVTableUtils class
+ *
+ * @param clr Description of the Parameter
+ * @return The aWTColor value
+ */
+ public final static Color getAWTColor(HSSFColor clr) {
+ short[] rgb = clr.getTriplet();
+ return new Color(rgb[0], rgb[1], rgb[2]);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SViewer.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SViewer.java
new file mode 100644
index 000000000..de2cfb1f6
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SViewer.java
@@ -0,0 +1,172 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+
+package org.apache.poi.hssf.view;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.io.*;
+import javax.swing.*;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Sheet Viewer - Views XLS files via HSSF. Can be used as an applet with
+ * filename="" or as a applications (pass the filename as the first parameter).
+ * Or you can pass it a URL in a "url" parameter when run as an applet or just
+ * that first parameter must start with http:// and it will guess its a url. I
+ * only tested it as an applet though, so it probably won't work...you fix it.
+ *
+ * @author Andrew C. Oliver
+ * @author Jason Height
+ */
+public class SViewer extends JApplet {
+ private SViewerPanel panel;
+ boolean isStandalone = false;
+ String filename = null;
+
+ /**Get a parameter value*/
+ public String getParameter(String key, String def) {
+ return isStandalone ? System.getProperty(key, def) :
+ (getParameter(key) != null ? getParameter(key) : def);
+ }
+
+ /**Construct the applet*/
+ public SViewer() {
+ }
+
+ /**Initialize the applet*/
+ public void init() {
+ try {
+ jbInit();
+ }
+ catch(Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ /**Component initialization*/
+ private void jbInit() throws Exception {
+ InputStream i = null;
+ boolean isurl = false;
+ if (filename == null) filename = getParameter("filename");
+
+ if (filename == null || filename.substring(0,7).equals("http://")) {
+ isurl = true;
+ if (filename == null) filename = getParameter("url");
+ i = getXLSFromURL(filename);
+ }
+
+ HSSFWorkbook wb = null;
+ if (isurl) {
+ wb = constructWorkbook(i);
+ } else {
+ wb = constructWorkbook(filename);
+ }
+ panel = new SViewerPanel(wb, false);
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(panel, BorderLayout.CENTER);
+ }
+
+ private HSSFWorkbook constructWorkbook(String filename) throws FileNotFoundException, IOException {
+ HSSFWorkbook wb = null;
+ FileInputStream in = new FileInputStream(filename);
+ wb = new HSSFWorkbook(in);
+ in.close();
+ return wb;
+ }
+
+ private HSSFWorkbook constructWorkbook(InputStream in) throws IOException {
+ HSSFWorkbook wb = null;
+
+ wb = new HSSFWorkbook(in);
+ in.close();
+ return wb;
+ }
+
+ /**Start the applet*/
+ public void start() {
+ }
+ /**Stop the applet*/
+ public void stop() {
+ }
+ /**Destroy the applet*/
+ public void destroy() {
+ }
+ /**Get Applet information*/
+ public String getAppletInfo() {
+ return "Applet Information";
+ }
+ /**Get parameter info*/
+ public String[][] getParameterInfo() {
+ return null;
+ }
+
+ /**
+ * opens a url and returns an inputstream
+ *
+ */
+ private InputStream getXLSFromURL(String urlstring) throws MalformedURLException, IOException {
+ URL url = new URL(urlstring);
+ URLConnection uc = url.openConnection();
+ String field = uc.getHeaderField(0);
+ for (int i=0;field != null; i++) {
+ System.out.println(field);
+ field = uc.getHeaderField(i);
+ }
+ BufferedInputStream is = new BufferedInputStream(uc.getInputStream());
+ return is;
+ }
+
+
+ /**Main method*/
+ public static void main(String[] args) {
+ if(args.length < 1) {
+ throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given");
+ }
+
+ SViewer applet = new SViewer();
+ applet.isStandalone = true;
+ applet.filename = args[0];
+ Frame frame;
+ frame = new Frame() {
+ protected void processWindowEvent(WindowEvent e) {
+ super.processWindowEvent(e);
+ if (e.getID() == WindowEvent.WINDOW_CLOSING) {
+ System.exit(0);
+ }
+ }
+ public synchronized void setTitle(String title) {
+ super.setTitle(title);
+ enableEvents(AWTEvent.WINDOW_EVENT_MASK);
+ }
+ };
+ frame.setTitle("Applet Frame");
+ frame.add(applet, BorderLayout.CENTER);
+ applet.init();
+ applet.start();
+ frame.setSize(400,320);
+ Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+ frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
+ frame.setVisible(true);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/SViewerPanel.java b/trunk/src/examples/src/org/apache/poi/hssf/view/SViewerPanel.java
new file mode 100644
index 000000000..5fe596220
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/SViewerPanel.java
@@ -0,0 +1,292 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.view;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.table.*;
+
+import org.apache.poi.hssf.usermodel.*;
+
+/**
+ * This class presents the sheets to the user.
+ *
+ *
+ * @author Andrew C. Oliver
+ * @author Jason Height
+ */
+public class SViewerPanel extends JPanel {
+ /** This field is the magic number to convert from a Character width to a
+ * java pixel width.
+ *
+ * When the "normal" font size in a workbook changes, this effects all
+ * of the heights and widths. Unfortunately there is no way to retrieve this
+ * information, hence the MAGIC number.
+ *
+ * This number may only work for the normal style font size of Arial size 10.
+ *
+ */
+ private static final int magicCharFactor = 7;
+ /** Reference to the wookbook that is being displayed*/
+ /* package */ HSSFWorkbook wb;
+ /** Reference to the tabs component*/
+ /* package */ JTabbedPane sheetPane;
+ /** Reference to the cell renderer that is used to render all cells*/
+ private SVTableCellRenderer cellRenderer;
+ /** Reference to the cell editor that is used to edit all cells.
+ * Only constructed if editing is allowed
+ */
+ private SVTableCellEditor cellEditor;
+ /** Flag indicating if editing is allowed. Otherwise the viewer is in
+ * view only mode.
+ */
+ private boolean allowEdits;
+
+ /**Construct the representation of the workbook*/
+ public SViewerPanel(HSSFWorkbook wb, boolean allowEdits) {
+ this.wb = wb;
+ this.allowEdits = allowEdits;
+
+ initialiseGui();
+ }
+
+ private void initialiseGui() {
+ cellRenderer = new SVTableCellRenderer(this.wb);
+ if (allowEdits)
+ cellEditor = new SVTableCellEditor(this.wb);
+
+ //Initialise the Panel
+ sheetPane = new JTabbedPane(JTabbedPane.BOTTOM);
+
+ if (allowEdits)
+ sheetPane.addMouseListener(createTabListener());
+ int sheetCount = wb.getNumberOfSheets();
+ for (int i=0; i
+ * It is up to the parent component to invoke the {@link #paint(Graphics2D)}
+ * method of this objet at that appropriate time.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class PendingPaintings {
+ /**
+ * The name of the client property that holds this object in the parent
+ * component.
+ */
+ public static final String PENDING_PAINTINGS =
+ PendingPaintings.class.getSimpleName();
+
+ private final List paintings;
+
+ /** A single painting description. */
+ public static class Painting {
+ final Stroke stroke;
+ final Color color;
+ final Shape shape;
+ final AffineTransform transform;
+
+ /**
+ * Creates a new painting description.
+ *
+ * @param stroke The stroke to paint.
+ * @param color The color of the stroke.
+ * @param shape The shape of the stroke.
+ * @param transform The transformation matrix to use.
+ */
+ public Painting(Stroke stroke, Color color, Shape shape,
+ AffineTransform transform) {
+
+ this.color = color;
+ this.shape = shape;
+ this.stroke = stroke;
+ this.transform = transform;
+ }
+
+ /**
+ * Draw the painting.
+ *
+ * @param g The graphics object to use to draw with.
+ */
+ public void draw(Graphics2D g) {
+ g.setTransform(transform);
+ g.setStroke(stroke);
+ g.setColor(color);
+ g.draw(shape);
+ }
+ }
+
+ /**
+ * Creates a new object on the given parent. The created object will be
+ * stored as a client property.
+ *
+ * @param parent
+ */
+ public PendingPaintings(JComponent parent) {
+ paintings = new ArrayList();
+ parent.putClientProperty(PENDING_PAINTINGS, this);
+ }
+
+ /** Drops all pending paintings. */
+ public void clear() {
+ paintings.clear();
+ }
+
+ /**
+ * Paints all pending paintings. Once they have been painted they are
+ * removed from the list of pending paintings (they aren't pending anymore,
+ * after all).
+ *
+ * @param g The graphics object to draw with.
+ */
+ public void paint(Graphics2D g) {
+ g.setBackground(Color.CYAN);
+ AffineTransform origTransform = g.getTransform();
+ for (Painting c : paintings) {
+ c.draw(g);
+ }
+ g.setTransform(origTransform);
+
+ clear();
+ }
+
+ /**
+ * Adds a new pending painting to the list on the given component. This
+ * will find the first ancestor that has a {@link PendingPaintings} client
+ * property, starting with the component itself.
+ *
+ * @param c The component for which the painting is being added.
+ * @param g The graphics object to draw with.
+ * @param stroke The stroke to draw.
+ * @param color The color to draw with.
+ * @param shape The shape to stroke.
+ */
+ public static void add(JComponent c, Graphics2D g, Stroke stroke,
+ Color color, Shape shape) {
+
+ add(c, new Painting(stroke, color, shape, g.getTransform()));
+ }
+
+ /**
+ * Adds a new pending painting to the list on the given component. This
+ * will find the first ancestor that has a {@link PendingPaintings} client
+ * property, starting with the component itself.
+ *
+ * @param c The component for which the painting is being added.
+ * @param newPainting The new painting.
+ */
+ public static void add(JComponent c, Painting newPainting) {
+ PendingPaintings pending = pendingPaintingsFor(c);
+ if (pending != null) {
+ pending.paintings.add(newPainting);
+ }
+ }
+
+ /**
+ * Returns the pending painting object for the given component, if any. This
+ * is retrieved from the first object found that has a {@link
+ * #PENDING_PAINTINGS} client property, starting with this component and
+ * looking up its ancestors (parent, parent's parent, etc.)
+ *
+ * This allows any descendant of a component that has a {@link
+ * PendingPaintings} property to add its own pending paintings.
+ *
+ * @param c The component for which the painting is being added.
+ *
+ * @return The pending painting object for that component, or null
+ * if there is none.
+ */
+ public static PendingPaintings pendingPaintingsFor(JComponent c) {
+ for (Component parent = c;
+ parent != null;
+ parent = parent.getParent()) {
+ if (parent instanceof JComponent) {
+ JComponent jc = (JComponent) parent;
+ Object pd = jc.getClientProperty(PENDING_PAINTINGS);
+ if (pd != null)
+ return (PendingPaintings) pd;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html b/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html
new file mode 100644
index 000000000..d9819fb66
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hssf/view/brush/package.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+This package contains some brushes that are used when drawing borders for Excel
+cells.
+
+
diff --git a/trunk/src/examples/src/org/apache/poi/hwpf/Word2Forrest.java b/trunk/src/examples/src/org/apache/poi/hwpf/Word2Forrest.java
new file mode 100644
index 000000000..3516b88e8
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/hwpf/Word2Forrest.java
@@ -0,0 +1,229 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hwpf;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+import org.apache.poi.hwpf.model.StyleDescription;
+import org.apache.poi.hwpf.model.StyleSheet;
+import org.apache.poi.hwpf.usermodel.CharacterRun;
+import org.apache.poi.hwpf.usermodel.Paragraph;
+import org.apache.poi.hwpf.usermodel.Range;
+
+public final class Word2Forrest
+{
+ Writer _out;
+ HWPFDocument _doc;
+
+ @SuppressWarnings("unused")
+ public Word2Forrest(HWPFDocument doc, OutputStream stream) throws IOException
+ {
+ OutputStreamWriter out = new OutputStreamWriter (stream, Charset.forName("UTF-8"));
+ _out = out;
+ _doc = doc;
+
+ init ();
+ openDocument ();
+ openBody ();
+
+ Range r = doc.getRange ();
+ StyleSheet styleSheet = doc.getStyleSheet ();
+
+ int sectionLevel = 0;
+ int lenParagraph = r.numParagraphs ();
+ boolean inCode = false;
+ for (int x = 0; x < lenParagraph; x++)
+ {
+ Paragraph p = r.getParagraph (x);
+ String text = p.text ();
+ if (text.trim ().length () == 0)
+ {
+ continue;
+ }
+ StyleDescription paragraphStyle = styleSheet.getStyleDescription (p.
+ getStyleIndex ());
+ String styleName = paragraphStyle.getName();
+ if (styleName.startsWith ("Heading"))
+ {
+ if (inCode)
+ {
+ closeSource();
+ inCode = false;
+ }
+
+ int headerLevel = Integer.parseInt (styleName.substring (8));
+ if (headerLevel > sectionLevel)
+ {
+ openSection ();
+ }
+ else
+ {
+ for (int y = 0; y < (sectionLevel - headerLevel) + 1; y++)
+ {
+ closeSection ();
+ }
+ openSection ();
+ }
+ sectionLevel = headerLevel;
+ openTitle ();
+ writePlainText (text);
+ closeTitle ();
+ }
+ else
+ {
+ int cruns = p.numCharacterRuns ();
+ CharacterRun run = p.getCharacterRun (0);
+ String fontName = run.getFontName();
+ if (fontName.startsWith ("Courier"))
+ {
+ if (!inCode)
+ {
+ openSource ();
+ inCode = true;
+ }
+ writePlainText (p.text());
+ }
+ else
+ {
+ if (inCode)
+ {
+ inCode = false;
+ closeSource();
+ }
+ openParagraph();
+ writePlainText(p.text());
+ closeParagraph();
+ }
+ }
+ }
+ for (int x = 0; x < sectionLevel; x++)
+ {
+ closeSection();
+ }
+ closeBody();
+ closeDocument();
+ _out.flush();
+
+ }
+
+ public void init ()
+ throws IOException
+ {
+ _out.write ("\r\n");
+ _out.write ("\r\n");
+ }
+
+ public void openDocument ()
+ throws IOException
+ {
+ _out.write ("\r\n");
+ }
+ public void closeDocument ()
+ throws IOException
+ {
+ _out.write ("\r\n");
+ }
+
+
+ public void openBody ()
+ throws IOException
+ {
+ _out.write ("\r\n");
+ }
+
+ public void closeBody ()
+ throws IOException
+ {
+ _out.write ("\r\n");
+ }
+
+
+ public void openSection ()
+ throws IOException
+ {
+ _out.write ("");
+
+ }
+
+ public void closeSection ()
+ throws IOException
+ {
+ _out.write ("");
+
+ }
+
+ public void openTitle ()
+ throws IOException
+ {
+ _out.write ("");
+ }
+
+ public void closeTitle ()
+ throws IOException
+ {
+ _out.write ("");
+ }
+
+ public void writePlainText (String text)
+ throws IOException
+ {
+ _out.write (text);
+ }
+
+ public void openParagraph ()
+ throws IOException
+ {
+ _out.write ("
");
+ }
+
+ public void openSource ()
+ throws IOException
+ {
+ _out.write ("");
+ }
+
+
+ public static void main(String[] args) throws IOException {
+ InputStream is = new FileInputStream(args[0]);
+ OutputStream out = new FileOutputStream("test.xml");
+ try {
+ new Word2Forrest(new HWPFDocument(is), out);
+ } finally {
+ out.close();
+ is.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/DocumentDescriptor.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/DocumentDescriptor.java
new file mode 100644
index 000000000..247684d1b
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/DocumentDescriptor.java
@@ -0,0 +1,76 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.io.*;
+import org.apache.poi.poifs.filesystem.*;
+
+/**
+ *
Describes the most important (whatever that is) features of a
+ * {@link POIFSDocument}.
+ *
+ * @param name The stream's name.
+ *
+ * @param path The stream's path in the POI filesystem hierarchy.
+ *
+ * @param stream The stream.
+ *
+ * @param nrOfBytes The maximum number of bytes to display in a
+ * dump starting at the beginning of the stream.
+ */
+ public DocumentDescriptor(final String name,
+ final POIFSDocumentPath path,
+ final DocumentInputStream stream,
+ final int nrOfBytes)
+ {
+ this.name = name;
+ this.path = path;
+ this.stream = stream;
+ try
+ {
+ size = stream.available();
+ if (stream.markSupported())
+ {
+ stream.mark(nrOfBytes);
+ final byte[] b = new byte[nrOfBytes];
+ final int read = stream.read(b, 0, Math.min(size, b.length));
+ bytes = new byte[read];
+ System.arraycopy(b, 0, bytes, 0, read);
+ stream.reset();
+ }
+ }
+ catch (IOException ex)
+ {
+ System.out.println(ex);
+ }
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/DocumentDescriptorRenderer.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/DocumentDescriptorRenderer.java
new file mode 100644
index 000000000..698aab35e
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/DocumentDescriptorRenderer.java
@@ -0,0 +1,78 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.tree.*;
+
+import org.apache.poi.util.HexDump;
+
+/**
+ *
{@link TreeCellRenderer} for a {@link DocumentDescriptor}. The
+ * renderer is extremly rudimentary since displays only the document's
+ * name, its size and its fist few bytes.
+ */
+public class DocumentDescriptorRenderer extends DefaultTreeCellRenderer
+{
+
+ public Component getTreeCellRendererComponent(final JTree tree,
+ final Object value,
+ final boolean selectedCell,
+ final boolean expanded,
+ final boolean leaf,
+ final int row,
+ final boolean hasCellFocus)
+ {
+ final DocumentDescriptor d = (DocumentDescriptor)
+ ((DefaultMutableTreeNode) value).getUserObject();
+ final JPanel p = new JPanel();
+ final JTextArea text = new JTextArea();
+ text.append(renderAsString(d));
+ text.setFont(new Font("Monospaced", Font.PLAIN, 10));
+ p.add(text);
+ if (selectedCell) {
+ Util.invert(text);
+ }
+ return p;
+ }
+
+
+ /**
+ *
Renders {@link DocumentDescriptor} as a string.
+ */
+ protected String renderAsString(final DocumentDescriptor d)
+ {
+ final StringBuilder b = new StringBuilder();
+ b.append("Name: ");
+ b.append(d.name);
+ b.append(" ");
+ b.append(HexDump.toHex(d.name));
+ b.append("\n");
+
+ b.append("Size: ");
+ b.append(d.size);
+ b.append(" bytes\n");
+
+ b.append("First bytes: ");
+ b.append(HexDump.toHex(d.bytes));
+
+ return b.toString();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/ExtendableTreeCellRenderer.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/ExtendableTreeCellRenderer.java
new file mode 100644
index 000000000..917c0b057
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/ExtendableTreeCellRenderer.java
@@ -0,0 +1,142 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.tree.*;
+import java.util.*;
+
+/**
+ *
This is a {@link TreeCellRenderer} implementation which is able
+ * to render arbitrary objects. The {@link ExtendableTreeCellRenderer}
+ * does not do the rendering itself but instead dispatches to
+ * class-specific renderers. A class/renderer pair must be registered
+ * using the {@link #register} method. If a class has no registered
+ * renderer, the renderer of its closest superclass is used. Since the
+ * {@link ExtendableTreeCellRenderer} always has a default renderer
+ * for the {@link Object} class, rendering is always possible. The
+ * default {@link Object} renderer can be replaced by another renderer
+ * but it cannot be unregistered.
Unregisters a renderer for a class. The renderer for the
+ * {@link Object} class cannot be unregistered.
+ */
+ public void unregister(final Class> c)
+ {
+ if (c == Object.class)
+ throw new IllegalArgumentException
+ ("Renderer for Object cannot be unregistered.");
+ renderers.put(c, null);
+ }
+
+
+
+ /**
+ *
Renders an object in a tree cell depending of the object's
+ * class.
+ *
+ * @see TreeCellRenderer#getTreeCellRendererComponent
+ */
+ public Component getTreeCellRendererComponent
+ (final JTree tree, final Object value, final boolean selected,
+ final boolean expanded, final boolean leaf, final int row,
+ final boolean hasFocus)
+ {
+ final String NULL = "null";
+ TreeCellRenderer r;
+ Object userObject;
+ if (value == null)
+ userObject = NULL;
+ else
+ {
+ userObject = ((DefaultMutableTreeNode) value).getUserObject();
+ if (userObject == null)
+ userObject = NULL;
+ }
+ r = findRenderer(userObject.getClass());
+ return r.getTreeCellRendererComponent
+ (tree, value, selected, expanded, leaf, row,
+ hasFocus);
+ }
+
+
+
+ /**
+ *
Find the renderer for the specified class.
+ */
+ protected TreeCellRenderer findRenderer(final Class> c)
+ {
+ final TreeCellRenderer r = renderers.get(c);
+ if (r != null)
+ /* The class has a renderer. */
+ return r;
+
+ /* The class has no renderer, try the superclass, if any. */
+ final Class> superclass = c.getSuperclass();
+ if (superclass != null) {
+ return findRenderer(superclass);
+ }
+ return null;
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/POIBrowser.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/POIBrowser.java
new file mode 100644
index 000000000..d1fa1727c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/POIBrowser.java
@@ -0,0 +1,127 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+
+import org.apache.poi.poifs.eventfilesystem.POIFSReader;
+
+/**
+ *
The main class of the POI Browser. It shows the structure of POI
+ * filesystems (Microsoft Office documents) in a {@link
+ * JTree}. Specify their filenames on the command line!
Takes a bunch of file names as command line parameters,
+ * opens each of them as a POI filesystem and displays their
+ * internal structures in a {@link JTree}.
+ */
+ public static void main(String[] args)
+ {
+ new POIBrowser().run(args);
+ }
+
+
+
+ protected void run(String[] args)
+ {
+ addWindowListener(new WindowAdapter()
+ {
+ public void windowClosing(WindowEvent e)
+ {
+ System.exit(0);
+ }
+ });
+
+ /* Create the tree model with a root node. The latter is
+ * invisible but it must be present because a tree model
+ * always needs a root. */
+ rootNode = new DefaultMutableTreeNode("POI Filesystems");
+ DefaultTreeModel treeModel = new DefaultTreeModel(rootNode);
+
+ /* Create the tree UI element. */
+ final JTree treeUI = new JTree(treeModel);
+ getContentPane().add(new JScrollPane(treeUI));
+
+ /* Add the POI filesystems to the tree. */
+ int displayedFiles = 0;
+ for (int i = 0; i < args.length; i++)
+ {
+ final String filename = args[i];
+ try {
+ FileInputStream fis = new FileInputStream(filename);
+ POIFSReader r = new POIFSReader();
+ r.registerListener(new TreeReaderListener(filename, rootNode));
+ r.read(fis);
+ fis.close();
+ displayedFiles++;
+ } catch (IOException ex) {
+ System.err.println(filename + ": " + ex);
+ } catch (Exception t) {
+ System.err.println("Unexpected exception while reading \"" +
+ filename + "\":");
+ t.printStackTrace(System.err);
+ }
+ }
+
+ /* Exit if there is no file to display (none specified or only
+ * files with problems). */
+ if (displayedFiles == 0)
+ {
+ System.out.println("No POI filesystem(s) to display.");
+ System.exit(0);
+ }
+
+ /* Make the tree UI element visible. */
+ treeUI.setRootVisible(true);
+ treeUI.setShowsRootHandles(true);
+ ExtendableTreeCellRenderer etcr = new ExtendableTreeCellRenderer();
+ etcr.register(DocumentDescriptor.class,
+ new DocumentDescriptorRenderer());
+ etcr.register(PropertySetDescriptor.class,
+ new PropertySetDescriptorRenderer());
+ treeUI.setCellRenderer(etcr);
+ setSize(600, 450);
+ setTitle("POI Browser 0.09");
+ setVisible(true);
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/PropertySetDescriptor.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/PropertySetDescriptor.java
new file mode 100644
index 000000000..f9658ae03
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/PropertySetDescriptor.java
@@ -0,0 +1,75 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.poi.hpsf.MarkUnsupportedException;
+import org.apache.poi.hpsf.NoPropertySetStreamException;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
+
+/**
+ *
Describes the most important (whatever that is) features of a
+ * stream containing a {@link PropertySet}.
Creates a {@link PropertySetDescriptor} by reading a {@link
+ * PropertySet} from a {@link DocumentInputStream}.
+ *
+ * @param name The stream's name.
+ *
+ * @param path The stream's path in the POI filesystem hierarchy.
+ *
+ * @param stream The stream.
+ *
+ * @param nrOfBytesToDump The maximum number of bytes to display in a
+ * dump starting at the beginning of the stream.
+ */
+ public PropertySetDescriptor(final String name,
+ final POIFSDocumentPath path,
+ final DocumentInputStream stream,
+ final int nrOfBytesToDump)
+ throws NoPropertySetStreamException,
+ MarkUnsupportedException, UnsupportedEncodingException,
+ IOException
+ {
+ super(name, path, stream, nrOfBytesToDump);
+ propertySet = PropertySetFactory.create(stream);
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/PropertySetDescriptorRenderer.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/PropertySetDescriptorRenderer.java
new file mode 100644
index 000000000..085f2db6d
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/PropertySetDescriptorRenderer.java
@@ -0,0 +1,169 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import org.apache.poi.hpsf.Property;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.Section;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.util.HexDump;
+
+/**
+ *
Renders a {@link PropertySetDescriptor} by more or less dumping
+ * the stuff into a {@link JTextArea}.
Returns a string representation of a list of {@link
+ * Section}s.
+ */
+ protected String sectionsToString(final List sections)
+ {
+ final StringBuffer b = new StringBuffer();
+ int count = 1;
+ for (Iterator i = sections.iterator(); i.hasNext();)
+ {
+ Section s = i.next();
+ String d = toString(s, "Section " + count++);
+ b.append(d);
+ }
+ return b.toString();
+ }
+
+
+
+ /**
+ *
Returns a string representation of a {@link Section}.
+ * @param s the section
+ * @param name the section's name
+ * @return a string representation of the {@link Section}
+ */
+ protected String toString(final Section s, final String name)
+ {
+ final StringBuffer b = new StringBuffer();
+ b.append("\n" + name + " Format ID: ");
+ b.append(HexDump.toHex(s.getFormatID().getBytes()));
+ b.append("\n" + name + " Offset: " + s.getOffset());
+ b.append("\n" + name + " Section size: " + s.getSize());
+ b.append("\n" + name + " Property count: " + s.getPropertyCount());
+
+ final Property[] properties = s.getProperties();
+ for (int i = 0; i < properties.length; i++)
+ {
+ final Property p = properties[i];
+ final long id = p.getID();
+ final long type = p.getType();
+ final Object value = p.getValue();
+ b.append('\n');
+ b.append(name);
+ b.append(", Name: ");
+ b.append(id);
+ b.append(" (");
+ b.append(s.getPIDString(id));
+ b.append("), Type: ");
+ b.append(type);
+ b.append(", Value: ");
+ if (value instanceof byte[]) {
+ byte[] buf = new byte[4];
+ System.arraycopy(value, 0, buf, 0, 4);
+ b.append(HexDump.toHex(buf));
+ b.append(' ');
+ System.arraycopy(value, ((byte[])value).length - 4, buf, 0, 4);
+ } else if (value != null) {
+ b.append(value.toString());
+ } else {
+ b.append("null");
+ }
+ }
+ return b.toString();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/TreeReaderListener.java b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/TreeReaderListener.java
new file mode 100644
index 000000000..3174610ca
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/poifs/poibrowser/TreeReaderListener.java
@@ -0,0 +1,216 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.poibrowser;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.MutableTreeNode;
+
+import org.apache.poi.hpsf.HPSFException;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
+import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
+
+/**
+ *
Organizes document information in a tree model in order to be
+ * e.g. displayed in a Swing {@link javax.swing.JTree}. An instance of this
+ * class is created with a root tree node ({@link MutableTreeNode}) and
+ * registered as a {@link POIFSReaderListener} with a {@link
+ * org.apache.poi.poifs.eventfilesystem.POIFSReader}. While the latter processes
+ * a POI filesystem it calls this class' {@link #processPOIFSReaderEvent} for
+ * each document it has been registered for. This method appends the document it
+ * processes at the appropriate position into the tree rooted at the
+ * above mentioned root tree node.
+ *
+ *
The root tree node should be the root tree node of a {@link
+ * javax.swing.tree.TreeModel}.
+ *
+ *
A top-level element in the tree model, i.e. an immediate child
+ * node of the root node, describes a POI filesystem as such. It is
+ * suggested to use the file's name (as seen by the operating system)
+ * but it could be any other string.
+ *
+ *
The value of a tree node is a {@link DocumentDescriptor}. Unlike
+ * a {@link org.apache.poi.poifs.filesystem.POIFSDocument} which may be as heavy
+ * as many megabytes, an instance of {@link DocumentDescriptor} is a
+ * light-weight object and contains only some meta-information about a
+ * document.
+ */
+public class ExcelComparator {
+
+ private static final String CELL_DATA_DOES_NOT_MATCH = "Cell Data does not Match ::";
+ private static final String CELL_FONT_ATTRIBUTES_DOES_NOT_MATCH = "Cell Font Attributes does not Match ::";
+
+ private static class Locator {
+ Workbook workbook;
+ Sheet sheet;
+ Row row;
+ Cell cell;
+ }
+
+ List listOfDifferences = new ArrayList();
+
+ public static void main(String args[]) throws Exception {
+ if (args.length != 2 || !(new File(args[0]).exists()) || !(new File(args[1]).exists())) {
+ System.err.println("java -cp "+ExcelComparator.class.getCanonicalName()+" compare(Workbook wb1, Workbook wb2) {
+ Locator loc1 = new Locator();
+ Locator loc2 = new Locator();
+ loc1.workbook = wb1;
+ loc2.workbook = wb2;
+
+ ExcelComparator excelComparator = new ExcelComparator();
+ excelComparator.compareNumberOfSheets(loc1, loc2 );
+ excelComparator.compareSheetNames(loc1, loc2);
+ excelComparator.compareSheetData(loc1, loc2);
+
+ return excelComparator.listOfDifferences;
+ }
+
+ /**
+ * Compare data in all sheets.
+ *
+ * @param workbook1 the workbook1
+ * @param workbook2 the workbook2
+ * @param listOfDifferences the list of differences
+ * @throws ExcelCompareException the excel compare exception
+ */
+ private void compareDataInAllSheets(Locator loc1, Locator loc2) {
+ for (int i = 0; i < loc1.workbook.getNumberOfSheets(); i++) {
+ if (loc2.workbook.getNumberOfSheets() <= i) return;
+
+ loc1.sheet = loc1.workbook.getSheetAt(i);
+ loc2.sheet = loc2.workbook.getSheetAt(i);
+
+ compareDataInSheet(loc1, loc2);
+ }
+ }
+
+ private void compareDataInSheet(Locator loc1, Locator loc2) {
+ for (int j = 0; j < loc1.sheet.getPhysicalNumberOfRows(); j++) {
+ if (loc2.sheet.getPhysicalNumberOfRows() <= j) return;
+
+ loc1.row = loc1.sheet.getRow(j);
+ loc2.row = loc2.sheet.getRow(j);
+
+ if ((loc1.row == null) || (loc2.row == null)) {
+ continue;
+ }
+
+ compareDataInRow(loc1, loc2);
+ }
+ }
+
+ private void compareDataInRow(Locator loc1, Locator loc2) {
+ for (int k = 0; k < loc1.row.getLastCellNum(); k++) {
+ if (loc2.row.getPhysicalNumberOfCells() <= k) return;
+
+ loc1.cell = loc1.row.getCell(k);
+ loc2.cell = loc2.row.getCell(k);
+
+ if ((loc1.cell == null) || (loc2.cell == null)) {
+ continue;
+ }
+
+ compareDataInCell(loc1, loc2);
+ }
+ }
+
+ private void compareDataInCell(Locator loc1, Locator loc2) {
+ if (isCellTypeMatches(loc1, loc2)) {
+ final CellType loc1cellType = loc1.cell.getCellTypeEnum();
+ switch(loc1cellType) {
+ case BLANK:
+ case STRING:
+ case ERROR:
+ isCellContentMatches(loc1,loc2);
+ break;
+ case BOOLEAN:
+ isCellContentMatchesForBoolean(loc1,loc2);
+ break;
+ case FORMULA:
+ isCellContentMatchesForFormula(loc1,loc2);
+ break;
+ case NUMERIC:
+ if (DateUtil.isCellDateFormatted(loc1.cell)) {
+ isCellContentMatchesForDate(loc1,loc2);
+ } else {
+ isCellContentMatchesForNumeric(loc1,loc2);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Unexpected cell type: " + loc1cellType);
+ }
+ }
+
+ isCellFillPatternMatches(loc1,loc2);
+ isCellAlignmentMatches(loc1,loc2);
+ isCellHiddenMatches(loc1,loc2);
+ isCellLockedMatches(loc1,loc2);
+ isCellFontFamilyMatches(loc1,loc2);
+ isCellFontSizeMatches(loc1,loc2);
+ isCellFontBoldMatches(loc1,loc2);
+ isCellUnderLineMatches(loc1,loc2);
+ isCellFontItalicsMatches(loc1,loc2);
+ isCellBorderMatches(loc1,loc2,'t');
+ isCellBorderMatches(loc1,loc2,'l');
+ isCellBorderMatches(loc1,loc2,'b');
+ isCellBorderMatches(loc1,loc2,'r');
+ isCellFillBackGroundMatches(loc1,loc2);
+ }
+
+ /**
+ * Compare number of columns in sheets.
+ */
+ private void compareNumberOfColumnsInSheets(Locator loc1, Locator loc2) {
+ for (int i = 0; i < loc1.workbook.getNumberOfSheets(); i++) {
+ if (loc2.workbook.getNumberOfSheets() <= i) return;
+
+ loc1.sheet = loc1.workbook.getSheetAt(i);
+ loc2.sheet = loc2.workbook.getSheetAt(i);
+
+ Iterator ri1 = loc1.sheet.rowIterator();
+ Iterator ri2 = loc2.sheet.rowIterator();
+
+ int num1 = (ri1.hasNext()) ? ri1.next().getPhysicalNumberOfCells() : 0;
+ int num2 = (ri2.hasNext()) ? ri2.next().getPhysicalNumberOfCells() : 0;
+
+ if (num1 != num2) {
+ String str = String.format(Locale.ROOT, "%s\nworkbook1 -> %s [%d] != workbook2 -> %s [%d]",
+ "Number Of Columns does not Match ::",
+ loc1.sheet.getSheetName(), num1,
+ loc2.sheet.getSheetName(), num2
+ );
+ listOfDifferences.add(str);
+ }
+ }
+ }
+
+ /**
+ * Compare number of rows in sheets.
+ */
+ private void compareNumberOfRowsInSheets(Locator loc1, Locator loc2) {
+ for (int i = 0; i < loc1.workbook.getNumberOfSheets(); i++) {
+ if (loc2.workbook.getNumberOfSheets() <= i) return;
+
+ loc1.sheet = loc1.workbook.getSheetAt(i);
+ loc2.sheet = loc2.workbook.getSheetAt(i);
+
+ int num1 = loc1.sheet.getPhysicalNumberOfRows();
+ int num2 = loc2.sheet.getPhysicalNumberOfRows();
+
+ if (num1 != num2) {
+ String str = String.format(Locale.ROOT, "%s\nworkbook1 -> %s [%d] != workbook2 -> %s [%d]",
+ "Number Of Rows does not Match ::",
+ loc1.sheet.getSheetName(), num1,
+ loc2.sheet.getSheetName(), num2
+ );
+ listOfDifferences.add(str);
+ }
+ }
+
+ }
+
+ /**
+ * Compare number of sheets.
+ */
+ private void compareNumberOfSheets(Locator loc1, Locator loc2) {
+ int num1 = loc1.workbook.getNumberOfSheets();
+ int num2 = loc2.workbook.getNumberOfSheets();
+ if (num1 != num2) {
+ String str = String.format(Locale.ROOT, "%s\nworkbook1 [%d] != workbook2 [%d]",
+ "Number of Sheets do not match ::",
+ num1, num2
+ );
+
+ listOfDifferences.add(str);
+
+ }
+ }
+
+ /**
+ * Compare sheet data.
+ *
+ * @param workbook1
+ * the workbook1
+ * @param workbook2
+ * the workbook2
+ * @param listOfDifferences
+ *
+ * @throws ExcelCompareException
+ * the excel compare exception
+ */
+ private void compareSheetData(Locator loc1, Locator loc2) {
+ compareNumberOfRowsInSheets(loc1, loc2);
+ compareNumberOfColumnsInSheets(loc1, loc2);
+ compareDataInAllSheets(loc1, loc2);
+
+ }
+
+ /**
+ * Compare sheet names.
+ */
+ private void compareSheetNames(Locator loc1, Locator loc2) {
+ for (int i = 0; i < loc1.workbook.getNumberOfSheets(); i++) {
+ String name1 = loc1.workbook.getSheetName(i);
+ String name2 = (loc2.workbook.getNumberOfSheets() > i) ? loc2.workbook.getSheetName(i) : "";
+
+ if (!name1.equals(name2)) {
+ String str = String.format(Locale.ROOT, "%s\nworkbook1 -> %s [%d] != workbook2 -> %s [%d]",
+ "Name of the sheets do not match ::",
+ loc1.sheet.getSheetName(), name1, i+1,
+ loc2.sheet.getSheetName(), name2, i+1
+ );
+ listOfDifferences.add(str);
+ }
+ }
+ }
+
+ /**
+ * Formats the message.
+ */
+ private void addMessage(Locator loc1, Locator loc2, String messageStart, String value1, String value2) {
+ String str =
+ String.format(Locale.ROOT, "%s\nworkbook1 -> %s -> %s [%s] != workbook2 -> %s -> %s [%s]",
+ messageStart,
+ loc1.sheet.getSheetName(), new CellReference(loc1.cell).formatAsString(), value1,
+ loc2.sheet.getSheetName(), new CellReference(loc2.cell).formatAsString(), value2
+ );
+ listOfDifferences.add(str);
+ }
+
+ /**
+ * Checks if cell alignment matches.
+ */
+ private void isCellAlignmentMatches(Locator loc1, Locator loc2) {
+ // TODO: check for NPE
+ short align1 = loc1.cell.getCellStyle().getAlignment();
+ short align2 = loc2.cell.getCellStyle().getAlignment();
+ if (align1 != align2) {
+ addMessage(loc1, loc2,
+ "Cell Alignment does not Match ::",
+ Short.toString(align1),
+ Short.toString(align2)
+ );
+ }
+ }
+
+ /**
+ * Checks if cell border bottom matches.
+ */
+ private void isCellBorderMatches(Locator loc1, Locator loc2, char borderSide) {
+ if (!(loc1.cell instanceof XSSFCell)) return;
+ XSSFCellStyle style1 = ((XSSFCell)loc1.cell).getCellStyle();
+ XSSFCellStyle style2 = ((XSSFCell)loc2.cell).getCellStyle();
+ boolean b1, b2;
+ String borderName;
+ switch (borderSide) {
+ case 't': default:
+ b1 = style1.getBorderTop() == BorderStyle.THIN;
+ b2 = style2.getBorderTop() == BorderStyle.THIN;
+ borderName = "TOP";
+ break;
+ case 'b':
+ b1 = style1.getBorderBottom() == BorderStyle.THIN;
+ b2 = style2.getBorderBottom() == BorderStyle.THIN;
+ borderName = "BOTTOM";
+ break;
+ case 'l':
+ b1 = style1.getBorderLeft() == BorderStyle.THIN;
+ b2 = style2.getBorderLeft() == BorderStyle.THIN;
+ borderName = "LEFT";
+ break;
+ case 'r':
+ b1 = style1.getBorderRight() == BorderStyle.THIN;
+ b2 = style2.getBorderRight() == BorderStyle.THIN;
+ borderName = "RIGHT";
+ break;
+ }
+ if (b1 != b2) {
+ addMessage(loc1, loc2,
+ "Cell Border Attributes does not Match ::",
+ (b1 ? "" : "NOT ")+borderName+" BORDER",
+ (b2 ? "" : "NOT ")+borderName+" BORDER"
+ );
+ }
+ }
+
+ /**
+ * Checks if cell content matches.
+ */
+ private void isCellContentMatches(Locator loc1, Locator loc2) {
+ // TODO: check for null and non-rich-text cells
+ String str1 = loc1.cell.getRichStringCellValue().getString();
+ String str2 = loc2.cell.getRichStringCellValue().getString();
+ if (!str1.equals(str2)) {
+ addMessage(loc1,loc2,CELL_DATA_DOES_NOT_MATCH,str1,str2);
+ }
+ }
+
+ /**
+ * Checks if cell content matches for boolean.
+ */
+ private void isCellContentMatchesForBoolean(Locator loc1, Locator loc2) {
+ boolean b1 = loc1.cell.getBooleanCellValue();
+ boolean b2 = loc2.cell.getBooleanCellValue();
+ if (b1 != b2) {
+ addMessage(loc1,loc2,CELL_DATA_DOES_NOT_MATCH,Boolean.toString(b1),Boolean.toString(b2));
+ }
+ }
+
+ /**
+ * Checks if cell content matches for date.
+ */
+ private void isCellContentMatchesForDate(Locator loc1, Locator loc2) {
+ Date date1 = loc1.cell.getDateCellValue();
+ Date date2 = loc2.cell.getDateCellValue();
+ if (!date1.equals(date2)) {
+ addMessage(loc1, loc2, CELL_DATA_DOES_NOT_MATCH, date1.toGMTString(), date2.toGMTString());
+ }
+ }
+
+
+ /**
+ * Checks if cell content matches for formula.
+ */
+ private void isCellContentMatchesForFormula(Locator loc1, Locator loc2) {
+ // TODO: actually evaluate the formula / NPE checks
+ String form1 = loc1.cell.getCellFormula();
+ String form2 = loc2.cell.getCellFormula();
+ if (!form1.equals(form2)) {
+ addMessage(loc1, loc2, CELL_DATA_DOES_NOT_MATCH, form1, form2);
+ }
+ }
+
+ /**
+ * Checks if cell content matches for numeric.
+ */
+ private void isCellContentMatchesForNumeric(Locator loc1, Locator loc2) {
+ // TODO: Check for NaN
+ double num1 = loc1.cell.getNumericCellValue();
+ double num2 = loc2.cell.getNumericCellValue();
+ if (num1 != num2) {
+ addMessage(loc1, loc2, CELL_DATA_DOES_NOT_MATCH, Double.toString(num1), Double.toString(num2));
+ }
+ }
+
+ private String getCellFillBackground(Locator loc) {
+ Color col = loc.cell.getCellStyle().getFillForegroundColorColor();
+ return (col instanceof XSSFColor) ? ((XSSFColor)col).getARGBHex() : "NO COLOR";
+ }
+
+ /**
+ * Checks if cell file back ground matches.
+ */
+ private void isCellFillBackGroundMatches(Locator loc1, Locator loc2) {
+ String col1 = getCellFillBackground(loc1);
+ String col2 = getCellFillBackground(loc2);
+ if (!col1.equals(col2)) {
+ addMessage(loc1, loc2, "Cell Fill Color does not Match ::", col1, col2);
+ }
+ }
+ /**
+ * Checks if cell fill pattern matches.
+ */
+ private void isCellFillPatternMatches(Locator loc1, Locator loc2) {
+ // TOOO: Check for NPE
+ short fill1 = loc1.cell.getCellStyle().getFillPattern();
+ short fill2 = loc2.cell.getCellStyle().getFillPattern();
+ if (fill1 != fill2) {
+ addMessage(loc1, loc2,
+ "Cell Fill pattern does not Match ::",
+ Short.toString(fill1),
+ Short.toString(fill2)
+ );
+ }
+ }
+
+ /**
+ * Checks if cell font bold matches.
+ */
+ private void isCellFontBoldMatches(Locator loc1, Locator loc2) {
+ if (!(loc1.cell instanceof XSSFCell)) return;
+ boolean b1 = ((XSSFCell)loc1.cell).getCellStyle().getFont().getBold();
+ boolean b2 = ((XSSFCell)loc2.cell).getCellStyle().getFont().getBold();
+ if (b1 != b2) {
+ addMessage(loc1, loc2,
+ CELL_FONT_ATTRIBUTES_DOES_NOT_MATCH,
+ (b1 ? "" : "NOT ")+"BOLD",
+ (b2 ? "" : "NOT ")+"BOLD"
+ );
+ }
+ }
+
+ /**
+ * Checks if cell font family matches.
+ */
+ private void isCellFontFamilyMatches(Locator loc1, Locator loc2) {
+ // TODO: Check for NPEs
+ if (!(loc1.cell instanceof XSSFCell)) return;
+ String family1 = ((XSSFCell)loc1.cell).getCellStyle().getFont().getFontName();
+ String family2 = ((XSSFCell)loc2.cell).getCellStyle().getFont().getFontName();
+ if (!family1.equals(family2)) {
+ addMessage(loc1, loc2, "Cell Font Family does not Match ::", family1, family2);
+ }
+ }
+
+ /**
+ * Checks if cell font italics matches.
+ */
+ private void isCellFontItalicsMatches(Locator loc1, Locator loc2) {
+ if (!(loc1.cell instanceof XSSFCell)) return;
+ boolean b1 = ((XSSFCell)loc1.cell).getCellStyle().getFont().getItalic();
+ boolean b2 = ((XSSFCell)loc2.cell).getCellStyle().getFont().getItalic();
+ if (b1 != b2) {
+ addMessage(loc1, loc2,
+ CELL_FONT_ATTRIBUTES_DOES_NOT_MATCH,
+ (b1 ? "" : "NOT ")+"ITALICS",
+ (b2 ? "" : "NOT ")+"ITALICS"
+ );
+ }
+ }
+
+ /**
+ * Checks if cell font size matches.
+ */
+ private void isCellFontSizeMatches(Locator loc1, Locator loc2) {
+ if (!(loc1.cell instanceof XSSFCell)) return;
+ short size1 = ((XSSFCell)loc1.cell).getCellStyle().getFont().getFontHeightInPoints();
+ short size2 = ((XSSFCell)loc2.cell).getCellStyle().getFont().getFontHeightInPoints();
+ if (size1 != size2) {
+ addMessage(loc1, loc2,
+ "Cell Font Size does not Match ::",
+ Short.toString(size1),
+ Short.toString(size2)
+ );
+ }
+ }
+
+ /**
+ * Checks if cell hidden matches.
+ */
+ private void isCellHiddenMatches(Locator loc1, Locator loc2) {
+ boolean b1 = loc1.cell.getCellStyle().getHidden();
+ boolean b2 = loc1.cell.getCellStyle().getHidden();
+ if (b1 != b2) {
+ addMessage(loc1, loc2,
+ "Cell Visibility does not Match ::",
+ (b1 ? "" : "NOT ")+"HIDDEN",
+ (b2 ? "" : "NOT ")+"HIDDEN"
+ );
+ }
+ }
+
+ /**
+ * Checks if cell locked matches.
+ */
+ private void isCellLockedMatches(Locator loc1, Locator loc2) {
+ boolean b1 = loc1.cell.getCellStyle().getLocked();
+ boolean b2 = loc1.cell.getCellStyle().getLocked();
+ if (b1 != b2) {
+ addMessage(loc1, loc2,
+ "Cell Protection does not Match ::",
+ (b1 ? "" : "NOT ")+"LOCKED",
+ (b2 ? "" : "NOT ")+"LOCKED"
+ );
+ }
+ }
+
+ /**
+ * Checks if cell type matches.
+ */
+ private boolean isCellTypeMatches(Locator loc1, Locator loc2) {
+ CellType type1 = loc1.cell.getCellTypeEnum();
+ CellType type2 = loc2.cell.getCellTypeEnum();
+ if (type1 == type2) return true;
+ addMessage(loc1, loc2,
+ "Cell Data-Type does not Match in :: ",
+ type1.name(), type2.name()
+ );
+ return false;
+ }
+
+ /**
+ * Checks if cell under line matches.
+ *
+ * @param cellWorkBook1
+ * the cell work book1
+ * @param cellWorkBook2
+ * the cell work book2
+ * @return true, if cell under line matches
+ */
+ private void isCellUnderLineMatches(Locator loc1, Locator loc2) {
+ // TOOO: distinguish underline type
+ if (!(loc1.cell instanceof XSSFCell)) return;
+ byte b1 = ((XSSFCell)loc1.cell).getCellStyle().getFont().getUnderline();
+ byte b2 = ((XSSFCell)loc2.cell).getCellStyle().getFont().getUnderline();
+ if (b1 != b2) {
+ addMessage(loc1, loc2,
+ CELL_FONT_ATTRIBUTES_DOES_NOT_MATCH,
+ (b1 == 1 ? "" : "NOT ")+"UNDERLINE",
+ (b2 == 1 ? "" : "NOT ")+"UNDERLINE"
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/LinkedDropDownLists.java b/trunk/src/examples/src/org/apache/poi/ss/examples/LinkedDropDownLists.java
new file mode 100644
index 000000000..b0efc4f0c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/LinkedDropDownLists.java
@@ -0,0 +1,228 @@
+ /* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ==================================================================== */
+
+package org.apache.poi.ss.examples;
+import java.io.*;
+import org.apache.poi.xssf.usermodel.*;
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+
+/**
+ * Demonstrates one technique that may be used to create linked or dependent
+ * drop down lists. This refers to a situation in which the selection made
+ * in one drop down list affects the options that are displayed in the second
+ * or subsequent drop down list(s). In this example, the value the user selects
+ * from the down list in cell A1 will affect the values displayed in the linked
+ * drop down list in cell B1. For the sake of simplicity, the data for the drop
+ * down lists is included on the same worksheet but this does not have to be the
+ * case; the data could appear on a separate sheet. If this were done, then the
+ * names for the regions would have to be different, they would have to include
+ * the name of the sheet.
+ *
+ * There are two keys to this technique. The first is the use of named area or
+ * regions of cells to hold the data for the drop down lists and the second is
+ * making use of the INDIRECT() function to convert a name into the addresses
+ * of the cells it refers to.
+ *
+ * Note that whilst this class builds just two linked drop down lists, there is
+ * nothing to prevent more being created. Quite simply, use the value selected
+ * by the user in one drop down list to determine what is shown in another and the
+ * value selected in that drop down list to determine what is shown in a third,
+ * and so on. Also, note that the data for the drop down lists is contained on
+ * contained on the same sheet as the validations themselves. This is done simply
+ * for simplicity and there is nothing to prevent a separate sheet being created
+ * and used to hold the data. If this is done then problems may be encountered
+ * if the sheet is opened with OpenOffice Calc. To prevent these problems, it is
+ * better to include the name of the sheet when calling the setRefersToFormula()
+ * method.
+ *
+ * @author Mark Beardsley [msb at apache.org]
+ * @version 1.00 30th March 2012
+ */
+public class LinkedDropDownLists {
+
+ LinkedDropDownLists(String workbookName) {
+ File file = null;
+ FileOutputStream fos = null;
+ Workbook workbook = null;
+ Sheet sheet = null;
+ DataValidationHelper dvHelper = null;
+ DataValidationConstraint dvConstraint = null;
+ DataValidation validation = null;
+ CellRangeAddressList addressList = null;
+ try {
+
+ // Using the ss.usermodel allows this class to support both binary
+ // and xml based workbooks. The choice of which one to create is
+ // made by checking the file extension.
+ if (workbookName.endsWith(".xlsx")) {
+ workbook = new XSSFWorkbook();
+ } else {
+ workbook = new HSSFWorkbook();
+ }
+
+ // Build the sheet that will hold the data for the validations. This
+ // must be done first as it will create names that are referenced
+ // later.
+ sheet = workbook.createSheet("Linked Validations");
+ LinkedDropDownLists.buildDataSheet(sheet);
+
+ // Build the first data validation to occupy cell A1. Note
+ // that it retrieves it's data from the named area or region called
+ // CHOICES. Further information about this can be found in the
+ // static buildDataSheet() method below.
+ addressList = new CellRangeAddressList(0, 0, 0, 0);
+ dvHelper = sheet.getDataValidationHelper();
+ dvConstraint = dvHelper.createFormulaListConstraint("CHOICES");
+ validation = dvHelper.createValidation(dvConstraint, addressList);
+ sheet.addValidationData(validation);
+
+ // Now, build the linked or dependent drop down list that will
+ // occupy cell B1. The key to the whole process is the use of the
+ // INDIRECT() function. In the buildDataSheet(0 method, a series of
+ // named regions are created and the names of three of them mirror
+ // the options available to the user in the first drop down list
+ // (in cell A1). Using the INDIRECT() function makes it possible
+ // to convert the selection the user makes in that first drop down
+ // into the addresses of a named region of cells and then to use
+ // those cells to populate the second drop down list.
+ addressList = new CellRangeAddressList(0, 0, 1, 1);
+ dvConstraint = dvHelper.createFormulaListConstraint(
+ "INDIRECT(UPPER($A$1))");
+ validation = dvHelper.createValidation(dvConstraint, addressList);
+ sheet.addValidationData(validation);
+
+ file = new File(workbookName);
+ fos = new FileOutputStream(file);
+ workbook.write(fos);
+ } catch (IOException ioEx) {
+ System.out.println("Caught a: " + ioEx.getClass().getName());
+ System.out.println("Message: " + ioEx.getMessage());
+ System.out.println("Stacktrace follws:.....");
+ ioEx.printStackTrace(System.out);
+ } finally {
+ try {
+ if (fos != null) {
+ fos.close();
+ fos = null;
+ }
+ } catch (IOException ioEx) {
+ System.out.println("Caught a: " + ioEx.getClass().getName());
+ System.out.println("Message: " + ioEx.getMessage());
+ System.out.println("Stacktrace follws:.....");
+ ioEx.printStackTrace(System.out);
+ }
+ }
+ }
+
+ /**
+ * Called to populate the named areas/regions. The contents of the cells on
+ * row one will be used to populate the first drop down list. The contents of
+ * the cells on rows two, three and four will be used to populate the second
+ * drop down list, just which row will be determined by the choice the user
+ * makes in the first drop down list.
+ *
+ * In all cases, the approach is to create a row, create and populate cells
+ * with data and then specify a name that identifies those cells. With the
+ * exception of the first range, the names that are chosen for each range
+ * of cells are quite important. In short, each of the options the user
+ * could select in the first drop down list is used as the name for another
+ * range of cells. Thus, in this example, the user can select either
+ * 'Animal', 'Vegetable' or 'Mineral' in the first drop down and so the
+ * sheet contains ranges named 'ANIMAL', 'VEGETABLE' and 'MINERAL'.
+ *
+ * @param dataSheet An instance of a class that implements the Sheet Sheet
+ * interface (HSSFSheet or XSSFSheet).
+ */
+ private static final void buildDataSheet(Sheet dataSheet) {
+ Row row = null;
+ Cell cell = null;
+ Name name = null;
+
+ // The first row will hold the data for the first validation.
+ row = dataSheet.createRow(10);
+ cell = row.createCell(0);
+ cell.setCellValue("Animal");
+ cell = row.createCell(1);
+ cell.setCellValue("Vegetable");
+ cell = row.createCell(2);
+ cell.setCellValue("Mineral");
+ name = dataSheet.getWorkbook().createName();
+ name.setRefersToFormula("$A$11:$C$11");
+ name.setNameName("CHOICES");
+
+ // The next three rows will hold the data that will be used to
+ // populate the second, or linked, drop down list.
+ row = dataSheet.createRow(11);
+ cell = row.createCell(0);
+ cell.setCellValue("Lion");
+ cell = row.createCell(1);
+ cell.setCellValue("Tiger");
+ cell = row.createCell(2);
+ cell.setCellValue("Leopard");
+ cell = row.createCell(3);
+ cell.setCellValue("Elephant");
+ cell = row.createCell(4);
+ cell.setCellValue("Eagle");
+ cell = row.createCell(5);
+ cell.setCellValue("Horse");
+ cell = row.createCell(6);
+ cell.setCellValue("Zebra");
+ name = dataSheet.getWorkbook().createName();
+ name.setRefersToFormula("$A$12:$G$12");
+ name.setNameName("ANIMAL");
+
+ row = dataSheet.createRow(12);
+ cell = row.createCell(0);
+ cell.setCellValue("Cabbage");
+ cell = row.createCell(1);
+ cell.setCellValue("Cauliflower");
+ cell = row.createCell(2);
+ cell.setCellValue("Potato");
+ cell = row.createCell(3);
+ cell.setCellValue("Onion");
+ cell = row.createCell(4);
+ cell.setCellValue("Beetroot");
+ cell = row.createCell(5);
+ cell.setCellValue("Asparagus");
+ cell = row.createCell(6);
+ cell.setCellValue("Spinach");
+ cell = row.createCell(7);
+ cell.setCellValue("Chard");
+ name = dataSheet.getWorkbook().createName();
+ name.setRefersToFormula("$A$13:$H$13");
+ name.setNameName("VEGETABLE");
+
+ row = dataSheet.createRow(13);
+ cell = row.createCell(0);
+ cell.setCellValue("Bauxite");
+ cell = row.createCell(1);
+ cell.setCellValue("Quartz");
+ cell = row.createCell(2);
+ cell.setCellValue("Feldspar");
+ cell = row.createCell(3);
+ cell.setCellValue("Shist");
+ cell = row.createCell(4);
+ cell.setCellValue("Shale");
+ cell = row.createCell(5);
+ cell.setCellValue("Mica");
+ name = dataSheet.getWorkbook().createName();
+ name.setRefersToFormula("$A$14:$F$14");
+ name.setNameName("MINERAL");
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/LoadEmbedded.java b/trunk/src/examples/src/org/apache/poi/ss/examples/LoadEmbedded.java
new file mode 100644
index 000000000..66ca8ba7b
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/LoadEmbedded.java
@@ -0,0 +1,129 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.examples;
+
+import java.io.File;
+import java.io.InputStream;
+
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hssf.usermodel.HSSFObjectData;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hwpf.HWPFDocument;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.sl.usermodel.SlideShow;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.xslf.usermodel.XSLFSlideShow;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+
+/**
+ * Loads embedded resources from Workbooks. Code taken from the website:
+ * https://poi.apache.org/spreadsheet/quick-guide.html#Embedded
+ */
+public class LoadEmbedded {
+ public static void main(String[] args) throws Exception {
+ Workbook wb = WorkbookFactory.create(new File(args[0]));
+ loadEmbedded(wb);
+ }
+
+ public static void loadEmbedded(Workbook wb) throws Exception {
+ if (wb instanceof HSSFWorkbook) {
+ loadEmbedded((HSSFWorkbook)wb);
+ }
+ else if (wb instanceof XSSFWorkbook) {
+ loadEmbedded((XSSFWorkbook)wb);
+ }
+ else {
+ throw new IllegalArgumentException(wb.getClass().getName());
+ }
+ }
+
+ public static void loadEmbedded(HSSFWorkbook workbook) throws Exception {
+ for (HSSFObjectData obj : workbook.getAllEmbeddedObjects()) {
+ //the OLE2 Class Name of the object
+ String oleName = obj.getOLE2ClassName();
+ if (oleName.equals("Worksheet")) {
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ HSSFWorkbook embeddedWorkbook = new HSSFWorkbook(dn, false);
+ //System.out.println(entry.getName() + ": " + embeddedWorkbook.getNumberOfSheets());
+ } else if (oleName.equals("Document")) {
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ HWPFDocument embeddedWordDocument = new HWPFDocument(dn);
+ //System.out.println(entry.getName() + ": " + embeddedWordDocument.getRange().text());
+ } else if (oleName.equals("Presentation")) {
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ SlideShow,?> embeddedPowerPointDocument = new HSLFSlideShow(dn);
+ //System.out.println(entry.getName() + ": " + embeddedPowerPointDocument.getSlides().length);
+ } else {
+ if(obj.hasDirectoryEntry()){
+ // The DirectoryEntry is a DocumentNode. Examine its entries to find out what it is
+ DirectoryNode dn = (DirectoryNode) obj.getDirectory();
+ for (Entry entry : dn) {
+ //System.out.println(oleName + "." + entry.getName());
+ }
+ } else {
+ // There is no DirectoryEntry
+ // Recover the object's data from the HSSFObjectData instance.
+ byte[] objectData = obj.getObjectData();
+ }
+ }
+ }
+ }
+
+ public static void loadEmbedded(XSSFWorkbook workbook) throws Exception {
+ for (PackagePart pPart : workbook.getAllEmbedds()) {
+ String contentType = pPart.getContentType();
+ // Excel Workbook - either binary or OpenXML
+ if (contentType.equals("application/vnd.ms-excel")) {
+ HSSFWorkbook embeddedWorkbook = new HSSFWorkbook(pPart.getInputStream());
+ }
+ // Excel Workbook - OpenXML file format
+ else if (contentType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
+ OPCPackage docPackage = OPCPackage.open(pPart.getInputStream());
+ XSSFWorkbook embeddedWorkbook = new XSSFWorkbook(docPackage);
+ }
+ // Word Document - binary (OLE2CDF) file format
+ else if (contentType.equals("application/msword")) {
+ HWPFDocument document = new HWPFDocument(pPart.getInputStream());
+ }
+ // Word Document - OpenXML file format
+ else if (contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) {
+ OPCPackage docPackage = OPCPackage.open(pPart.getInputStream());
+ XWPFDocument document = new XWPFDocument(docPackage);
+ }
+ // PowerPoint Document - binary file format
+ else if (contentType.equals("application/vnd.ms-powerpoint")) {
+ HSLFSlideShow slideShow = new HSLFSlideShow(pPart.getInputStream());
+ }
+ // PowerPoint Document - OpenXML file format
+ else if (contentType.equals("application/vnd.openxmlformats-officedocument.presentationml.presentation")) {
+ OPCPackage docPackage = OPCPackage.open(pPart.getInputStream());
+ XSLFSlideShow slideShow = new XSLFSlideShow(docPackage);
+ }
+ // Any other type of embedded object.
+ else {
+ System.out.println("Unknown Embedded Document: " + contentType);
+ InputStream inputStream = pPart.getInputStream();
+ }
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/LoanCalculator.java b/trunk/src/examples/src/org/apache/poi/ss/examples/LoanCalculator.java
new file mode 100644
index 000000000..d6375dc43
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/LoanCalculator.java
@@ -0,0 +1,305 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.examples;
+
+import org.apache.poi.xssf.usermodel.*;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.io.FileOutputStream;
+
+/**
+ * Simple Loan Calculator. Demonstrates advance usage of cell formulas and named ranges.
+ *
+ * Usage:
+ * LoanCalculator -xls|xlsx
+ *
+ * @author Yegor Kozlov
+ */
+public class LoanCalculator {
+
+ public static void main(String[] args) throws Exception {
+ Workbook wb;
+
+ if(args.length > 0 && args[0].equals("-xls")) wb = new HSSFWorkbook();
+ else wb = new XSSFWorkbook();
+
+ Map styles = createStyles(wb);
+ Sheet sheet = wb.createSheet("Loan Calculator");
+ sheet.setPrintGridlines(false);
+ sheet.setDisplayGridlines(false);
+
+ PrintSetup printSetup = sheet.getPrintSetup();
+ printSetup.setLandscape(true);
+ sheet.setFitToPage(true);
+ sheet.setHorizontallyCenter(true);
+
+ sheet.setColumnWidth(0, 3*256);
+ sheet.setColumnWidth(1, 3*256);
+ sheet.setColumnWidth(2, 11*256);
+ sheet.setColumnWidth(3, 14*256);
+ sheet.setColumnWidth(4, 14*256);
+ sheet.setColumnWidth(5, 14*256);
+ sheet.setColumnWidth(6, 14*256);
+
+ createNames(wb);
+
+ Row titleRow = sheet.createRow(0);
+ titleRow.setHeightInPoints(35);
+ for (int i = 1; i <= 7; i++) {
+ titleRow.createCell(i).setCellStyle(styles.get("title"));
+ }
+ Cell titleCell = titleRow.getCell(2);
+ titleCell.setCellValue("Simple Loan Calculator");
+ sheet.addMergedRegion(CellRangeAddress.valueOf("$C$1:$H$1"));
+
+ Row row = sheet.createRow(2);
+ Cell cell = row.createCell(4);
+ cell.setCellValue("Enter values");
+ cell.setCellStyle(styles.get("item_right"));
+
+ row = sheet.createRow(3);
+ cell = row.createCell(2);
+ cell.setCellValue("Loan amount");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellStyle(styles.get("input_$"));
+ cell.setAsActiveCell();
+
+ row = sheet.createRow(4);
+ cell = row.createCell(2);
+ cell.setCellValue("Annual interest rate");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellStyle(styles.get("input_%"));
+
+ row = sheet.createRow(5);
+ cell = row.createCell(2);
+ cell.setCellValue("Loan period in years");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellStyle(styles.get("input_i"));
+
+ row = sheet.createRow(6);
+ cell = row.createCell(2);
+ cell.setCellValue("Start date of loan");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellStyle(styles.get("input_d"));
+
+ row = sheet.createRow(8);
+ cell = row.createCell(2);
+ cell.setCellValue("Monthly payment");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellFormula("IF(Values_Entered,Monthly_Payment,\"\")");
+ cell.setCellStyle(styles.get("formula_$"));
+
+ row = sheet.createRow(9);
+ cell = row.createCell(2);
+ cell.setCellValue("Number of payments");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellFormula("IF(Values_Entered,Loan_Years*12,\"\")");
+ cell.setCellStyle(styles.get("formula_i"));
+
+ row = sheet.createRow(10);
+ cell = row.createCell(2);
+ cell.setCellValue("Total interest");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellFormula("IF(Values_Entered,Total_Cost-Loan_Amount,\"\")");
+ cell.setCellStyle(styles.get("formula_$"));
+
+ row = sheet.createRow(11);
+ cell = row.createCell(2);
+ cell.setCellValue("Total cost of loan");
+ cell.setCellStyle(styles.get("item_left"));
+ cell = row.createCell(4);
+ cell.setCellFormula("IF(Values_Entered,Monthly_Payment*Number_of_Payments,\"\")");
+ cell.setCellStyle(styles.get("formula_$"));
+
+
+ // Write the output to a file
+ String file = "loan-calculator.xls";
+ if(wb instanceof XSSFWorkbook) file += "x";
+ FileOutputStream out = new FileOutputStream(file);
+ wb.write(out);
+ out.close();
+ }
+
+ /**
+ * cell styles used for formatting calendar sheets
+ */
+ private static Map createStyles(Workbook wb){
+ Map styles = new HashMap();
+
+ CellStyle style;
+ Font titleFont = wb.createFont();
+ titleFont.setFontHeightInPoints((short)14);
+ titleFont.setFontName("Trebuchet MS");
+ style = wb.createCellStyle();
+ style.setFont(titleFont);
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ styles.put("title", style);
+
+ Font itemFont = wb.createFont();
+ itemFont.setFontHeightInPoints((short)9);
+ itemFont.setFontName("Trebuchet MS");
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.LEFT);
+ style.setFont(itemFont);
+ styles.put("item_left", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.RIGHT);
+ style.setFont(itemFont);
+ styles.put("item_right", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.RIGHT);
+ style.setFont(itemFont);
+ style.setBorderRight(BorderStyle.DOTTED);
+ style.setRightBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderLeft(BorderStyle.DOTTED);
+ style.setLeftBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderTop(BorderStyle.DOTTED);
+ style.setTopBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setDataFormat(wb.createDataFormat().getFormat("_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)"));
+ styles.put("input_$", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.RIGHT);
+ style.setFont(itemFont);
+ style.setBorderRight(BorderStyle.DOTTED);
+ style.setRightBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderLeft(BorderStyle.DOTTED);
+ style.setLeftBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderTop(BorderStyle.DOTTED);
+ style.setTopBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setDataFormat(wb.createDataFormat().getFormat("0.000%"));
+ styles.put("input_%", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.RIGHT);
+ style.setFont(itemFont);
+ style.setBorderRight(BorderStyle.DOTTED);
+ style.setRightBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderLeft(BorderStyle.DOTTED);
+ style.setLeftBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderTop(BorderStyle.DOTTED);
+ style.setTopBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setDataFormat(wb.createDataFormat().getFormat("0"));
+ styles.put("input_i", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setFont(itemFont);
+ style.setDataFormat(wb.createDataFormat().getFormat("m/d/yy"));
+ styles.put("input_d", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.RIGHT);
+ style.setFont(itemFont);
+ style.setBorderRight(BorderStyle.DOTTED);
+ style.setRightBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderLeft(BorderStyle.DOTTED);
+ style.setLeftBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderTop(BorderStyle.DOTTED);
+ style.setTopBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setDataFormat(wb.createDataFormat().getFormat("$##,##0.00"));
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ styles.put("formula_$", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.RIGHT);
+ style.setFont(itemFont);
+ style.setBorderRight(BorderStyle.DOTTED);
+ style.setRightBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderLeft(BorderStyle.DOTTED);
+ style.setLeftBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setBorderTop(BorderStyle.DOTTED);
+ style.setTopBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setDataFormat(wb.createDataFormat().getFormat("0"));
+ style.setBorderBottom(BorderStyle.DOTTED);
+ style.setBottomBorderColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ styles.put("formula_i", style);
+
+ return styles;
+ }
+
+ //define named ranges for the inputs and formulas
+ public static void createNames(Workbook wb){
+ Name name;
+
+ name = wb.createName();
+ name.setNameName("Interest_Rate");
+ name.setRefersToFormula("'Loan Calculator'!$E$5");
+
+ name = wb.createName();
+ name.setNameName("Loan_Amount");
+ name.setRefersToFormula("'Loan Calculator'!$E$4");
+
+ name = wb.createName();
+ name.setNameName("Loan_Start");
+ name.setRefersToFormula("'Loan Calculator'!$E$7");
+
+ name = wb.createName();
+ name.setNameName("Loan_Years");
+ name.setRefersToFormula("'Loan Calculator'!$E$6");
+
+ name = wb.createName();
+ name.setNameName("Number_of_Payments");
+ name.setRefersToFormula("'Loan Calculator'!$E$10");
+
+ name = wb.createName();
+ name.setNameName("Monthly_Payment");
+ name.setRefersToFormula("-PMT(Interest_Rate/12,Number_of_Payments,Loan_Amount)");
+
+ name = wb.createName();
+ name.setNameName("Total_Cost");
+ name.setRefersToFormula("'Loan Calculator'!$E$12");
+
+ name = wb.createName();
+ name.setNameName("Total_Interest");
+ name.setRefersToFormula("'Loan Calculator'!$E$11");
+
+ name = wb.createName();
+ name.setNameName("Values_Entered");
+ name.setRefersToFormula("IF(Loan_Amount*Interest_Rate*Loan_Years*Loan_Start>0,1,0)");
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/SSPerformanceTest.java b/trunk/src/examples/src/org/apache/poi/ss/examples/SSPerformanceTest.java
new file mode 100644
index 000000000..10c3a4a7d
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/SSPerformanceTest.java
@@ -0,0 +1,229 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+package org.apache.poi.ss.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+public class SSPerformanceTest {
+ public static void main(String[] args) throws IOException {
+ if (args.length != 4) usage("need four command arguments");
+
+ String type = args[0];
+ long timeStarted = System.currentTimeMillis();
+ Workbook workBook = createWorkbook(type);
+ boolean isHType = workBook instanceof HSSFWorkbook;
+
+ int rows = parseInt(args[1], "Failed to parse rows value as integer");
+ int cols = parseInt(args[2], "Failed to parse cols value as integer");
+ boolean saveFile = parseInt(args[3], "Failed to parse saveFile value as integer") != 0;
+
+ addContent(workBook, isHType, rows, cols);
+
+ if (saveFile) {
+ String fileName = type + "_" + rows + "_" + cols + "." + getFileSuffix(args[0]);
+ saveFile(workBook, fileName);
+ }
+ long timeFinished = System.currentTimeMillis();
+ System.out.println("Elapsed " + (timeFinished-timeStarted)/1000 + " seconds");
+
+ workBook.close();
+ }
+
+ private static void addContent(Workbook workBook, boolean isHType, int rows, int cols) {
+ Map styles = createStyles(workBook);
+
+ Sheet sheet = workBook.createSheet("Main Sheet");
+
+ Cell headerCell = sheet.createRow(0).createCell(0);
+ headerCell.setCellValue("Header text is spanned across multiple cells");
+ headerCell.setCellStyle(styles.get("header"));
+ sheet.addMergedRegion(CellRangeAddress.valueOf("$A$1:$F$1"));
+
+ int sheetNo = 0;
+ int rowIndexInSheet = 1;
+ double value = 0;
+ Calendar calendar = Calendar.getInstance();
+ for (int rowIndex = 0; rowIndex < rows; rowIndex++) {
+ if (isHType && sheetNo != rowIndex / 0x10000) {
+ sheet = workBook.createSheet("Spillover from sheet " + (++sheetNo));
+ headerCell.setCellValue("Header text is spanned across multiple cells");
+ headerCell.setCellStyle(styles.get("header"));
+ sheet.addMergedRegion(CellRangeAddress.valueOf("$A$1:$F$1"));
+ rowIndexInSheet = 1;
+ }
+
+ Row row = sheet.createRow(rowIndexInSheet);
+ for (int colIndex = 0; colIndex < cols; colIndex++) {
+ value = populateCell(styles, value, calendar, rowIndex, row, colIndex);
+ }
+ rowIndexInSheet++;
+ }
+ }
+
+ private static double populateCell(Map styles, double value, Calendar calendar, int rowIndex, Row row, int colIndex) {
+ Cell cell = row.createCell(colIndex);
+ String address = new CellReference(cell).formatAsString();
+ switch (colIndex){
+ case 0:
+ // column A: default number format
+ cell.setCellValue(value++);
+ break;
+ case 1:
+ // column B: #,##0
+ cell.setCellValue(value++);
+ cell.setCellStyle(styles.get("#,##0.00"));
+ break;
+ case 2:
+ // column C: $#,##0.00
+ cell.setCellValue(value++);
+ cell.setCellStyle(styles.get("$#,##0.00"));
+ break;
+ case 3:
+ // column D: red bold text on yellow background
+ cell.setCellValue(address);
+ cell.setCellStyle(styles.get("red-bold"));
+ break;
+ case 4:
+ // column E: boolean
+ // TODO booleans are shown as 1/0 instead of TRUE/FALSE
+ cell.setCellValue(rowIndex % 2 == 0);
+ break;
+ case 5:
+ // column F: date / time
+ cell.setCellValue(calendar);
+ cell.setCellStyle(styles.get("m/d/yyyy"));
+ calendar.roll(Calendar.DAY_OF_YEAR, -1);
+ break;
+ case 6:
+ // column F: formula
+ // TODO formulas are not yet supported in SXSSF
+ //cell.setCellFormula("SUM(A" + (rowIndex+1) + ":E" + (rowIndex+1)+ ")");
+ //break;
+ default:
+ cell.setCellValue(value++);
+ break;
+ }
+ return value;
+ }
+
+ private static void saveFile(Workbook workBook, String fileName) {
+ try {
+ FileOutputStream out = new FileOutputStream(fileName);
+ workBook.write(out);
+ out.close();
+ } catch (IOException ioe) {
+ System.err.println("Error: failed to write to file \"" + fileName + "\", reason=" + ioe.getMessage());
+ }
+ }
+
+ static Map createStyles(Workbook wb) {
+ Map styles = new HashMap();
+ CellStyle style;
+
+ Font headerFont = wb.createFont();
+ headerFont.setFontHeightInPoints((short) 14);
+ headerFont.setBold(true);
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFont(headerFont);
+ style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ styles.put("header", style);
+
+ Font monthFont = wb.createFont();
+ monthFont.setFontHeightInPoints((short)12);
+ monthFont.setColor(IndexedColors.RED.getIndex());
+ monthFont.setBold(true);
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setFont(monthFont);
+ styles.put("red-bold", style);
+
+ String[] nfmt = {"#,##0.00", "$#,##0.00", "m/d/yyyy"};
+ for(String fmt : nfmt){
+ style = wb.createCellStyle();
+ style.setDataFormat(wb.createDataFormat().getFormat(fmt));
+ styles.put(fmt, style);
+ }
+
+ return styles;
+ }
+
+
+ static void usage(String message) {
+ System.err.println(message);
+ System.err.println("usage: java SSPerformanceTest HSSF|XSSF|SXSSF rows cols saveFile (0|1)? ");
+ System.exit(1);
+ }
+
+ static Workbook createWorkbook(String type) {
+ if ("HSSF".equals(type))
+ return new HSSFWorkbook();
+ else if ("XSSF".equals(type))
+ return new XSSFWorkbook();
+ else if ("SXSSF".equals(type))
+ return new SXSSFWorkbook();
+ else
+ usage("Unknown type \"" + type + "\"");
+ return null;
+ }
+
+ static String getFileSuffix(String type) {
+ if ("HSSF".equals(type))
+ return "xls";
+ else if ("XSSF".equals(type))
+ return "xlsx";
+ else if ("SXSSF".equals(type))
+ return "xlsx";
+ return null;
+ }
+
+ static int parseInt(String value, String msg) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ usage(msg);
+ }
+ return 0;
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java b/trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
new file mode 100644
index 000000000..8ef20fe43
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/TimesheetDemo.java
@@ -0,0 +1,220 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.examples;
+
+import org.apache.poi.xssf.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.io.FileOutputStream;
+
+/**
+ * A weekly timesheet created using Apache POI.
+ * Usage:
+ * TimesheetDemo -xls|xlsx
+ *
+ * @author Yegor Kozlov
+ */
+public class TimesheetDemo {
+ private static final String[] titles = {
+ "Person", "ID", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
+ "Total\nHrs", "Overtime\nHrs", "Regular\nHrs"
+ };
+
+ private static Object[][] sample_data = {
+ {"Yegor Kozlov", "YK", 5.0, 8.0, 10.0, 5.0, 5.0, 7.0, 6.0},
+ {"Gisella Bronzetti", "GB", 4.0, 3.0, 1.0, 3.5, null, null, 4.0},
+ };
+
+ public static void main(String[] args) throws Exception {
+ Workbook wb;
+
+ if(args.length > 0 && args[0].equals("-xls")) wb = new HSSFWorkbook();
+ else wb = new XSSFWorkbook();
+
+ Map styles = createStyles(wb);
+
+ Sheet sheet = wb.createSheet("Timesheet");
+ PrintSetup printSetup = sheet.getPrintSetup();
+ printSetup.setLandscape(true);
+ sheet.setFitToPage(true);
+ sheet.setHorizontallyCenter(true);
+
+ //title row
+ Row titleRow = sheet.createRow(0);
+ titleRow.setHeightInPoints(45);
+ Cell titleCell = titleRow.createCell(0);
+ titleCell.setCellValue("Weekly Timesheet");
+ titleCell.setCellStyle(styles.get("title"));
+ sheet.addMergedRegion(CellRangeAddress.valueOf("$A$1:$L$1"));
+
+ //header row
+ Row headerRow = sheet.createRow(1);
+ headerRow.setHeightInPoints(40);
+ Cell headerCell;
+ for (int i = 0; i < titles.length; i++) {
+ headerCell = headerRow.createCell(i);
+ headerCell.setCellValue(titles[i]);
+ headerCell.setCellStyle(styles.get("header"));
+ }
+
+ int rownum = 2;
+ for (int i = 0; i < 10; i++) {
+ Row row = sheet.createRow(rownum++);
+ for (int j = 0; j < titles.length; j++) {
+ Cell cell = row.createCell(j);
+ if(j == 9){
+ //the 10th cell contains sum over week days, e.g. SUM(C3:I3)
+ String ref = "C" +rownum+ ":I" + rownum;
+ cell.setCellFormula("SUM("+ref+")");
+ cell.setCellStyle(styles.get("formula"));
+ } else if (j == 11){
+ cell.setCellFormula("J" +rownum+ "-K" + rownum);
+ cell.setCellStyle(styles.get("formula"));
+ } else {
+ cell.setCellStyle(styles.get("cell"));
+ }
+ }
+ }
+
+ //row with totals below
+ Row sumRow = sheet.createRow(rownum++);
+ sumRow.setHeightInPoints(35);
+ Cell cell;
+ cell = sumRow.createCell(0);
+ cell.setCellStyle(styles.get("formula"));
+ cell = sumRow.createCell(1);
+ cell.setCellValue("Total Hrs:");
+ cell.setCellStyle(styles.get("formula"));
+
+ for (int j = 2; j < 12; j++) {
+ cell = sumRow.createCell(j);
+ String ref = (char)('A' + j) + "3:" + (char)('A' + j) + "12";
+ cell.setCellFormula("SUM(" + ref + ")");
+ if(j >= 9) cell.setCellStyle(styles.get("formula_2"));
+ else cell.setCellStyle(styles.get("formula"));
+ }
+ rownum++;
+ sumRow = sheet.createRow(rownum++);
+ sumRow.setHeightInPoints(25);
+ cell = sumRow.createCell(0);
+ cell.setCellValue("Total Regular Hours");
+ cell.setCellStyle(styles.get("formula"));
+ cell = sumRow.createCell(1);
+ cell.setCellFormula("L13");
+ cell.setCellStyle(styles.get("formula_2"));
+ sumRow = sheet.createRow(rownum++);
+ sumRow.setHeightInPoints(25);
+ cell = sumRow.createCell(0);
+ cell.setCellValue("Total Overtime Hours");
+ cell.setCellStyle(styles.get("formula"));
+ cell = sumRow.createCell(1);
+ cell.setCellFormula("K13");
+ cell.setCellStyle(styles.get("formula_2"));
+
+ //set sample data
+ for (int i = 0; i < sample_data.length; i++) {
+ Row row = sheet.getRow(2 + i);
+ for (int j = 0; j < sample_data[i].length; j++) {
+ if(sample_data[i][j] == null) continue;
+
+ if(sample_data[i][j] instanceof String) {
+ row.getCell(j).setCellValue((String)sample_data[i][j]);
+ } else {
+ row.getCell(j).setCellValue((Double)sample_data[i][j]);
+ }
+ }
+ }
+
+ //finally set column widths, the width is measured in units of 1/256th of a character width
+ sheet.setColumnWidth(0, 30*256); //30 characters wide
+ for (int i = 2; i < 9; i++) {
+ sheet.setColumnWidth(i, 6*256); //6 characters wide
+ }
+ sheet.setColumnWidth(10, 10*256); //10 characters wide
+
+ // Write the output to a file
+ String file = "timesheet.xls";
+ if(wb instanceof XSSFWorkbook) file += "x";
+ FileOutputStream out = new FileOutputStream(file);
+ wb.write(out);
+ out.close();
+ }
+
+ /**
+ * Create a library of cell styles
+ */
+ private static Map createStyles(Workbook wb){
+ Map styles = new HashMap();
+ CellStyle style;
+ Font titleFont = wb.createFont();
+ titleFont.setFontHeightInPoints((short)18);
+ titleFont.setBold(true);
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFont(titleFont);
+ styles.put("title", style);
+
+ Font monthFont = wb.createFont();
+ monthFont.setFontHeightInPoints((short)11);
+ monthFont.setColor(IndexedColors.WHITE.getIndex());
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setFont(monthFont);
+ style.setWrapText(true);
+ styles.put("header", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setWrapText(true);
+ style.setBorderRight(BorderStyle.THIN);
+ style.setRightBorderColor(IndexedColors.BLACK.getIndex());
+ style.setBorderLeft(BorderStyle.THIN);
+ style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
+ style.setBorderTop(BorderStyle.THIN);
+ style.setTopBorderColor(IndexedColors.BLACK.getIndex());
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
+ styles.put("cell", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setDataFormat(wb.createDataFormat().getFormat("0.00"));
+ styles.put("formula", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setDataFormat(wb.createDataFormat().getFormat("0.00"));
+ styles.put("formula_2", style);
+
+ return styles;
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java b/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java
new file mode 100644
index 000000000..e8bca901f
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/ToCSV.java
@@ -0,0 +1,771 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.examples;
+
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+
+/**
+ * Demonstrates one way to convert an Excel spreadsheet into a CSV
+ * file. This class makes the following assumptions;
+ *
+ *
1. Where the Excel workbook contains more that one worksheet, then a single
+ * CSV file will contain the data from all of the worksheets.
+ *
2. The data matrix contained in the CSV file will be square. This means that
+ * the number of fields in each record of the CSV file will match the number
+ * of cells in the longest row found in the Excel workbook. Any short records
+ * will be 'padded' with empty fields - an empty field is represented in the
+ * the CSV file in this way - ,,.
+ *
3. Empty fields will represent missing cells.
+ *
4. A record consisting of empty fields will be used to represent an empty row
+ * in the Excel workbook.
+ *
+ * Therefore, if the worksheet looked like this;
+ *
+ *
+ * Typically, the comma is used to separate each of the fields that, together,
+ * constitute a single record or line within the CSV file. This is not however
+ * a hard and fast rule and so this class allows the user to determine which
+ * character is used as the field separator and assumes the comma if none other
+ * is specified.
+ *
+ * If a field contains the separator then it will be escaped. If the file should
+ * obey Excel's CSV formatting rules, then the field will be surrounded with
+ * speech marks whilst if it should obey UNIX conventions, each occurrence of
+ * the separator will be preceded by the backslash character.
+ *
+ * If a field contains an end of line (EOL) character then it too will be
+ * escaped. If the file should obey Excel's CSV formatting rules then the field
+ * will again be surrounded by speech marks. On the other hand, if the file
+ * should follow UNIX conventions then a single backslash will precede the
+ * EOL character. There is no single applicable standard for UNIX and some
+ * appications replace the CR with \r and the LF with \n but this class will
+ * not do so.
+ *
+ * If the field contains double quotes then that character will be escaped. It
+ * seems as though UNIX does not define a standard for this whilst Excel does.
+ * Should the CSV file have to obey Excel's formmating rules then the speech
+ * mark character will be escaped with a second set of speech marks. Finally, an
+ * enclosing set of speah marks will also surround the entire field. Thus, if
+ * the following line of text appeared in a cell - "Hello" he said - it would
+ * look like this when converted into a field within a CSV file - """Hello"" he
+ * said".
+ *
+ * Finally, it is worth noting that talk of CSV 'standards' is really slightly
+ * missleading as there is no such thing. It may well be that the code in this
+ * class has to be modified to produce files to suit a specific application
+ * or requirement.
+ *
+ * @author Mark B
+ * @version 1.00 9th April 2010
+ * 1.10 13th April 2010 - Added support for processing all Excel
+ * workbooks in a folder along with the ability
+ * to specify a field separator character.
+ * 2.00 14th April 2010 - Added support for embedded characters; the
+ * field separator, EOL and double quotes or
+ * speech marks. In addition, gave the client
+ * the ability to select how these are handled,
+ * either obeying Excel's or UNIX formatting
+ * conventions.
+ */
+public class ToCSV {
+
+ private Workbook workbook = null;
+ private ArrayList> csvData = null;
+ private int maxRowWidth = 0;
+ private int formattingConvention = 0;
+ private DataFormatter formatter = null;
+ private FormulaEvaluator evaluator = null;
+ private String separator = null;
+
+ private static final String CSV_FILE_EXTENSION = ".csv";
+ private static final String DEFAULT_SEPARATOR = ",";
+
+ /**
+ * Identifies that the CSV file should obey Excel's formatting conventions
+ * with regard to escaping certain embedded characters - the field separator,
+ * speech mark and end of line (EOL) character
+ */
+ public static final int EXCEL_STYLE_ESCAPING = 0;
+
+ /**
+ * Identifies that the CSV file should obey UNIX formatting conventions
+ * with regard to escaping certain embedded characters - the field separator
+ * and end of line (EOL) character
+ */
+ public static final int UNIX_STYLE_ESCAPING = 1;
+
+ /**
+ * Process the contents of a folder, convert the contents of each Excel
+ * workbook into CSV format and save the resulting file to the specified
+ * folder using the same name as the original workbook with the .xls or
+ * .xlsx extension replaced by .csv. This method will ensure that the
+ * CSV file created contains the comma field separator and that embedded
+ * characters such as the field separator, the EOL and double quotes are
+ * escaped in accordance with Excel's convention.
+ *
+ * @param strSource An instance of the String class that encapsulates the
+ * name of and path to either a folder containing those Excel
+ * workbook(s) or the name of and path to an individual Excel workbook
+ * that is/are to be converted.
+ * @param strDestination An instance of the String class encapsulating the
+ * name of and path to a folder that will contain the resulting CSV
+ * files.
+ * @throws java.io.FileNotFoundException Thrown if any file cannot be located
+ * on the filesystem during processing.
+ * @throws java.io.IOException Thrown if the filesystem encounters any
+ * problems during processing.
+ * @throws java.lang.IllegalArgumentException Thrown if the values passed
+ * to the strSource parameter refers to a file or folder that does not
+ * exist or if the value passed to the strDestination paramater refers
+ * to a folder that does not exist or simply does not refer to a
+ * folder.
+ * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown
+ * if the xml markup encountered whilst parsing a SpreadsheetML
+ * file (.xlsx) is invalid.
+ */
+ public void convertExcelToCSV(String strSource, String strDestination)
+ throws FileNotFoundException, IOException,
+ IllegalArgumentException, InvalidFormatException {
+
+ // Simply chain the call to the overloaded convertExcelToCSV(String,
+ // String, String, int) method, pass the default separator and ensure
+ // that certain embedded characters are escaped in accordance with
+ // Excel's formatting conventions
+ this.convertExcelToCSV(strSource, strDestination,
+ ToCSV.DEFAULT_SEPARATOR, ToCSV.EXCEL_STYLE_ESCAPING);
+ }
+
+ /**
+ * Process the contents of a folder, convert the contents of each Excel
+ * workbook into CSV format and save the resulting file to the specified
+ * folder using the same name as the original workbook with the .xls or
+ * .xlsx extension replaced by .csv. This method allows the client to
+ * define the field separator but will ensure that embedded characters such
+ * as the field separator, the EOL and double quotes are escaped in
+ * accordance with Excel's convention.
+ *
+ * @param strSource An instance of the String class that encapsulates the
+ * name of and path to either a folder containing those Excel
+ * workbook(s) or the name of and path to an individual Excel workbook
+ * that is/are to be converted.
+ * @param strDestination An instance of the String class encapsulating the
+ * name of and path to a folder that will contain the resulting CSV
+ * files.
+ * @param separator An instance of the String class that encapsulates the
+ * character or characters the client wishes to use as the field
+ * separator.
+ * @throws java.io.FileNotFoundException Thrown if any file cannot be located
+ * on the filesystem during processing.
+ * @throws java.io.IOException Thrown if the filesystem encounters any
+ * problems during processing.
+ * @throws java.lang.IllegalArgumentException Thrown if the values passed
+ * to the strSource parameter refers to a file or folder that does not
+ * exist or if the value passed to the strDestination paramater refers
+ * to a folder that does not exist or simply does not refer to a
+ * folder.
+ * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown
+ * if the xml markup encounetered whilst parsing a SpreadsheetML
+ * file (.xlsx) is invalid.
+ */
+ public void convertExcelToCSV(String strSource, String strDestination,
+ String separator)
+ throws FileNotFoundException, IOException,
+ IllegalArgumentException, InvalidFormatException {
+
+ // Simply chain the call to the overloaded convertExcelToCSV(String,
+ // String, String, int) method and ensure that certain embedded
+ // characters are escaped in accordance with Excel's formatting
+ // conventions
+ this.convertExcelToCSV(strSource, strDestination,
+ separator, ToCSV.EXCEL_STYLE_ESCAPING);
+ }
+
+ /**
+ * Process the contents of a folder, convert the contents of each Excel
+ * workbook into CSV format and save the resulting file to the specified
+ * folder using the same name as the original workbook with the .xls or
+ * .xlsx extension replaced by .csv
+ *
+ * @param strSource An instance of the String class that encapsulates the
+ * name of and path to either a folder containing those Excel
+ * workbook(s) or the name of and path to an individual Excel workbook
+ * that is/are to be converted.
+ * @param strDestination An instance of the String class encapsulating the name
+ * of and path to a folder that will contain the resulting CSV files.
+ * @param formattingConvention A primitive int whose value will determine
+ * whether certain embedded characters should be escaped in accordance
+ * with Excel's or UNIX formatting conventions. Two constants are
+ * defined to support this option; ToCSV.EXCEL_STYLE_ESCAPING and
+ * ToCSV.UNIX_STYLE_ESCAPING
+ * @param separator An instance of the String class encapsulating the
+ * characters or characters that should be used to separate items
+ * on a line within the CSV file.
+ * @throws java.io.FileNotFoundException Thrown if any file cannot be located
+ * on the filesystem during processing.
+ * @throws java.io.IOException Thrown if the filesystem encounters any
+ * problems during processing.
+ * @throws java.lang.IllegalArgumentException Thrown if the values passed
+ * to the strSource parameter refers to a file or folder that does not
+ * exist, if the value passed to the strDestination paramater refers
+ * to a folder that does not exist, if the value passed to the
+ * strDestination parameter does not refer to a folder or if the
+ * value passed to the formattingConvention parameter is other than
+ * one of the values defined by the constants ToCSV.EXCEL_STYLE_ESCAPING
+ * and ToCSV.UNIX_STYLE_ESCAPING.
+ * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown
+ * if the xml markup encounetered whilst parsing a SpreadsheetML
+ * file (.xlsx) is invalid.
+ */
+ public void convertExcelToCSV(String strSource, String strDestination,
+ String separator, int formattingConvention)
+ throws FileNotFoundException, IOException,
+ IllegalArgumentException, InvalidFormatException {
+ File source = new File(strSource);
+ File destination = new File(strDestination);
+ File[] filesList = null;
+ String destinationFilename = null;
+
+ // Check that the source file/folder exists.
+ if(!source.exists()) {
+ throw new IllegalArgumentException("The source for the Excel " +
+ "file(s) cannot be found.");
+ }
+
+ // Ensure thaat the folder the user has chosen to save the CSV files
+ // away into firstly exists and secondly is a folder rather than, for
+ // instance, a data file.
+ if(!destination.exists()) {
+ throw new IllegalArgumentException("The folder/directory for the " +
+ "converted CSV file(s) does not exist.");
+ }
+ if(!destination.isDirectory()) {
+ throw new IllegalArgumentException("The destination for the CSV " +
+ "file(s) is not a directory/folder.");
+ }
+
+ // Ensure the value passed to the formattingConvention parameter is
+ // within range.
+ if(formattingConvention != ToCSV.EXCEL_STYLE_ESCAPING &&
+ formattingConvention != ToCSV.UNIX_STYLE_ESCAPING) {
+ throw new IllegalArgumentException("The value passed to the " +
+ "formattingConvention parameter is out of range.");
+ }
+
+ // Copy the spearator character and formatting convention into local
+ // variables for use in other methods.
+ this.separator = separator;
+ this.formattingConvention = formattingConvention;
+
+ // Check to see if the sourceFolder variable holds a reference to
+ // a file or a folder full of files.
+ if(source.isDirectory()) {
+ // Get a list of all of the Excel spreadsheet files (workbooks) in
+ // the source folder/directory
+ filesList = source.listFiles(new ExcelFilenameFilter());
+ }
+ else {
+ // Assume that it must be a file handle - although there are other
+ // options the code should perhaps check - and store the reference
+ // into the filesList variable.
+ filesList = new File[]{source};
+ }
+
+ // Step through each of the files in the source folder and for each
+ // open the workbook, convert it's contents to CSV format and then
+ // save the resulting file away into the folder specified by the
+ // contents of the destination variable. Note that the name of the
+ // csv file will be created by taking the name of the Excel file,
+ // removing the extension and replacing it with .csv. Note that there
+ // is one drawback with this approach; if the folder holding the files
+ // contains two workbooks whose names match but one is a binary file
+ // (.xls) and the other a SpreadsheetML file (.xlsx), then the names
+ // for both CSV files will be identical and one CSV file will,
+ // therefore, over-write the other.
+ for(File excelFile : filesList) {
+ // Open the workbook
+ this.openWorkbook(excelFile);
+
+ // Convert it's contents into a CSV file
+ this.convertToCSV();
+
+ // Build the name of the csv folder from that of the Excel workbook.
+ // Simply replace the .xls or .xlsx file extension with .csv
+ destinationFilename = excelFile.getName();
+ destinationFilename = destinationFilename.substring(
+ 0, destinationFilename.lastIndexOf(".")) +
+ ToCSV.CSV_FILE_EXTENSION;
+
+ // Save the CSV file away using the newly constricted file name
+ // and to the specified directory.
+ this.saveCSVFile(new File(destination, destinationFilename));
+ }
+ }
+
+ /**
+ * Open an Excel workbook ready for conversion.
+ *
+ * @param file An instance of the File class that encapsulates a handle
+ * to a valid Excel workbook. Note that the workbook can be in
+ * either binary (.xls) or SpreadsheetML (.xlsx) format.
+ * @throws java.io.FileNotFoundException Thrown if the file cannot be located.
+ * @throws java.io.IOException Thrown if a problem occurs in the file system.
+ * @throws org.apache.poi.openxml4j.exceptions.InvalidFormatException Thrown
+ * if invalid xml is found whilst parsing an input SpreadsheetML
+ * file.
+ */
+ private void openWorkbook(File file) throws FileNotFoundException,
+ IOException, InvalidFormatException {
+ FileInputStream fis = null;
+ try {
+ System.out.println("Opening workbook [" + file.getName() + "]");
+
+ fis = new FileInputStream(file);
+
+ // Open the workbook and then create the FormulaEvaluator and
+ // DataFormatter instances that will be needed to, respectively,
+ // force evaluation of forumlae found in cells and create a
+ // formatted String encapsulating the cells contents.
+ this.workbook = WorkbookFactory.create(fis);
+ this.evaluator = this.workbook.getCreationHelper().createFormulaEvaluator();
+ this.formatter = new DataFormatter(true);
+ }
+ finally {
+ if(fis != null) {
+ fis.close();
+ }
+ }
+ }
+
+ /**
+ * Called to convert the contents of the currently opened workbook into
+ * a CSV file.
+ */
+ private void convertToCSV() {
+ Sheet sheet = null;
+ Row row = null;
+ int lastRowNum = 0;
+ this.csvData = new ArrayList>();
+
+ System.out.println("Converting files contents to CSV format.");
+
+ // Discover how many sheets there are in the workbook....
+ int numSheets = this.workbook.getNumberOfSheets();
+
+ // and then iterate through them.
+ for(int i = 0; i < numSheets; i++) {
+
+ // Get a reference to a sheet and check to see if it contains
+ // any rows.
+ sheet = this.workbook.getSheetAt(i);
+ if(sheet.getPhysicalNumberOfRows() > 0) {
+
+ // Note down the index number of the bottom-most row and
+ // then iterate through all of the rows on the sheet starting
+ // from the very first row - number 1 - even if it is missing.
+ // Recover a reference to the row and then call another method
+ // which will strip the data from the cells and build lines
+ // for inclusion in the resylting CSV file.
+ lastRowNum = sheet.getLastRowNum();
+ for(int j = 0; j <= lastRowNum; j++) {
+ row = sheet.getRow(j);
+ this.rowToCSV(row);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called to actually save the data recovered from the Excel workbook
+ * as a CSV file.
+ *
+ * @param file An instance of the File class that encapsulates a handle
+ * referring to the CSV file.
+ * @throws java.io.FileNotFoundException Thrown if the file cannot be found.
+ * @throws java.io.IOException Thrown to indicate and error occurred in the
+ * underylying file system.
+ */
+ private void saveCSVFile(File file)
+ throws FileNotFoundException, IOException {
+ FileWriter fw = null;
+ BufferedWriter bw = null;
+ ArrayList line = null;
+ StringBuffer buffer = null;
+ String csvLineElement = null;
+ try {
+
+ System.out.println("Saving the CSV file [" + file.getName() + "]");
+
+ // Open a writer onto the CSV file.
+ fw = new FileWriter(file);
+ bw = new BufferedWriter(fw);
+
+ // Step through the elements of the ArrayList that was used to hold
+ // all of the data recovered from the Excel workbooks' sheets, rows
+ // and cells.
+ for(int i = 0; i < this.csvData.size(); i++) {
+ buffer = new StringBuffer();
+
+ // Get an element from the ArrayList that contains the data for
+ // the workbook. This element will itself be an ArrayList
+ // containing Strings and each String will hold the data recovered
+ // from a single cell. The for() loop is used to recover elements
+ // from this 'row' ArrayList one at a time and to write the Strings
+ // away to a StringBuffer thus assembling a single line for inclusion
+ // in the CSV file. If a row was empty or if it was short, then
+ // the ArrayList that contains it's data will also be shorter than
+ // some of the others. Therefore, it is necessary to check within
+ // the for loop to ensure that the ArrayList contains data to be
+ // processed. If it does, then an element will be recovered and
+ // appended to the StringBuffer.
+ line = this.csvData.get(i);
+ for(int j = 0; j < this.maxRowWidth; j++) {
+ if(line.size() > j) {
+ csvLineElement = line.get(j);
+ if(csvLineElement != null) {
+ buffer.append(this.escapeEmbeddedCharacters(
+ csvLineElement));
+ }
+ }
+ if(j < (this.maxRowWidth - 1)) {
+ buffer.append(this.separator);
+ }
+ }
+
+ // Once the line is built, write it away to the CSV file.
+ bw.write(buffer.toString().trim());
+
+ // Condition the inclusion of new line characters so as to
+ // avoid an additional, superfluous, new line at the end of
+ // the file.
+ if(i < (this.csvData.size() - 1)) {
+ bw.newLine();
+ }
+ }
+ }
+ finally {
+ if(bw != null) {
+ bw.flush();
+ bw.close();
+ }
+ }
+ }
+
+ /**
+ * Called to convert a row of cells into a line of data that can later be
+ * output to the CSV file.
+ *
+ * @param row An instance of either the HSSFRow or XSSFRow classes that
+ * encapsulates information about a row of cells recovered from
+ * an Excel workbook.
+ */
+ private void rowToCSV(Row row) {
+ Cell cell = null;
+ int lastCellNum = 0;
+ ArrayList csvLine = new ArrayList();
+
+ // Check to ensure that a row was recovered from the sheet as it is
+ // possible that one or more rows between other populated rows could be
+ // missing - blank. If the row does contain cells then...
+ if(row != null) {
+
+ // Get the index for the right most cell on the row and then
+ // step along the row from left to right recovering the contents
+ // of each cell, converting that into a formatted String and
+ // then storing the String into the csvLine ArrayList.
+ lastCellNum = row.getLastCellNum();
+ for(int i = 0; i <= lastCellNum; i++) {
+ cell = row.getCell(i);
+ if(cell == null) {
+ csvLine.add("");
+ }
+ else {
+ if(cell.getCellTypeEnum() != CellType.FORMULA) {
+ csvLine.add(this.formatter.formatCellValue(cell));
+ }
+ else {
+ csvLine.add(this.formatter.formatCellValue(cell, this.evaluator));
+ }
+ }
+ }
+ // Make a note of the index number of the right most cell. This value
+ // will later be used to ensure that the matrix of data in the CSV file
+ // is square.
+ if(lastCellNum > this.maxRowWidth) {
+ this.maxRowWidth = lastCellNum;
+ }
+ }
+ this.csvData.add(csvLine);
+ }
+
+ /**
+ * Checks to see whether the field - which consists of the formatted
+ * contents of an Excel worksheet cell encapsulated within a String - contains
+ * any embedded characters that must be escaped. The method is able to
+ * comply with either Excel's or UNIX formatting conventions in the
+ * following manner;
+ *
+ * With regard to UNIX conventions, if the field contains any embedded
+ * field separator or EOL characters they will each be escaped by prefixing
+ * a leading backspace character. These are the only changes that have yet
+ * emerged following some research as being required.
+ *
+ * Excel has other embedded character escaping requirements, some that emerged
+ * from empirical testing, other through research. Firstly, with regards to
+ * any embedded speech marks ("), each occurrence should be escaped with
+ * another speech mark and the whole field then surrounded with speech marks.
+ * Thus if a field holds "Hello" he said then it should be modified
+ * to appear as """Hello"" he said". Furthermore, if the field
+ * contains either embedded separator or EOL characters, it should also
+ * be surrounded with speech marks. As a result 1,400 would become
+ * "1,400" assuming that the comma is the required field separator.
+ * This has one consequence in, if a field contains embedded speech marks
+ * and embedded separator characters, checks for both are not required as the
+ * additional set of speech marks that should be placed around ay field
+ * containing embedded speech marks will also account for the embedded
+ * separator.
+ *
+ * It is worth making one further note with regard to embedded EOL
+ * characters. If the data in a worksheet is exported as a CSV file using
+ * Excel itself, then the field will be surounded with speech marks. If the
+ * resulting CSV file is then re-imports into another worksheet, the EOL
+ * character will result in the original simgle field occupying more than
+ * one cell. This same 'feature' is replicated in this classes behaviour.
+ *
+ * @param field An instance of the String class encapsulating the formatted
+ * contents of a cell on an Excel worksheet.
+ * @return A String that encapsulates the formatted contents of that
+ * Excel worksheet cell but with any embedded separator, EOL or
+ * speech mark characters correctly escaped.
+ */
+ private String escapeEmbeddedCharacters(String field) {
+ StringBuffer buffer = null;
+
+ // If the fields contents should be formatted to confrom with Excel's
+ // convention....
+ if(this.formattingConvention == ToCSV.EXCEL_STYLE_ESCAPING) {
+
+ // Firstly, check if there are any speech marks (") in the field;
+ // each occurrence must be escaped with another set of spech marks
+ // and then the entire field should be enclosed within another
+ // set of speech marks. Thus, "Yes" he said would become
+ // """Yes"" he said"
+ if(field.contains("\"")) {
+ buffer = new StringBuffer(field.replaceAll("\"", "\\\"\\\""));
+ buffer.insert(0, "\"");
+ buffer.append("\"");
+ }
+ else {
+ // If the field contains either embedded separator or EOL
+ // characters, then escape the whole field by surrounding it
+ // with speech marks.
+ buffer = new StringBuffer(field);
+ if((buffer.indexOf(this.separator)) > -1 ||
+ (buffer.indexOf("\n")) > -1) {
+ buffer.insert(0, "\"");
+ buffer.append("\"");
+ }
+ }
+ return(buffer.toString().trim());
+ }
+ // The only other formatting convention this class obeys is the UNIX one
+ // where any occurrence of the field separator or EOL character will
+ // be escaped by preceding it with a backslash.
+ else {
+ if(field.contains(this.separator)) {
+ field = field.replaceAll(this.separator, ("\\\\" + this.separator));
+ }
+ if(field.contains("\n")) {
+ field = field.replaceAll("\n", "\\\\\n");
+ }
+ return(field);
+ }
+ }
+
+ /**
+ * The main() method contains code that demonstrates how to use the class.
+ *
+ * @param args An array containing zero, one or more elements all of type
+ * String. Each element will encapsulate an argument specified by the
+ * user when running the program from the command prompt.
+ */
+ public static void main(String[] args) {
+ // Check the number of arguments passed to the main method. There
+ // must be two, three or four; the name of and path to either the folder
+ // containing the Excel files or an individual Excel workbook that is/are
+ // to be converted, the name of and path to the folder to which the CSV
+ // files should be written, - optionally - the separator character
+ // that should be used to separate individual items (fields) on the
+ // lines (records) of the CSV file and - again optionally - an integer
+ // that idicates whether the CSV file ought to obey Excel's or UNIX
+ // convnetions with regard to formatting fields that contain embedded
+ // separator, Speech mark or EOL character(s).
+ //
+ // Note that the names of the CSV files will be derived from those
+ // of the Excel file(s). Put simply the .xls or .xlsx extension will be
+ // replaced with .csv. Therefore, if the source folder contains files
+ // with matching names but different extensions - Test.xls and Test.xlsx
+ // for example - then the CSV file generated from one will overwrite
+ // that generated from the other.
+ ToCSV converter = null;
+ boolean converted = true;
+ long startTime = System.currentTimeMillis();
+ try {
+ converter = new ToCSV();
+ if(args.length == 2) {
+ // Just the Source File/Folder and Destination Folder were
+ // passed to the main method.
+ converter.convertExcelToCSV(args[0], args[1]);
+ }
+ else if(args.length == 3){
+ // The Source File/Folder, Destination Folder and Separator
+ // were passed to the main method.
+ converter.convertExcelToCSV(args[0], args[1], args[2]);
+ }
+ else if(args.length == 4) {
+ // The Source File/Folder, Destination Folder, Separator and
+ // Formatting Convnetion were passed to the main method.
+ converter.convertExcelToCSV(args[0], args[1],
+ args[2], Integer.parseInt(args[3]));
+ }
+ else {
+ // None or more than four parameters were passed so display
+ //a Usage message.
+ System.out.println("Usage: java ToCSV [Source File/Folder] " +
+ "[Destination Folder] [Separator] [Formatting Convention]\n" +
+ "\tSource File/Folder\tThis argument should contain the name of and\n" +
+ "\t\t\t\tpath to either a single Excel workbook or a\n" +
+ "\t\t\t\tfolder containing one or more Excel workbooks.\n" +
+ "\tDestination Folder\tThe name of and path to the folder that the\n" +
+ "\t\t\t\tCSV files should be written out into. The\n" +
+ "\t\t\t\tfolder must exist before running the ToCSV\n" +
+ "\t\t\t\tcode as it will not check for or create it.\n" +
+ "\tSeparator\t\tOptional. The character or characters that\n" +
+ "\t\t\t\tshould be used to separate fields in the CSV\n" +
+ "\t\t\t\trecord. If no value is passed then the comma\n" +
+ "\t\t\t\twill be assumed.\n" +
+ "\tFormatting Convention\tOptional. This argument can take one of two\n" +
+ "\t\t\t\tvalues. Passing 0 (zero) will result in a CSV\n" +
+ "\t\t\t\tfile that obeys Excel's formatting conventions\n" +
+ "\t\t\t\twhilst passing 1 (one) will result in a file\n" +
+ "\t\t\t\tthat obeys UNIX formatting conventions. If no\n" +
+ "\t\t\t\tvalue is passed, then the CSV file produced\n" +
+ "\t\t\t\twill obey Excel's formatting conventions.");
+ converted = false;
+ }
+ }
+ // It is not wise to have such a wide catch clause - Exception is very
+ // close to being at the top of the inheritance hierarchy - though it
+ // will suffice for this example as it is really not possible to recover
+ // easilly from an exceptional set of circumstances at this point in the
+ // program. It should however, ideally be replaced with one or more
+ // catch clauses optimised to handle more specific problems.
+ catch(Exception ex) {
+ System.out.println("Caught an: " + ex.getClass().getName());
+ System.out.println("Message: " + ex.getMessage());
+ System.out.println("Stacktrace follows:.....");
+ ex.printStackTrace(System.out);
+ converted = false;
+ }
+
+ if (converted) {
+ System.out.println("Conversion took " +
+ (int)((System.currentTimeMillis() - startTime)/1000) + " seconds");
+ }
+ }
+
+ /**
+ * An instance of this class can be used to control the files returned
+ * be a call to the listFiles() method when made on an instance of the
+ * File class and that object refers to a folder/directory
+ */
+ class ExcelFilenameFilter implements FilenameFilter {
+
+ /**
+ * Determine those files that will be returned by a call to the
+ * listFiles() method. In this case, the name of the file must end with
+ * either of the following two extension; '.xls' or '.xlsx'. For the
+ * future, it is very possible to parameterise this and allow the
+ * containing class to pass, for example, an array of Strings to this
+ * class on instantiation. Each element in that array could encapsulate
+ * a valid file extension - '.xls', '.xlsx', '.xlt', '.xlst', etc. These
+ * could then be used to control which files were returned by the call
+ * to the listFiles() method.
+ *
+ * @param file An instance of the File class that encapsulates a handle
+ * referring to the folder/directory that contains the file.
+ * @param name An instance of the String class that encapsulates the
+ * name of the file.
+ * @return A boolean value that indicates whether the file should be
+ * included in the array retirned by the call to the listFiles()
+ * method. In this case true will be returned if the name of the
+ * file ends with either '.xls' or '.xlsx' and false will be
+ * returned in all other instances.
+ */
+ public boolean accept(File file, String name) {
+ return(name.endsWith(".xls") || name.endsWith(".xlsx"));
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/formula/CalculateMortgage.java b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/CalculateMortgage.java
new file mode 100644
index 000000000..4b9a325cd
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/CalculateMortgage.java
@@ -0,0 +1,93 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.formula;
+
+import org.apache.poi.ss.formula.OperationEvaluationContext ;
+import org.apache.poi.ss.formula.eval.ErrorEval ;
+import org.apache.poi.ss.formula.eval.EvaluationException ;
+import org.apache.poi.ss.formula.eval.NumberEval ;
+import org.apache.poi.ss.formula.eval.OperandResolver ;
+import org.apache.poi.ss.formula.eval.ValueEval ;
+import org.apache.poi.ss.formula.functions.FreeRefFunction ;
+
+/**
+ * A simple user-defined function to calculate principal and interest.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class CalculateMortgage implements FreeRefFunction {
+
+ public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) {
+
+ // verify that we have enough data
+ if (args.length != 3) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ // declare doubles for values
+ double principal, rate, years, result;
+ try {
+ // extract values as ValueEval
+ ValueEval v1 = OperandResolver.getSingleValue( args[0],
+ ec.getRowIndex(),
+ ec.getColumnIndex() ) ;
+ ValueEval v2 = OperandResolver.getSingleValue( args[1],
+ ec.getRowIndex(),
+ ec.getColumnIndex() ) ;
+ ValueEval v3 = OperandResolver.getSingleValue( args[2],
+ ec.getRowIndex(),
+ ec.getColumnIndex() ) ;
+
+ // get data as doubles
+ principal = OperandResolver.coerceValueToDouble( v1 ) ;
+ rate = OperandResolver.coerceValueToDouble( v2 ) ;
+ years = OperandResolver.coerceValueToDouble( v3 ) ;
+
+ result = calculateMortgagePayment( principal, rate, years ) ;
+ System.out.println( "Result = " + result ) ;
+
+ checkValue(result);
+
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ return new NumberEval( result ) ;
+ }
+
+ public double calculateMortgagePayment( double p, double r, double y ) {
+ double i = r / 12 ;
+ double n = y * 12 ;
+
+ double principalAndInterest =
+ p * (( i * Math.pow((1 + i),n ) ) / ( Math.pow((1 + i),n) - 1)) ;
+
+ return principalAndInterest ;
+ }
+ /**
+ * Excel does not support infinities and NaNs, rather, it gives a #NUM! error in these cases
+ *
+ * @throws EvaluationException (#NUM!) if result is NaN> or Infinity
+ */
+ private void checkValue(double result) throws EvaluationException {
+ if (Double.isNaN(result) || Double.isInfinite(result)) {
+ throw new EvaluationException(ErrorEval.NUM_ERROR);
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/formula/CheckFunctionsSupported.java b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/CheckFunctionsSupported.java
new file mode 100644
index 000000000..cf6c176cc
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/CheckFunctionsSupported.java
@@ -0,0 +1,161 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.formula;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.poi.ss.formula.eval.NotImplementedException;
+import org.apache.poi.ss.formula.eval.NotImplementedFunctionException;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.CellReference;
+
+/**
+ * Attempts to re-evaluate all the formulas in the workbook, and
+ * reports what (if any) formula functions used are not (currently)
+ * supported by Apache POI.
+ *
+ *
This provides examples of how to evaluate formulas in excel
+ * files using Apache POI, along with how to handle errors whilst
+ * doing so.
+ */
+public class CheckFunctionsSupported {
+ public static void main(String[] args) throws Exception {
+ if (args.length < 1) {
+ System.err.println("Use:");
+ System.err.println(" CheckFunctionsSupported ");
+ return;
+ }
+
+ Workbook wb = WorkbookFactory.create(new File(args[0]));
+ CheckFunctionsSupported check = new CheckFunctionsSupported(wb);
+
+ // Fetch all the problems
+ List problems = new ArrayList();
+ for (int sn=0; sn unsupportedFunctions = new TreeSet();
+ for (FormulaEvaluationProblems p : problems) {
+ unsupportedFunctions.addAll(p.unsupportedFunctions);
+ }
+ if (unsupportedFunctions.isEmpty()) {
+ System.out.println("There are no unsupported formula functions used");
+ } else {
+ System.out.println("Unsupported formula functions:");
+ for (String function : unsupportedFunctions) {
+ System.out.println(" " + function);
+ }
+ System.out.println("Total unsupported functions = " + unsupportedFunctions.size());
+ }
+
+ // Report sheet by sheet
+ for (int sn=0; sn getUnsupportedFunctions(String sheetName) {
+ return getUnsupportedFunctions(workbook.getSheet(sheetName));
+ }
+ public Set getUnsupportedFunctions(int sheetIndex) {
+ return getUnsupportedFunctions(workbook.getSheetAt(sheetIndex));
+ }
+ public Set getUnsupportedFunctions(Sheet sheet) {
+ FormulaEvaluationProblems problems = getEvaluationProblems(sheet);
+ return problems.unsupportedFunctions;
+ }
+
+ public FormulaEvaluationProblems getEvaluationProblems(String sheetName) {
+ return getEvaluationProblems(workbook.getSheet(sheetName));
+ }
+ public FormulaEvaluationProblems getEvaluationProblems(int sheetIndex) {
+ return getEvaluationProblems(workbook.getSheetAt(sheetIndex));
+ }
+ public FormulaEvaluationProblems getEvaluationProblems(Sheet sheet) {
+ Set unsupportedFunctions = new HashSet();
+ Map unevaluatableCells = new HashMap();
+
+ for (Row r : sheet) {
+ for (Cell c : r) {
+ try {
+ evaluator.evaluate(c);
+ } catch (Exception e) {
+ if (e instanceof NotImplementedException && e.getCause() != null) {
+ // Has been wrapped with cell details, but we know those
+ e = (Exception)e.getCause();
+ }
+
+ if (e instanceof NotImplementedFunctionException) {
+ NotImplementedFunctionException nie = (NotImplementedFunctionException)e;
+ unsupportedFunctions.add(nie.getFunctionName());
+ }
+ unevaluatableCells.put(new CellReference(c), e);
+ }
+ }
+ }
+
+ return new FormulaEvaluationProblems(unsupportedFunctions, unevaluatableCells);
+ }
+
+ public static class FormulaEvaluationProblems {
+ /** Which used functions are unsupported by POI at this time */
+ public Set unsupportedFunctions;
+ /** Which cells had unevaluatable formulas, and why? */
+ public Map unevaluatableCells;
+
+ protected FormulaEvaluationProblems(Set unsupportedFunctions,
+ Map unevaluatableCells) {
+ this.unsupportedFunctions = Collections.unmodifiableSet(unsupportedFunctions);
+ this.unevaluatableCells = Collections.unmodifiableMap(unevaluatableCells);
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/formula/SettingExternalFunction.java b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/SettingExternalFunction.java
new file mode 100644
index 000000000..ae7079436
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/SettingExternalFunction.java
@@ -0,0 +1,94 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.ss.examples.formula;
+
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Demonstrates how to use functions provided by third-party add-ins, e.g. Bloomberg Excel Add-in.
+ *
+ * There can be situations when you are not interested in formula evaluation,
+ * you just need to set the formula and the workbook will be evaluation by the client.
+ *
+ * @author Yegor Kozlov
+ */
+public class SettingExternalFunction {
+
+ /**
+ * wrap external functions in a plugin
+ */
+ public static class BloombergAddIn implements UDFFinder {
+ private final Map _functionsByName;
+
+ public BloombergAddIn() {
+ // dummy function that returns NA
+ // don't care about the implementation, we are not interested in evaluation
+ // and this method will never be called
+ FreeRefFunction NA = new FreeRefFunction() {
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ return ErrorEval.NA;
+ }
+ };
+ _functionsByName = new HashMap();
+ _functionsByName.put("BDP", NA);
+ _functionsByName.put("BDH", NA);
+ _functionsByName.put("BDS", NA);
+ }
+
+ public FreeRefFunction findFunction(String name) {
+ return _functionsByName.get(name.toUpperCase(Locale.ROOT));
+ }
+
+ }
+
+ public static void main( String[] args ) throws IOException {
+
+ Workbook wb = new XSSFWorkbook(); // or new HSSFWorkbook()
+
+ // register the add-in
+ wb.addToolPack(new BloombergAddIn());
+
+ Sheet sheet = wb.createSheet();
+ Row row = sheet.createRow(0);
+ row.createCell(0).setCellFormula("BDP(\"GOOG Equity\",\"CHG_PCT_YTD\")/100");
+ row.createCell(1).setCellFormula("BDH(\"goog us equity\",\"EBIT\",\"1/1/2005\",\"12/31/2009\",\"per=cy\",\"curr=USD\") ");
+ row.createCell(2).setCellFormula("BDS(\"goog us equity\",\"top_20_holders_public_filings\") ");
+
+ FileOutputStream out = new FileOutputStream("bloomberg-demo.xlsx");
+ wb.write(out);
+ out.close();
+
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/formula/UserDefinedFunctionExample.java b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/UserDefinedFunctionExample.java
new file mode 100644
index 000000000..74dca7d1b
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/UserDefinedFunctionExample.java
@@ -0,0 +1,89 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.formula;
+
+import java.io.File ;
+import java.io.FileInputStream ;
+import java.io.FileNotFoundException ;
+import java.io.IOException ;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException ;
+import org.apache.poi.ss.formula.functions.FreeRefFunction ;
+import org.apache.poi.ss.formula.udf.DefaultUDFFinder ;
+import org.apache.poi.ss.formula.udf.UDFFinder ;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellReference ;
+
+
+/**
+ * An example class of how to invoke a User Defined Function for a given
+ * XLS instance using POI's UDFFinder implementation.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class UserDefinedFunctionExample {
+
+ public static void main( String[] args ) {
+
+ if( args.length != 2 ) {
+ System.out.println( "usage: UserDefinedFunctionExample fileName cellId" ) ;
+ return;
+ }
+
+ System.out.println( "fileName: " + args[0] ) ;
+ System.out.println( "cell: " + args[1] ) ;
+
+ File workbookFile = new File( args[0] ) ;
+
+ try {
+ FileInputStream fis = new FileInputStream(workbookFile);
+ Workbook workbook = WorkbookFactory.create(fis);
+ fis.close();
+
+ String[] functionNames = { "calculatePayment" } ;
+ FreeRefFunction[] functionImpls = { new CalculateMortgage() } ;
+
+ UDFFinder udfToolpack = new DefaultUDFFinder( functionNames, functionImpls ) ;
+
+ // register the user-defined function in the workbook
+ workbook.addToolPack(udfToolpack);
+
+ FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
+
+ CellReference cr = new CellReference( args[1] ) ;
+ String sheetName = cr.getSheetName() ;
+ Sheet sheet = workbook.getSheet( sheetName ) ;
+ int rowIdx = cr.getRow() ;
+ int colIdx = cr.getCol() ;
+ Row row = sheet.getRow( rowIdx ) ;
+ Cell cell = row.getCell( colIdx ) ;
+
+ CellValue value = evaluator.evaluate( cell ) ;
+
+ System.out.println("returns value: " + value ) ;
+
+ } catch( FileNotFoundException e ) {
+ e.printStackTrace();
+ } catch( InvalidFormatException e ) {
+ e.printStackTrace();
+ } catch( IOException e ) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/formula/mortgage-calculation.xls b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/mortgage-calculation.xls
new file mode 100644
index 000000000..4e71ba8e6
Binary files /dev/null and b/trunk/src/examples/src/org/apache/poi/ss/examples/formula/mortgage-calculation.xls differ
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java b/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java
new file mode 100644
index 000000000..1e235f929
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/html/HSSFHtmlHelper.java
@@ -0,0 +1,66 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.html;
+
+import org.apache.poi.hssf.usermodel.HSSFCellStyle;
+import org.apache.poi.hssf.usermodel.HSSFPalette;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.util.HSSFColor;
+import org.apache.poi.ss.usermodel.CellStyle;
+
+import java.util.Formatter;
+
+/**
+ * Implementation of {@link HtmlHelper} for HSSF files.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class HSSFHtmlHelper implements HtmlHelper {
+ private final HSSFWorkbook wb;
+ private final HSSFPalette colors;
+
+ private static final HSSFColor HSSF_AUTO = new HSSFColor.AUTOMATIC();
+
+ public HSSFHtmlHelper(HSSFWorkbook wb) {
+ this.wb = wb;
+ // If there is no custom palette, then this creates a new one that is
+ // a copy of the default
+ colors = wb.getCustomPalette();
+ }
+
+ public void colorStyles(CellStyle style, Formatter out) {
+ HSSFCellStyle cs = (HSSFCellStyle) style;
+ out.format(" /* fill pattern = %d */%n", cs.getFillPattern());
+ styleColor(out, "background-color", cs.getFillForegroundColor());
+ styleColor(out, "color", cs.getFont(wb).getColor());
+ styleColor(out, "border-left-color", cs.getLeftBorderColor());
+ styleColor(out, "border-right-color", cs.getRightBorderColor());
+ styleColor(out, "border-top-color", cs.getTopBorderColor());
+ styleColor(out, "border-bottom-color", cs.getBottomBorderColor());
+ }
+
+ private void styleColor(Formatter out, String attr, short index) {
+ HSSFColor color = colors.getColor(index);
+ if (index == HSSF_AUTO.getIndex() || color == null) {
+ out.format(" /* %s: index = %d */%n", attr, index);
+ } else {
+ short[] rgb = color.getTriplet();
+ out.format(" %s: #%02x%02x%02x; /* index = %d */%n", attr, rgb[0],
+ rgb[1], rgb[2], index);
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java b/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java
new file mode 100644
index 000000000..2cb1a9173
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/html/HtmlHelper.java
@@ -0,0 +1,40 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.examples.html;
+
+import org.apache.poi.ss.usermodel.CellStyle;
+
+import java.util.Formatter;
+
+/**
+ * This interface is used where code wants to be independent of the workbook
+ * formats. If you are writing such code, you can add a method to this
+ * interface, and then implement it for both HSSF and XSSF workbooks, letting
+ * the driving code stay independent of format.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public interface HtmlHelper {
+ /**
+ * Outputs the appropriate CSS style for the given cell style.
+ *
+ * @param style The cell style.
+ * @param out The place to write the output.
+ */
+ void colorStyles(CellStyle style, Formatter out);
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java b/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java
new file mode 100644
index 000000000..c5fdc1da9
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/html/ToHtml.java
@@ -0,0 +1,465 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.html;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.format.CellFormat;
+import org.apache.poi.ss.format.CellFormatResult;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * This example shows how to display a spreadsheet in HTML using the classes for
+ * spreadsheet display.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class ToHtml {
+ private final Workbook wb;
+ private final Appendable output;
+ private boolean completeHTML;
+ private Formatter out;
+ private boolean gotBounds;
+ private int firstColumn;
+ private int endColumn;
+ private HtmlHelper helper;
+
+ private static final String DEFAULTS_CLASS = "excelDefaults";
+ private static final String COL_HEAD_CLASS = "colHeader";
+ private static final String ROW_HEAD_CLASS = "rowHeader";
+
+ private static final Map HALIGN = mapFor(
+ HorizontalAlignment.LEFT, "left",
+ HorizontalAlignment.CENTER, "center",
+ HorizontalAlignment.RIGHT, "right",
+ HorizontalAlignment.FILL, "left",
+ HorizontalAlignment.JUSTIFY, "left",
+ HorizontalAlignment.CENTER_SELECTION, "center");
+
+ private static final Map VALIGN = mapFor(
+ VerticalAlignment.BOTTOM, "bottom",
+ VerticalAlignment.CENTER, "middle",
+ VerticalAlignment.TOP, "top");
+
+ private static final Map BORDER = mapFor(
+ BorderStyle.DASH_DOT, "dashed 1pt",
+ BorderStyle.DASH_DOT_DOT, "dashed 1pt",
+ BorderStyle.DASHED, "dashed 1pt",
+ BorderStyle.DOTTED, "dotted 1pt",
+ BorderStyle.DOUBLE, "double 3pt",
+ BorderStyle.HAIR, "solid 1px",
+ BorderStyle.MEDIUM, "solid 2pt",
+ BorderStyle.MEDIUM_DASH_DOT, "dashed 2pt",
+ BorderStyle.MEDIUM_DASH_DOT_DOT, "dashed 2pt",
+ BorderStyle.MEDIUM_DASHED, "dashed 2pt",
+ BorderStyle.NONE, "none",
+ BorderStyle.SLANTED_DASH_DOT, "dashed 2pt",
+ BorderStyle.THICK, "solid 3pt",
+ BorderStyle.THIN, "dashed 1pt");
+
+ @SuppressWarnings({"unchecked"})
+ private static Map mapFor(Object... mapping) {
+ Map map = new HashMap();
+ for (int i = 0; i < mapping.length; i += 2) {
+ map.put((K) mapping[i], (V) mapping[i + 1]);
+ }
+ return map;
+ }
+
+ /**
+ * Creates a new converter to HTML for the given workbook.
+ *
+ * @param wb The workbook.
+ * @param output Where the HTML output will be written.
+ *
+ * @return An object for converting the workbook to HTML.
+ */
+ public static ToHtml create(Workbook wb, Appendable output) {
+ return new ToHtml(wb, output);
+ }
+
+ /**
+ * Creates a new converter to HTML for the given workbook. If the path ends
+ * with ".xlsx" an {@link XSSFWorkbook} will be used; otherwise
+ * this will use an {@link HSSFWorkbook}.
+ *
+ * @param path The file that has the workbook.
+ * @param output Where the HTML output will be written.
+ *
+ * @return An object for converting the workbook to HTML.
+ */
+ public static ToHtml create(String path, Appendable output)
+ throws IOException {
+ return create(new FileInputStream(path), output);
+ }
+
+ /**
+ * Creates a new converter to HTML for the given workbook. This attempts to
+ * detect whether the input is XML (so it should create an {@link
+ * XSSFWorkbook} or not (so it should create an {@link HSSFWorkbook}).
+ *
+ * @param in The input stream that has the workbook.
+ * @param output Where the HTML output will be written.
+ *
+ * @return An object for converting the workbook to HTML.
+ */
+ public static ToHtml create(InputStream in, Appendable output)
+ throws IOException {
+ try {
+ Workbook wb = WorkbookFactory.create(in);
+ return create(wb, output);
+ } catch (InvalidFormatException e){
+ throw new IllegalArgumentException("Cannot create workbook from stream", e);
+ }
+ }
+
+ private ToHtml(Workbook wb, Appendable output) {
+ if (wb == null)
+ throw new NullPointerException("wb");
+ if (output == null)
+ throw new NullPointerException("output");
+ this.wb = wb;
+ this.output = output;
+ setupColorMap();
+ }
+
+ private void setupColorMap() {
+ if (wb instanceof HSSFWorkbook)
+ helper = new HSSFHtmlHelper((HSSFWorkbook) wb);
+ else if (wb instanceof XSSFWorkbook)
+ helper = new XSSFHtmlHelper();
+ else
+ throw new IllegalArgumentException(
+ "unknown workbook type: " + wb.getClass().getSimpleName());
+ }
+
+ /**
+ * Run this class as a program
+ *
+ * @param args The command line arguments.
+ *
+ * @throws Exception Exception we don't recover from.
+ */
+ public static void main(String[] args) throws Exception {
+ if(args.length < 2){
+ System.err.println("usage: ToHtml inputWorkbook outputHtmlFile");
+ return;
+ }
+
+ ToHtml toHtml = create(args[0], new PrintWriter(new FileWriter(args[1])));
+ toHtml.setCompleteHTML(true);
+ toHtml.printPage();
+ }
+
+ public void setCompleteHTML(boolean completeHTML) {
+ this.completeHTML = completeHTML;
+ }
+
+ public void printPage() throws IOException {
+ try {
+ ensureOut();
+ if (completeHTML) {
+ out.format(
+ "%n");
+ out.format("%n");
+ out.format("%n");
+ out.format("%n");
+ out.format("%n");
+ }
+
+ print();
+
+ if (completeHTML) {
+ out.format("%n");
+ out.format("%n");
+ }
+ } finally {
+ if (out != null)
+ out.close();
+ if (output instanceof Closeable) {
+ Closeable closeable = (Closeable) output;
+ closeable.close();
+ }
+ }
+ }
+
+ public void print() {
+ printInlineStyle();
+ printSheets();
+ }
+
+ private void printInlineStyle() {
+ //out.format("%n");
+ out.format("%n");
+ }
+
+ private void ensureOut() {
+ if (out == null)
+ out = new Formatter(output);
+ }
+
+ public void printStyles() {
+ ensureOut();
+
+ // First, copy the base css
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(
+ getClass().getResourceAsStream("excelStyle.css")));
+ String line;
+ while ((line = in.readLine()) != null) {
+ out.format("%s%n", line);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Reading standard css", e);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //noinspection ThrowFromFinallyBlock
+ throw new IllegalStateException("Reading standard css", e);
+ }
+ }
+ }
+
+ // now add css for each used style
+ Set seen = new HashSet();
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ Sheet sheet = wb.getSheetAt(i);
+ Iterator rows = sheet.rowIterator();
+ while (rows.hasNext()) {
+ Row row = rows.next();
+ for (Cell cell : row) {
+ CellStyle style = cell.getCellStyle();
+ if (!seen.contains(style)) {
+ printStyle(style);
+ seen.add(style);
+ }
+ }
+ }
+ }
+ }
+
+ private void printStyle(CellStyle style) {
+ out.format(".%s .%s {%n", DEFAULTS_CLASS, styleName(style));
+ styleContents(style);
+ out.format("}%n");
+ }
+
+ private void styleContents(CellStyle style) {
+ styleOut("text-align", style.getAlignmentEnum(), HALIGN);
+ styleOut("vertical-align", style.getVerticalAlignmentEnum(), VALIGN);
+ fontStyle(style);
+ borderStyles(style);
+ helper.colorStyles(style, out);
+ }
+
+ private void borderStyles(CellStyle style) {
+ styleOut("border-left", style.getBorderLeft(), BORDER);
+ styleOut("border-right", style.getBorderRight(), BORDER);
+ styleOut("border-top", style.getBorderTop(), BORDER);
+ styleOut("border-bottom", style.getBorderBottom(), BORDER);
+ }
+
+ private void fontStyle(CellStyle style) {
+ Font font = wb.getFontAt(style.getFontIndex());
+
+ if (font.getBold())
+ out.format(" font-weight: bold;%n");
+ if (font.getItalic())
+ out.format(" font-style: italic;%n");
+
+ int fontheight = font.getFontHeightInPoints();
+ if (fontheight == 9) {
+ //fix for stupid ol Windows
+ fontheight = 10;
+ }
+ out.format(" font-size: %dpt;%n", fontheight);
+
+ // Font color is handled with the other colors
+ }
+
+ private String styleName(CellStyle style) {
+ if (style == null)
+ style = wb.getCellStyleAt((short) 0);
+ StringBuilder sb = new StringBuilder();
+ Formatter fmt = new Formatter(sb);
+ try {
+ fmt.format("style_%02x", style.getIndex());
+ return fmt.toString();
+ } finally {
+ fmt.close();
+ }
+ }
+
+ private void styleOut(String attr, K key, Map mapping) {
+ String value = mapping.get(key);
+ if (value != null) {
+ out.format(" %s: %s;%n", attr, value);
+ }
+ }
+
+ private static CellType ultimateCellType(Cell c) {
+ CellType type = c.getCellTypeEnum();
+ if (type == CellType.FORMULA)
+ type = c.getCachedFormulaResultTypeEnum();
+ return type;
+ }
+
+ private void printSheets() {
+ ensureOut();
+ Sheet sheet = wb.getSheetAt(0);
+ printSheet(sheet);
+ }
+
+ public void printSheet(Sheet sheet) {
+ ensureOut();
+ out.format("
%n");
+ }
+ out.format("%n");
+ }
+
+ private String tagStyle(Cell cell, CellStyle style) {
+ if (style.getAlignmentEnum() == HorizontalAlignment.GENERAL) {
+ switch (ultimateCellType(cell)) {
+ case STRING:
+ return "style=\"text-align: left;\"";
+ case BOOLEAN:
+ case ERROR:
+ return "style=\"text-align: center;\"";
+ case NUMERIC:
+ default:
+ // "right" is the default
+ break;
+ }
+ }
+ return "";
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java b/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java
new file mode 100644
index 000000000..9b22e1ed0
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/html/XSSFHtmlHelper.java
@@ -0,0 +1,58 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.html;
+
+import java.util.Formatter;
+
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFColor;
+
+/**
+ * Implementation of {@link HtmlHelper} for XSSF files.
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+public class XSSFHtmlHelper implements HtmlHelper {
+ public void colorStyles(CellStyle style, Formatter out) {
+ XSSFCellStyle cs = (XSSFCellStyle) style;
+ styleColor(out, "background-color", cs.getFillForegroundXSSFColor());
+ styleColor(out, "text-color", cs.getFont().getXSSFColor());
+ }
+
+ private void styleColor(Formatter out, String attr, XSSFColor color) {
+ if (color == null || color.isAuto()) {
+ return;
+ }
+
+ byte[] rgb = color.getRGB();
+ if (rgb == null) {
+ return;
+ }
+
+ // This is done twice -- rgba is new with CSS 3, and browser that don't
+ // support it will ignore the rgba specification and stick with the
+ // solid color, which is declared first
+ out.format(" %s: #%02x%02x%02x;%n", attr, rgb[0], rgb[1], rgb[2]);
+ byte[] argb = color.getARGB();
+ if (argb == null) {
+ return;
+ }
+ out.format(" %s: rgba(0x%02x, 0x%02x, 0x%02x, 0x%02x);%n", attr,
+ argb[3], argb[0], argb[1], argb[2]);
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css b/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css
new file mode 100644
index 000000000..1083b637a
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/html/excelStyle.css
@@ -0,0 +1,72 @@
+/*
+ ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ====================================================================
+ */
+/*
+ * This is the default style sheet for html generated by ToHtml
+ *
+ * @author Ken Arnold, Industrious Media LLC
+ */
+.excelDefaults {
+ background-color: white;
+ color: black;
+ text-decoration: none;
+ direction: ltr;
+ text-transform: none;
+ text-indent: 0;
+ letter-spacing: 0;
+ word-spacing: 0;
+ white-space: normal;
+ unicode-bidi: normal;
+ vertical-align: 0;
+ background-image: none;
+ text-shadow: none;
+ list-style-image: none;
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+ border-collapse: collapse;
+ white-space: pre;
+ vertical-align: bottom;
+ font-style: normal;
+ font-family: sans-serif;
+ font-variant: normal;
+ font-weight: normal;
+ font-size: 10pt;
+ text-align: right;
+}
+
+.excelDefaults td {
+ padding: 1px 5px;
+ border: 1px solid silver;
+}
+
+.excelDefaults .colHeader {
+ background-color: silver;
+ font-weight: bold;
+ border: 1px solid black;
+ text-align: center;
+ padding: 1px 5px;
+}
+
+.excelDefaults .rowHeader {
+ background-color: silver;
+ font-weight: bold;
+ border: 1px solid black;
+ text-align: right;
+ padding: 1px 5px;
+}
diff --git a/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html b/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html
new file mode 100644
index 000000000..1c8e6af5c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/ss/examples/html/package.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+This package contains an example that uses POI to convert a workbook into
+an HTML representation of the data. It can use both XSSF and HSSF workbooks.
+
+
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/AddVideoToPptx.java.txt b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/AddVideoToPptx.java.txt
new file mode 100644
index 000000000..2d32c4d81
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/AddVideoToPptx.java.txt
@@ -0,0 +1,251 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.text.DecimalFormat;
+
+import javax.imageio.ImageIO;
+import javax.xml.namespace.QName;
+
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
+import org.apache.xmlbeans.XmlCursor;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink;
+import org.openxmlformats.schemas.officeDocument.x2006.relationships.STRelationshipId;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTApplicationNonVisualDrawingProps;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTExtension;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTSlide;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTTLCommonMediaNodeData;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTTLCommonTimeNodeData;
+import org.openxmlformats.schemas.presentationml.x2006.main.CTTimeNodeList;
+import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeIndefinite;
+import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeNodeFillType;
+import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeNodeRestartType;
+import org.openxmlformats.schemas.presentationml.x2006.main.STTLTimeNodeType;
+
+import com.xuggle.mediatool.IMediaReader;
+import com.xuggle.mediatool.MediaListenerAdapter;
+import com.xuggle.mediatool.ToolFactory;
+import com.xuggle.mediatool.event.IVideoPictureEvent;
+import com.xuggle.xuggler.Global;
+import com.xuggle.xuggler.IContainer;
+import com.xuggle.xuggler.io.InputOutputStreamHandler;
+
+/**
+ * Adding multiple videos to a slide
+ *
+ * need the Xuggler 5.4 jars:
+ * <repositories>
+ * <repository>
+ * <id>xuggle repo</id>
+ * <url>http://xuggle.googlecode.com/svn/trunk/repo/share/java/</url>
+ * </repository>
+ * </repositories>
+ * ...
+ * <dependency>
+ * <groupId>xuggle</groupId>
+ * <artifactId>xuggle-xuggler</artifactId>
+ * <version>5.4</version>
+ * </dependency>
+ *
+ * @see Apache POI XSLF Adding movie to the slide
+ * @see Question about embedded video in PPTX files
+ */
+public class AddVideoToPptx {
+ static DecimalFormat df_time = new DecimalFormat("0.####");
+
+ public static void main(String[] args) throws Exception {
+ URL video = new URL("http://archive.org/download/test-mpeg/test-mpeg.mpg");
+ // URL video = new URL("file:test-mpeg.mpg");
+
+ XMLSlideShow pptx = new XMLSlideShow();
+
+ // add video file
+ String videoFileName = video.getPath().substring(video.getPath().lastIndexOf('/')+1);
+ PackagePartName partName = PackagingURIHelper.createPartName("/ppt/media/"+videoFileName);
+ PackagePart part = pptx.getPackage().createPart(partName, "video/mpeg");
+ OutputStream partOs = part.getOutputStream();
+ InputStream fis = video.openStream();
+ byte buf[] = new byte[1024];
+ for (int readBytes; (readBytes = fis.read(buf)) != -1; partOs.write(buf, 0, readBytes));
+ fis.close();
+ partOs.close();
+
+ XSLFSlide slide1 = pptx.createSlide();
+ XSLFPictureShape pv1 = addPreview(pptx, slide1, part, 5, 50, 50);
+ addVideo(pptx, slide1, part, pv1, 5);
+ addTimingInfo(slide1, pv1);
+ XSLFPictureShape pv2 = addPreview(pptx, slide1, part, 9, 50, 250);
+ addVideo(pptx, slide1, part, pv2, 9);
+ addTimingInfo(slide1, pv2);
+
+ FileOutputStream fos = new FileOutputStream("pptx-with-video.pptx");
+ pptx.write(fos);
+ fos.close();
+
+ pptx.close();
+ }
+
+ static XSLFPictureShape addPreview(XMLSlideShow pptx, XSLFSlide slide1, PackagePart videoPart, double seconds, int x, int y) throws IOException {
+ // get preview after 5 sec.
+ IContainer ic = IContainer.make();
+ InputOutputStreamHandler iosh = new InputOutputStreamHandler(videoPart.getInputStream());
+ if (ic.open(iosh, IContainer.Type.READ, null) < 0) return null;
+
+ IMediaReader mediaReader = ToolFactory.makeReader(ic);
+
+ // stipulate that we want BufferedImages created in BGR 24bit color space
+ mediaReader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
+
+ ImageSnapListener isl = new ImageSnapListener(seconds);
+ mediaReader.addListener(isl);
+
+ // read out the contents of the media file and
+ // dispatch events to the attached listener
+ while (!isl.hasFired && mediaReader.readPacket() == null) ;
+
+ mediaReader.close();
+ ic.close();
+
+ // add snapshot
+ BufferedImage image1 = isl.image;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ImageIO.write(image1, "jpeg", bos);
+ XSLFPictureData snap = pptx.addPicture(bos.toByteArray(), PictureType.JPEG);
+ XSLFPictureShape pic1 = slide1.createPicture(snap);
+ pic1.setAnchor(new Rectangle(x, y, image1.getWidth(), image1.getHeight()));
+ return pic1;
+ }
+
+ static void addVideo(XMLSlideShow pptx, XSLFSlide slide1, PackagePart videoPart, XSLFPictureShape pic1, double seconds) throws IOException {
+
+ // add video shape
+ PackagePartName partName = videoPart.getPartName();
+ PackageRelationship prsEmbed1 = slide1.getPackagePart().addRelationship(partName, TargetMode.INTERNAL, "http://schemas.microsoft.com/office/2007/relationships/media");
+ PackageRelationship prsExec1 = slide1.getPackagePart().addRelationship(partName, TargetMode.INTERNAL, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video");
+ CTPicture xpic1 = (CTPicture)pic1.getXmlObject();
+ CTHyperlink link1 = xpic1.getNvPicPr().getCNvPr().addNewHlinkClick();
+ link1.setId("");
+ link1.setAction("ppaction://media");
+
+ // add video relation
+ CTApplicationNonVisualDrawingProps nvPr = xpic1.getNvPicPr().getNvPr();
+ nvPr.addNewVideoFile().setLink(prsExec1.getId());
+ CTExtension ext = nvPr.addNewExtLst().addNewExt();
+ // see http://msdn.microsoft.com/en-us/library/dd950140(v=office.12).aspx
+ ext.setUri("{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
+ String p14Ns = "http://schemas.microsoft.com/office/powerpoint/2010/main";
+ XmlCursor cur = ext.newCursor();
+ cur.toEndToken();
+ cur.beginElement(new QName(p14Ns, "media", "p14"));
+ cur.insertNamespace("p14", p14Ns);
+ cur.insertAttributeWithValue(new QName(STRelationshipId.type.getName().getNamespaceURI(), "embed"), prsEmbed1.getId());
+ cur.beginElement(new QName(p14Ns, "trim", "p14"));
+ cur.insertAttributeWithValue("st", df_time.format(seconds*1000.0));
+ cur.dispose();
+
+ }
+
+ static void addTimingInfo(XSLFSlide slide1, XSLFPictureShape pic1) {
+ // add slide timing information, so video can be controlled
+ CTSlide xslide = slide1.getXmlObject();
+ CTTimeNodeList ctnl;
+ if (!xslide.isSetTiming()) {
+ CTTLCommonTimeNodeData ctn = xslide.addNewTiming().addNewTnLst().addNewPar().addNewCTn();
+ ctn.setDur(STTLTimeIndefinite.INDEFINITE);
+ ctn.setRestart(STTLTimeNodeRestartType.NEVER);
+ ctn.setNodeType(STTLTimeNodeType.TM_ROOT);
+ ctnl = ctn.addNewChildTnLst();
+ } else {
+ ctnl = xslide.getTiming().getTnLst().getParArray(0).getCTn().getChildTnLst();
+ }
+
+ CTTLCommonMediaNodeData cmedia = ctnl.addNewVideo().addNewCMediaNode();
+ cmedia.setVol(80000);
+ CTTLCommonTimeNodeData ctn = cmedia.addNewCTn();
+ ctn.setFill(STTLTimeNodeFillType.HOLD);
+ ctn.setDisplay(false);
+ ctn.addNewStCondLst().addNewCond().setDelay(STTLTimeIndefinite.INDEFINITE);
+ cmedia.addNewTgtEl().addNewSpTgt().setSpid(""+pic1.getShapeId());
+ }
+
+
+ static class ImageSnapListener extends MediaListenerAdapter {
+ final double SECONDS_BETWEEN_FRAMES;
+ final long MICRO_SECONDS_BETWEEN_FRAMES;
+ boolean hasFired = false;
+ BufferedImage image = null;
+
+ // The video stream index, used to ensure we display frames from one and
+ // only one video stream from the media container.
+ int mVideoStreamIndex = -1;
+
+ // Time of last frame write
+ long mLastPtsWrite = Global.NO_PTS;
+
+ public ImageSnapListener(double seconds) {
+ SECONDS_BETWEEN_FRAMES = seconds;
+ MICRO_SECONDS_BETWEEN_FRAMES =
+ (long)(Global.DEFAULT_PTS_PER_SECOND * SECONDS_BETWEEN_FRAMES);
+ }
+
+
+ @Override
+ public void onVideoPicture(IVideoPictureEvent event) {
+
+ if (event.getStreamIndex() != mVideoStreamIndex) {
+ // if the selected video stream id is not yet set, go ahead an
+ // select this lucky video stream
+ if (mVideoStreamIndex != -1) return;
+ mVideoStreamIndex = event.getStreamIndex();
+ }
+
+ long evtTS = event.getTimeStamp();
+
+ // if uninitialized, back date mLastPtsWrite to get the very first frame
+ if (mLastPtsWrite == Global.NO_PTS)
+ mLastPtsWrite = Math.max(0, evtTS - MICRO_SECONDS_BETWEEN_FRAMES);
+
+ // if it's time to write the next frame
+ if (evtTS - mLastPtsWrite >= MICRO_SECONDS_BETWEEN_FRAMES) {
+ if (!hasFired) {
+ image = event.getImage();
+ hasFired = true;
+ }
+ // update last write time
+ mLastPtsWrite += MICRO_SECONDS_BETWEEN_FRAMES;
+ }
+ }
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/DataExtraction.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/DataExtraction.java
new file mode 100644
index 000000000..335840685
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/DataExtraction.java
@@ -0,0 +1,95 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Dimension;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+
+/**
+ * Demonstrates how you can extract data from a .pptx file
+ */
+public final class DataExtraction {
+
+ public static void main(String args[]) throws IOException, OpenXML4JException {
+
+ PrintStream out = System.out;
+
+ if (args.length == 0) {
+ out.println("Input file is required");
+ return;
+ }
+
+ FileInputStream is = new FileInputStream(args[0]);
+ XMLSlideShow ppt = new XMLSlideShow(is);
+ is.close();
+
+ // Get the document's embedded files.
+ for (PackagePart p : ppt.getAllEmbedds()) {
+ String type = p.getContentType();
+ // typically file name
+ String name = p.getPartName().getName();
+ out.println("Embedded file ("+type+"): "+name);
+
+ InputStream pIs = p.getInputStream();
+ // make sense of the part data
+ pIs.close();
+
+ }
+
+ // Get the document's embedded files.
+ for (XSLFPictureData data : ppt.getPictureData()) {
+ String type = data.getContentType();
+ String name = data.getFileName();
+ out.println("Picture ("+type+"): "+name);
+
+ InputStream pIs = data.getInputStream();
+ // make sense of the image data
+ pIs.close();
+ }
+
+ // size of the canvas in points
+ Dimension pageSize = ppt.getPageSize();
+ out.println("Pagesize: "+pageSize);
+
+ for(XSLFSlide slide : ppt.getSlides()) {
+ for(XSLFShape shape : slide){
+ if(shape instanceof XSLFTextShape) {
+ XSLFTextShape txShape = (XSLFTextShape)shape;
+ out.println(txShape.getText());
+ } else if (shape instanceof XSLFPictureShape){
+ XSLFPictureShape pShape = (XSLFPictureShape)shape;
+ XSLFPictureData pData = pShape.getPictureData();
+ out.println(pData.getFileName());
+ } else {
+ out.println("Process me: " + shape.getClass());
+ }
+ }
+ }
+
+ ppt.close();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/MergePresentations.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/MergePresentations.java
new file mode 100644
index 000000000..38e928501
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/MergePresentations.java
@@ -0,0 +1,54 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+/**
+ * Merge multiple pptx presentations together
+ *
+ * @author Yegor Kozlov
+ */
+public final class MergePresentations {
+
+ public static void main(String args[]) throws Exception {
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ for(String arg : args){
+ FileInputStream is = new FileInputStream(arg);
+ XMLSlideShow src = new XMLSlideShow(is);
+ is.close();
+
+ for(XSLFSlide srcSlide : src.getSlides()){
+ ppt.createSlide().importContent(srcSlide);
+ }
+
+ src.close();
+ }
+
+ FileOutputStream out = new FileOutputStream("merged.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/PPTX2SVG.txt b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/PPTX2SVG.txt
new file mode 100644
index 000000000..dbe089ac3
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/PPTX2SVG.txt
@@ -0,0 +1,176 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import org.apache.batik.dom.svg.SVGDOMImplementation;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.transcoder.wmf.tosvg.WMFPainter;
+import org.apache.batik.transcoder.wmf.tosvg.WMFRecordStore;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+import javax.imageio.ImageIO;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.DataInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+/**
+ * Convert each slide of a .pptx presentation into SVG
+ *
+ * @author Yegor Kozlov
+ */
+public class PPTX2SVG {
+
+ static void usage() {
+ System.out.println("Usage: PPTX2SVG ");
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ usage();
+ return;
+ }
+
+ String file = args[0];
+
+ System.out.println("Processing " + file);
+
+ // read the .pptx file
+ XMLSlideShow ppt = new XMLSlideShow(OPCPackage.open(file));
+
+ Dimension pgsize = ppt.getPageSize();
+
+ // convert each slide into a .svg file
+ XSLFSlide[] slide = ppt.getSlides();
+ for (int i = 0; i < slide.length; i++) {
+ // Create initial SVG DOM
+ DOMImplementation domImpl = SVGDOMImplementation.getDOMImplementation();
+ Document doc = domImpl.createDocument("http://www.w3.org/2000/svg", "svg", null);
+ //Use Batik SVG Graphics2D driver
+ SVGGraphics2D graphics = new SVGGraphics2D(doc);
+ graphics.setRenderingHint(XSLFRenderingHint.IMAGE_RENDERER, new WMFImageRender());
+ graphics.setSVGCanvasSize(pgsize);
+
+ String title = slide[i].getTitle();
+ System.out.println("Rendering slide " + (i + 1) + (title == null ? "" : ": " + title));
+
+ // draw stuff. All the heavy-lifting happens here
+ slide[i].draw(graphics);
+
+ // save the result.
+ int sep = file.lastIndexOf(".");
+ String fname = file.substring(0, sep == -1 ? file.length() : sep) + "-" + (i + 1) + ".svg";
+ OutputStreamWriter out =
+ new OutputStreamWriter(new FileOutputStream(fname), "UTF-8");
+ DOMSource domSource = new DOMSource(graphics.getRoot());
+ StreamResult streamResult = new StreamResult(out);
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer serializer = tf.newTransformer();
+ serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ serializer.setOutputProperty(OutputKeys.INDENT, "yes");
+ serializer.transform(domSource, streamResult);
+ out.flush();
+ out.close();
+ }
+ System.out.println("Done");
+ }
+
+ /**
+ * Image renderer with support for .wmf images
+ */
+ static class WMFImageRender extends XSLFImageRendener {
+
+ /**
+ * Use Apache Batik to render WMF,
+ * delegate all other types of images to the javax.imageio framework
+ */
+ @Override
+ public boolean drawImage(Graphics2D graphics, XSLFPictureData data,
+ Rectangle2D anchor) {
+ try {
+ // see what type of image we are
+ PackagePart part = data.getPackagePart();
+ String contentType = part.getContentType();
+ if (contentType.equals("image/x-wmf")) {
+ WMFRecordStore currentStore = new WMFRecordStore();
+ currentStore.read(new DataInputStream(part.getInputStream()));
+ int wmfwidth = currentStore.getWidthPixels();
+ float conv = (float) anchor.getWidth() / wmfwidth;
+
+ // Build a painter for the RecordStore
+ WMFPainter painter = new WMFPainter(currentStore,
+ (int) anchor.getX(), (int) anchor.getY(), conv);
+ painter.paint(graphics);
+ } else {
+ BufferedImage img = ImageIO.read(data.getPackagePart().getInputStream());
+ graphics.drawImage(img,
+ (int) anchor.getX(), (int) anchor.getY(),
+ (int) anchor.getWidth(), (int) anchor.getHeight(), null);
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Convert data form the supplied package part into a BufferedImage.
+ * This method is used to create texture paint.
+ */
+ @Override
+ public BufferedImage readImage(PackagePart packagePart) throws IOException {
+ String contentType = packagePart.getContentType();
+ if (contentType.equals("image/x-wmf")) {
+ try {
+ WMFRecordStore currentStore = new WMFRecordStore();
+ currentStore.read(new DataInputStream(packagePart.getInputStream()));
+ int wmfwidth = currentStore.getWidthPixels();
+ int wmfheight = currentStore.getHeightPixels();
+
+ BufferedImage img = new BufferedImage(wmfwidth, wmfheight, BufferedImage.TYPE_INT_RGB);
+ Graphics2D graphics = img.createGraphics();
+
+ // Build a painter for the RecordStore
+ WMFPainter painter = new WMFPainter(currentStore, 0, 0, 1.0f);
+ painter.paint(graphics);
+
+ return img;
+ } catch (IOException e) {
+ return null;
+ }
+ } else {
+ return ImageIO.read(packagePart.getInputStream());
+ }
+ }
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/PieChartDemo.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/PieChartDemo.java
new file mode 100644
index 000000000..df98b9296
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/PieChartDemo.java
@@ -0,0 +1,165 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import org.apache.poi.POIXMLDocumentPart;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTAxDataSource;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumData;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumDataSource;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTNumVal;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieChart;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieSer;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTPlotArea;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTSerTx;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrData;
+import org.openxmlformats.schemas.drawingml.x2006.chart.CTStrVal;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.OutputStream;
+
+/**
+ * Build a pie chart from a template pptx
+ *
+ * @author Yegor Kozlov
+ */
+public class PieChartDemo {
+ private static void usage(){
+ System.out.println("Usage: PieChartDemo ");
+ System.out.println(" pie-chart-template.pptx template with a pie chart");
+ System.out.println(" pie-chart-data.txt the model to set. First line is chart title, " +
+ "then go pairs {axis-label value}");
+ }
+
+ public static void main(String[] args) throws Exception {
+ if(args.length < 2) {
+ usage();
+ return;
+ }
+
+ BufferedReader modelReader = new BufferedReader(new FileReader(args[1]));
+ XMLSlideShow pptx = null;
+ try {
+ String chartTitle = modelReader.readLine(); // first line is chart title
+
+ pptx = new XMLSlideShow(new FileInputStream(args[0]));
+ XSLFSlide slide = pptx.getSlides().get(0);
+
+ // find chart in the slide
+ XSLFChart chart = null;
+ for(POIXMLDocumentPart part : slide.getRelations()){
+ if(part instanceof XSLFChart){
+ chart = (XSLFChart) part;
+ break;
+ }
+ }
+
+ if(chart == null) throw new IllegalStateException("chart not found in the template");
+
+ // embedded Excel workbook that holds the chart data
+ POIXMLDocumentPart xlsPart = chart.getRelations().get(0);
+ XSSFWorkbook wb = new XSSFWorkbook();
+ try {
+ XSSFSheet sheet = wb.createSheet();
+
+ CTChart ctChart = chart.getCTChart();
+ CTPlotArea plotArea = ctChart.getPlotArea();
+
+ CTPieChart pieChart = plotArea.getPieChartArray(0);
+ //Pie Chart Series
+ CTPieSer ser = pieChart.getSerArray(0);
+
+ // Series Text
+ CTSerTx tx = ser.getTx();
+ tx.getStrRef().getStrCache().getPtArray(0).setV(chartTitle);
+ sheet.createRow(0).createCell(1).setCellValue(chartTitle);
+ String titleRef = new CellReference(sheet.getSheetName(), 0, 1, true, true).formatAsString();
+ tx.getStrRef().setF(titleRef);
+
+ // Category Axis Data
+ CTAxDataSource cat = ser.getCat();
+ CTStrData strData = cat.getStrRef().getStrCache();
+
+ // Values
+ CTNumDataSource val = ser.getVal();
+ CTNumData numData = val.getNumRef().getNumCache();
+
+ strData.setPtArray(null); // unset old axis text
+ numData.setPtArray(null); // unset old values
+
+ // set model
+ int idx = 0;
+ int rownum = 1;
+ String ln;
+ while((ln = modelReader.readLine()) != null){
+ String[] vals = ln.split("\\s+");
+ CTNumVal numVal = numData.addNewPt();
+ numVal.setIdx(idx);
+ numVal.setV(vals[1]);
+
+ CTStrVal sVal = strData.addNewPt();
+ sVal.setIdx(idx);
+ sVal.setV(vals[0]);
+
+ idx++;
+ XSSFRow row = sheet.createRow(rownum++);
+ row.createCell(0).setCellValue(vals[0]);
+ row.createCell(1).setCellValue(Double.valueOf(vals[1]));
+ }
+ numData.getPtCount().setVal(idx);
+ strData.getPtCount().setVal(idx);
+
+ String numDataRange = new CellRangeAddress(1, rownum-1, 1, 1).formatAsString(sheet.getSheetName(), true);
+ val.getNumRef().setF(numDataRange);
+ String axisDataRange = new CellRangeAddress(1, rownum-1, 0, 0).formatAsString(sheet.getSheetName(), true);
+ cat.getStrRef().setF(axisDataRange);
+
+ // updated the embedded workbook with the data
+ OutputStream xlsOut = xlsPart.getPackagePart().getOutputStream();
+ try {
+ wb.write(xlsOut);
+ } finally {
+ xlsOut.close();
+ }
+
+ // save the result
+ OutputStream out = new FileOutputStream("pie-chart-demo-output.pptx");
+ try {
+ pptx.write(out);
+ } finally {
+ out.close();
+ }
+ } finally {
+ wb.close();
+ }
+ } finally {
+ if (pptx != null) pptx.close();
+ modelReader.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial1.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial1.java
new file mode 100644
index 000000000..60f5d7121
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial1.java
@@ -0,0 +1,74 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Demonstrates how to create slides with predefined layout
+ * and fill the placeholder shapes
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial1 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ // XSLFSlide#createSlide() with no arguments creates a blank slide
+ /*XSLFSlide blankSlide =*/ ppt.createSlide();
+
+
+ XSLFSlideMaster master = ppt.getSlideMasters().get(0);
+
+ XSLFSlideLayout layout1 = master.getLayout(SlideLayout.TITLE);
+ XSLFSlide slide1 = ppt.createSlide(layout1) ;
+ XSLFTextShape[] ph1 = slide1.getPlaceholders();
+ XSLFTextShape titlePlaceholder1 = ph1[0];
+ titlePlaceholder1.setText("This is a title");
+ XSLFTextShape subtitlePlaceholder1 = ph1[1];
+ subtitlePlaceholder1.setText("this is a subtitle");
+
+ XSLFSlideLayout layout2 = master.getLayout(SlideLayout.TITLE_AND_CONTENT);
+ XSLFSlide slide2 = ppt.createSlide(layout2) ;
+ XSLFTextShape[] ph2 = slide2.getPlaceholders();
+ XSLFTextShape titlePlaceholder2 = ph2[0];
+ titlePlaceholder2.setText("This is a title");
+ XSLFTextShape bodyPlaceholder = ph2[1];
+ // we are going to add text by paragraphs. Clear the default placehoder text before that
+ bodyPlaceholder.clearText();
+ XSLFTextParagraph p1 = bodyPlaceholder.addNewTextParagraph();
+ p1.setIndentLevel(0);
+ p1.addNewTextRun().setText("Level1 text");
+ XSLFTextParagraph p2 = bodyPlaceholder.addNewTextParagraph();
+ p2.setIndentLevel(1);
+ p2.addNewTextRun().setText("Level2 text");
+ XSLFTextParagraph p3 = bodyPlaceholder.addNewTextParagraph();
+ p3.setIndentLevel(2);
+ p3.addNewTextRun().setText("Level3 text");
+
+ FileOutputStream out = new FileOutputStream("slides.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java
new file mode 100644
index 000000000..af05962ee
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial2.java
@@ -0,0 +1,85 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Basic paragraph and text formatting
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial2 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ XSLFSlide slide1 = ppt.createSlide();
+ XSLFTextBox shape1 = slide1.createTextBox();
+ // initial height of the text box is 100 pt but
+ Rectangle anchor = new Rectangle(10, 100, 300, 100);
+ shape1.setAnchor(anchor);
+
+ XSLFTextParagraph p1 = shape1.addNewTextParagraph();
+ XSLFTextRun r1 = p1.addNewTextRun();
+ r1.setText("Paragraph Formatting");
+ r1.setFontSize(24d);
+ r1.setFontColor(new Color(85, 142, 213));
+
+ XSLFTextParagraph p2 = shape1.addNewTextParagraph();
+ // If spaceBefore >= 0, then space is a percentage of normal line height.
+ // If spaceBefore < 0, the absolute value of linespacing is the spacing in points
+ p2.setSpaceBefore(-20d); // 20 pt from the previous paragraph
+ p2.setSpaceAfter(300d); // 3 lines after the paragraph
+ XSLFTextRun r2 = p2.addNewTextRun();
+ r2.setText("Paragraph properties apply to all text residing within the corresponding paragraph.");
+ r2.setFontSize(16d);
+
+ XSLFTextParagraph p3 = shape1.addNewTextParagraph();
+
+ XSLFTextRun r3 = p3.addNewTextRun();
+ r3.setText("Run Formatting");
+ r3.setFontSize(24d);
+ r3.setFontColor(new Color(85, 142, 213));
+
+ XSLFTextParagraph p4 = shape1.addNewTextParagraph();
+ p4.setSpaceBefore(-20d); // 20 pt from the previous paragraph
+ p4.setSpaceAfter(300d); // 3 lines after the paragraph
+ XSLFTextRun r4 = p4.addNewTextRun();
+ r4.setFontSize(16d);
+ r4.setText(
+ "Run level formatting is the most granular property level and allows " +
+ "for the specifying of all low level text properties. The text run is " +
+ "what all paragraphs are derived from and thus specifying various " +
+ "properties per run will allow for a diversely formatted text paragraph.");
+
+ // resize the shape to fit text
+ shape1.resizeToFitText();
+
+ FileOutputStream out = new FileOutputStream("text.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial3.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial3.java
new file mode 100644
index 000000000..4433605f3
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial3.java
@@ -0,0 +1,51 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.sl.usermodel.Placeholder;
+
+/**
+ * How to set slide title
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial3 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ XSLFSlide slide = ppt.createSlide();
+
+ XSLFTextShape titleShape = slide.createTextBox();
+ titleShape.setPlaceholder(Placeholder.TITLE);
+ titleShape.setText("This is a slide title");
+ titleShape.setAnchor(new Rectangle(50, 50, 400, 100));
+
+ FileOutputStream out = new FileOutputStream("title.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial4.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial4.java
new file mode 100644
index 000000000..94082a2ef
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial4.java
@@ -0,0 +1,94 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.sl.usermodel.TableCell.BorderEdge;
+import org.apache.poi.sl.usermodel.TextParagraph.TextAlign;
+
+/**
+ * PPTX Tables
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial4 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ // XSLFSlide#createSlide() with no arguments creates a blank slide
+ XSLFSlide slide = ppt.createSlide();
+
+ XSLFTable tbl = slide.createTable();
+ tbl.setAnchor(new Rectangle(50, 50, 450, 300));
+
+ int numColumns = 3;
+ int numRows = 5;
+ XSLFTableRow headerRow = tbl.addRow();
+ headerRow.setHeight(50);
+ // header
+ for(int i = 0; i < numColumns; i++) {
+ XSLFTableCell th = headerRow.addCell();
+ XSLFTextParagraph p = th.addNewTextParagraph();
+ p.setTextAlign(TextAlign.CENTER);
+ XSLFTextRun r = p.addNewTextRun();
+ r.setText("Header " + (i+1));
+ r.setBold(true);
+ r.setFontColor(Color.white);
+ th.setFillColor(new Color(79, 129, 189));
+ th.setBorderWidth(BorderEdge.bottom, 2.0);
+ th.setBorderColor(BorderEdge.bottom, Color.white);
+
+ tbl.setColumnWidth(i, 150); // all columns are equally sized
+ }
+
+ // rows
+
+ for(int rownum = 0; rownum < numRows; rownum ++){
+ XSLFTableRow tr = tbl.addRow();
+ tr.setHeight(50);
+ // header
+ for(int i = 0; i < numColumns; i++) {
+ XSLFTableCell cell = tr.addCell();
+ XSLFTextParagraph p = cell.addNewTextParagraph();
+ XSLFTextRun r = p.addNewTextRun();
+
+ r.setText("Cell " + (i+1));
+ if(rownum % 2 == 0)
+ cell.setFillColor(new Color(208, 216, 232));
+ else
+ cell.setFillColor(new Color(233, 247, 244));
+
+ }
+
+ }
+
+
+ FileOutputStream out = new FileOutputStream("table.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial5.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial5.java
new file mode 100644
index 000000000..06542afd9
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial5.java
@@ -0,0 +1,51 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
+
+/**
+ * Images
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial5 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ XSLFSlide slide = ppt.createSlide();
+
+ File img = new File(System.getProperty("POI.testdata.path", "test-data"), "slideshow/clock.jpg");
+ XSLFPictureData pictureData = ppt.addPicture(img, PictureType.PNG);
+
+ /*XSLFPictureShape shape =*/ slide.createPicture(pictureData);
+
+ FileOutputStream out = new FileOutputStream("images.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java
new file mode 100644
index 000000000..73a59d0b8
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial6.java
@@ -0,0 +1,61 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Hyperlinks
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial6 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ XSLFSlide slide1 = ppt.createSlide();
+ XSLFSlide slide2 = ppt.createSlide();
+
+ XSLFTextBox shape1 = slide1.createTextBox();
+ shape1.setAnchor(new Rectangle(50, 50, 200, 50));
+ XSLFTextRun r1 = shape1.addNewTextParagraph().addNewTextRun();
+ XSLFHyperlink link1 = r1.createHyperlink();
+ r1.setText("http://poi.apache.org"); // visible text
+ link1.setAddress("http://poi.apache.org"); // link address
+
+ XSLFTextBox shape2 = slide1.createTextBox();
+ shape2.setAnchor(new Rectangle(300, 50, 200, 50));
+ XSLFTextRun r2 = shape2.addNewTextParagraph().addNewTextRun();
+ XSLFHyperlink link2 = r2.createHyperlink();
+ r2.setText("Go to the second slide"); // visible text
+ link2.linkToSlide(slide2); // link address
+
+
+
+ FileOutputStream out = new FileOutputStream("hyperlinks.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java
new file mode 100644
index 000000000..26f822d1c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/Tutorial7.java
@@ -0,0 +1,90 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.sl.usermodel.AutoNumberingScheme;
+
+/**
+ * Bullets and numbering
+ *
+ * @author Yegor Kozlov
+ */
+public class Tutorial7 {
+
+ public static void main(String[] args) throws IOException{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+ XSLFSlide slide = ppt.createSlide();
+ XSLFTextBox shape = slide.createTextBox();
+ shape.setAnchor(new Rectangle(50, 50, 400, 200));
+
+ XSLFTextParagraph p1 = shape.addNewTextParagraph();
+ p1.setIndentLevel(0);
+ p1.setBullet(true);
+ XSLFTextRun r1 = p1.addNewTextRun();
+ r1.setText("Bullet1");
+
+ XSLFTextParagraph p2 = shape.addNewTextParagraph();
+ // indentation before text
+ p2.setLeftMargin(60d);
+ // the bullet is set 40 pt before the text
+ p2.setIndent(-40d);
+ p2.setBullet(true);
+ // customize bullets
+ p2.setBulletFontColor(Color.red);
+ p2.setBulletFont("Wingdings");
+ p2.setBulletCharacter("\u0075");
+ p2.setIndentLevel(1);
+ XSLFTextRun r2 = p2.addNewTextRun();
+ r2.setText("Bullet2");
+
+ // the next three paragraphs form an auto-numbered list
+ XSLFTextParagraph p3 = shape.addNewTextParagraph();
+ p3.setBulletAutoNumber(AutoNumberingScheme.alphaLcParenRight, 1);
+ p3.setIndentLevel(2);
+ XSLFTextRun r3 = p3.addNewTextRun();
+ r3.setText("Numbered List Item - 1");
+
+ XSLFTextParagraph p4 = shape.addNewTextParagraph();
+ p4.setBulletAutoNumber(AutoNumberingScheme.alphaLcParenRight, 2);
+ p4.setIndentLevel(2);
+ XSLFTextRun r4 = p4.addNewTextRun();
+ r4.setText("Numbered List Item - 2");
+
+ XSLFTextParagraph p5 = shape.addNewTextParagraph();
+ p5.setBulletAutoNumber(AutoNumberingScheme.alphaLcParenRight, 3);
+ p5.setIndentLevel(2);
+ XSLFTextRun r5 = p5.addNewTextRun();
+ r5.setText("Numbered List Item - 3");
+
+ shape.resizeToFitText();
+
+ FileOutputStream out = new FileOutputStream("list.pptx");
+ ppt.write(out);
+ out.close();
+
+ ppt.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/pie-chart-data.txt b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/pie-chart-data.txt
new file mode 100644
index 000000000..40b6959c2
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/pie-chart-data.txt
@@ -0,0 +1,4 @@
+My Chart
+First 1.0
+Second 3.0
+Third 4.0
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/pie-chart-template.pptx b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/pie-chart-template.pptx
new file mode 100644
index 000000000..33d28e154
Binary files /dev/null and b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/pie-chart-template.pptx differ
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/tutorial/Step1.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/tutorial/Step1.java
new file mode 100644
index 000000000..41c6cc2da
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/tutorial/Step1.java
@@ -0,0 +1,68 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel.tutorial;
+
+import org.apache.poi.xslf.usermodel.XMLSlideShow;
+import org.apache.poi.xslf.usermodel.XSLFShape;
+import org.apache.poi.xslf.usermodel.XSLFSlide;
+import org.apache.poi.xslf.usermodel.XSLFTextParagraph;
+import org.apache.poi.xslf.usermodel.XSLFTextRun;
+import org.apache.poi.xslf.usermodel.XSLFTextShape;
+
+import java.io.FileInputStream;
+
+/**
+ * Reading a .pptx presentation and printing basic shape properties
+ *
+ * @author Yegor Kozlov
+ */
+public class Step1 {
+
+ public static void main(String[] args) throws Exception {
+ if(args.length == 0) {
+ System.out.println("Input file is required");
+ return;
+ }
+
+ XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(args[0]));
+
+ for(XSLFSlide slide : ppt.getSlides()){
+ System.out.println("Title: " + slide.getTitle());
+
+ for(XSLFShape shape : slide.getShapes()){
+ if(shape instanceof XSLFTextShape) {
+ XSLFTextShape tsh = (XSLFTextShape)shape;
+ for(XSLFTextParagraph p : tsh){
+ System.out.println("Paragraph level: " + p.getIndentLevel());
+ for(XSLFTextRun r : p){
+ System.out.println(r.getRawText());
+ System.out.println(" bold: " + r.isBold());
+ System.out.println(" italic: " + r.isItalic());
+ System.out.println(" underline: " + r.isUnderlined());
+ System.out.println(" font.family: " + r.getFontFamily());
+ System.out.println(" font.size: " + r.getFontSize());
+ System.out.println(" font.color: " + r.getFontColor());
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xslf/usermodel/tutorial/Step2.java b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/tutorial/Step2.java
new file mode 100644
index 000000000..cd01d60c8
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xslf/usermodel/tutorial/Step2.java
@@ -0,0 +1,80 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xslf.usermodel.tutorial;
+
+import org.apache.poi.xslf.usermodel.SlideLayout;
+import org.apache.poi.xslf.usermodel.XMLSlideShow;
+import org.apache.poi.xslf.usermodel.XSLFSlide;
+import org.apache.poi.xslf.usermodel.XSLFSlideLayout;
+import org.apache.poi.xslf.usermodel.XSLFSlideMaster;
+import org.apache.poi.xslf.usermodel.XSLFTextShape;
+
+import java.io.FileOutputStream;
+
+/**
+ * Create slides from pre-defined slide layouts
+ *
+ * @author Yegor Kozlov
+ */
+public class Step2 {
+ public static void main(String[] args) throws Exception{
+ XMLSlideShow ppt = new XMLSlideShow();
+
+
+ // first see what slide layouts are available by default
+ System.out.println("Available slide layouts:");
+ for(XSLFSlideMaster master : ppt.getSlideMasters()){
+ for(XSLFSlideLayout layout : master.getSlideLayouts()){
+ System.out.println(layout.getType());
+ }
+ }
+
+ // blank slide
+ /*XSLFSlide blankSlide =*/ ppt.createSlide();
+
+ XSLFSlideMaster defaultMaster = ppt.getSlideMasters().get(0);
+
+ // title slide
+ XSLFSlideLayout titleLayout = defaultMaster.getLayout(SlideLayout.TITLE);
+ XSLFSlide slide1 = ppt.createSlide(titleLayout);
+ XSLFTextShape title1 = slide1.getPlaceholder(0);
+ title1.setText("First Title");
+
+ // title and content
+ XSLFSlideLayout titleBodyLayout = defaultMaster.getLayout(SlideLayout.TITLE_AND_CONTENT);
+ XSLFSlide slide2 = ppt.createSlide(titleBodyLayout);
+
+ XSLFTextShape title2 = slide2.getPlaceholder(0);
+ title2.setText("Second Title");
+
+ XSLFTextShape body2 = slide2.getPlaceholder(1);
+ body2.clearText(); // unset any existing text
+ body2.addNewTextParagraph().addNewTextRun().setText("First paragraph");
+ body2.addNewTextParagraph().addNewTextRun().setText("Second paragraph");
+ body2.addNewTextParagraph().addNewTextRun().setText("Third paragraph");
+
+
+
+ FileOutputStream out = new FileOutputStream("step2.pptx");
+ ppt.write(out);
+ out.close();
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java b/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java
new file mode 100644
index 000000000..11d14ae37
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java
@@ -0,0 +1,244 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.eventusermodel;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.util.SAXHelper;
+import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
+import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
+import org.apache.poi.xssf.model.StylesTable;
+import org.apache.poi.xssf.usermodel.XSSFComment;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+/**
+ * A rudimentary XLSX -> CSV processor modeled on the
+ * POI sample program XLS2CSVmra from the package
+ * org.apache.poi.hssf.eventusermodel.examples.
+ * As with the HSSF version, this tries to spot missing
+ * rows and cells, and output empty entries for them.
+ *
+ * Data sheets are read using a SAX parser to keep the
+ * memory footprint relatively small, so this should be
+ * able to read enormous workbooks. The styles table and
+ * the shared-string table must be kept in memory. The
+ * standard POI styles table class is used, but a custom
+ * (read-only) class is used for the shared string table
+ * because the standard POI SharedStringsTable grows very
+ * quickly with the number of unique strings.
+ *
+ * For a more advanced implementation of SAX event parsing
+ * of XLSX files, see {@link XSSFEventBasedExcelExtractor}
+ * and {@link XSSFSheetXMLHandler}. Note that for many cases,
+ * it may be possible to simply use those with a custom
+ * {@link SheetContentsHandler} and no SAX code needed of
+ * your own!
+ */
+public class XLSX2CSV {
+ /**
+ * Uses the XSSF Event SAX helpers to do most of the work
+ * of parsing the Sheet XML, and outputs the contents
+ * as a (basic) CSV.
+ */
+ private class SheetToCSV implements SheetContentsHandler {
+ private boolean firstCellOfRow = false;
+ private int currentRow = -1;
+ private int currentCol = -1;
+
+ private void outputMissingRows(int number) {
+ for (int i=0; i CSV converter
+ *
+ * @param pkg The XLSX package to process
+ * @param output The PrintStream to output the CSV to
+ * @param minColumns The minimum number of columns to output, or -1 for no minimum
+ */
+ public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns) {
+ this.xlsxPackage = pkg;
+ this.output = output;
+ this.minColumns = minColumns;
+ }
+
+ /**
+ * Parses and shows the content of one sheet
+ * using the specified styles and shared-strings tables.
+ *
+ * @param styles
+ * @param strings
+ * @param sheetInputStream
+ */
+ public void processSheet(
+ StylesTable styles,
+ ReadOnlySharedStringsTable strings,
+ SheetContentsHandler sheetHandler,
+ InputStream sheetInputStream)
+ throws IOException, ParserConfigurationException, SAXException {
+ DataFormatter formatter = new DataFormatter();
+ InputSource sheetSource = new InputSource(sheetInputStream);
+ try {
+ XMLReader sheetParser = SAXHelper.newXMLReader();
+ ContentHandler handler = new XSSFSheetXMLHandler(
+ styles, null, strings, sheetHandler, formatter, false);
+ sheetParser.setContentHandler(handler);
+ sheetParser.parse(sheetSource);
+ } catch(ParserConfigurationException e) {
+ throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
+ }
+ }
+
+ /**
+ * Initiates the processing of the XLS workbook file to CSV.
+ *
+ * @throws IOException
+ * @throws OpenXML4JException
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ */
+ public void process()
+ throws IOException, OpenXML4JException, ParserConfigurationException, SAXException {
+ ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);
+ XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
+ StylesTable styles = xssfReader.getStylesTable();
+ XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
+ int index = 0;
+ while (iter.hasNext()) {
+ InputStream stream = iter.next();
+ String sheetName = iter.getSheetName();
+ this.output.println();
+ this.output.println(sheetName + " [index=" + index + "]:");
+ processSheet(styles, strings, new SheetToCSV(), stream);
+ stream.close();
+ ++index;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length < 1) {
+ System.err.println("Use:");
+ System.err.println(" XLSX2CSV [min columns]");
+ return;
+ }
+
+ File xlsxFile = new File(args[0]);
+ if (!xlsxFile.exists()) {
+ System.err.println("Not found or not a file: " + xlsxFile.getPath());
+ return;
+ }
+
+ int minColumns = -1;
+ if (args.length >= 2)
+ minColumns = Integer.parseInt(args[1]);
+
+ // The package open is instantaneous, as it should be.
+ OPCPackage p = OPCPackage.open(xlsxFile.getPath(), PackageAccess.READ);
+ XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, minColumns);
+ xlsx2csv.process();
+ p.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/examples/FromHowTo.java b/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/examples/FromHowTo.java
new file mode 100644
index 000000000..1997e6776
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/examples/FromHowTo.java
@@ -0,0 +1,165 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.eventusermodel.examples;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.xssf.eventusermodel.XLSX2CSV;
+import org.apache.poi.xssf.eventusermodel.XSSFReader;
+import org.apache.poi.xssf.model.SharedStringsTable;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * XSSF and SAX (Event API) basic example.
+ * See {@link XLSX2CSV} for a fuller example of doing
+ * XSLX processing with the XSSF Event code.
+ */
+public class FromHowTo {
+ public void processFirstSheet(String filename) throws Exception {
+ OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
+ try {
+ XSSFReader r = new XSSFReader(pkg);
+ SharedStringsTable sst = r.getSharedStringsTable();
+
+ XMLReader parser = fetchSheetParser(sst);
+
+ // process the first sheet
+ InputStream sheet2 = r.getSheetsData().next();
+ InputSource sheetSource = new InputSource(sheet2);
+ parser.parse(sheetSource);
+ sheet2.close();
+ } finally {
+ pkg.close();
+ }
+ }
+
+ public void processAllSheets(String filename) throws Exception {
+ OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ);
+ try {
+ XSSFReader r = new XSSFReader(pkg);
+ SharedStringsTable sst = r.getSharedStringsTable();
+
+ XMLReader parser = fetchSheetParser(sst);
+
+ Iterator sheets = r.getSheetsData();
+ while (sheets.hasNext()) {
+ System.out.println("Processing new sheet:\n");
+ InputStream sheet = sheets.next();
+ InputSource sheetSource = new InputSource(sheet);
+ parser.parse(sheetSource);
+ sheet.close();
+ System.out.println("");
+ }
+ } finally {
+ pkg.close();
+ }
+ }
+
+ public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
+ XMLReader parser = XMLReaderFactory.createXMLReader();
+ ContentHandler handler = new SheetHandler(sst);
+ parser.setContentHandler(handler);
+ return parser;
+ }
+
+ /**
+ * See org.xml.sax.helpers.DefaultHandler javadocs
+ */
+ private static class SheetHandler extends DefaultHandler {
+ private final SharedStringsTable sst;
+ private String lastContents;
+ private boolean nextIsString;
+ private boolean inlineStr;
+ private final LruCache lruCache = new LruCache(50);
+
+ private static class LruCache extends LinkedHashMap {
+ private final int maxEntries;
+
+ public LruCache(final int maxEntries) {
+ super(maxEntries + 1, 1.0f, true);
+ this.maxEntries = maxEntries;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(final Map.Entry eldest) {
+ return super.size() > maxEntries;
+ }
+ }
+
+ private SheetHandler(SharedStringsTable sst) {
+ this.sst = sst;
+ }
+
+ public void startElement(String uri, String localName, String name,
+ Attributes attributes) throws SAXException {
+ // c => cell
+ if(name.equals("c")) {
+ // Print the cell reference
+ System.out.print(attributes.getValue("r") + " - ");
+ // Figure out if the value is an index in the SST
+ String cellType = attributes.getValue("t");
+ nextIsString = cellType != null && cellType.equals("s");
+ inlineStr = cellType != null && cellType.equals("inlineStr");
+ }
+ // Clear contents cache
+ lastContents = "";
+ }
+
+ public void endElement(String uri, String localName, String name)
+ throws SAXException {
+ // Process the last contents as required.
+ // Do now, as characters() may be called more than once
+ if(nextIsString) {
+ Integer idx = Integer.valueOf(lastContents);
+ lastContents = lruCache.get(idx);
+ if (lastContents == null && !lruCache.containsKey(idx)) {
+ lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
+ lruCache.put(idx, lastContents);
+ }
+ nextIsString = false;
+ }
+
+ // v => contents of a cell
+ // Output after we've seen the string contents
+ if(name.equals("v") || (inlineStr && name.equals("c"))) {
+ System.out.println(lastContents);
+ }
+ }
+
+ public void characters(char[] ch, int start, int length) throws SAXException { // NOSONAR
+ lastContents += new String(ch, start, length);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ FromHowTo howto = new FromHowTo();
+ howto.processFirstSheet(args[0]);
+ howto.processAllSheets(args[0]);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/streaming/examples/HybridStreaming.java b/trunk/src/examples/src/org/apache/poi/xssf/streaming/examples/HybridStreaming.java
new file mode 100644
index 000000000..1e5515859
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/streaming/examples/HybridStreaming.java
@@ -0,0 +1,76 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.streaming.examples;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
+import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
+import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;
+import org.apache.poi.xssf.usermodel.XSSFComment;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheet;
+import org.xml.sax.SAXException;
+
+/**
+ * This demonstrates how a hybrid approach to workbook read can be taken, using
+ * a mix of traditional XSSF and streaming one particular worksheet (perhaps one
+ * which is too big for the ordinary DOM parse).
+ */
+public class HybridStreaming {
+
+ private static final String SHEET_TO_STREAM = "large sheet";
+
+ public static void main(String[] args) throws IOException, SAXException {
+ InputStream sourceBytes = new FileInputStream("workbook.xlsx");
+ XSSFWorkbook workbook = new XSSFWorkbook(sourceBytes) {
+ /** Avoid DOM parse of large sheet */
+ public void parseSheet(java.util.Map shIdMap, CTSheet ctSheet) {
+ if (!SHEET_TO_STREAM.equals(ctSheet.getName())) {
+ super.parseSheet(shIdMap, ctSheet);
+ }
+ }
+ };
+
+ // Having avoided a DOM-based parse of the sheet, we can stream it instead.
+ ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(workbook.getPackage());
+ new XSSFSheetXMLHandler(workbook.getStylesSource(), strings, createSheetContentsHandler(), false);
+ workbook.close();
+ sourceBytes.close();
+ }
+
+ private static SheetContentsHandler createSheetContentsHandler() {
+ return new SheetContentsHandler() {
+
+ public void startRow(int rowNum) {
+ }
+
+ public void headerFooter(String text, boolean isHeader, String tagName) {
+ }
+
+ public void endRow(int rowNum) {
+ }
+
+ public void cell(String cellReference, String formattedValue, XSSFComment comment) {
+ }
+ };
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/streaming/examples/Outlining.java b/trunk/src/examples/src/org/apache/poi/xssf/streaming/examples/Outlining.java
new file mode 100644
index 000000000..4c0b35c85
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/streaming/examples/Outlining.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.streaming.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.xssf.streaming.SXSSFSheet;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+
+public class Outlining {
+
+ public static void main(String[] args) throws IOException {
+ Outlining o = new Outlining();
+ o.collapseRow();
+ }
+
+ private void collapseRow() throws IOException {
+ SXSSFWorkbook wb2 = new SXSSFWorkbook(100);
+ SXSSFSheet sheet2 = wb2.createSheet("new sheet");
+
+ int rowCount = 20;
+ for (int i = 0; i < rowCount; i++) {
+ sheet2.createRow(i);
+ }
+
+ sheet2.groupRow(4, 9);
+ sheet2.groupRow(11, 19);
+
+ sheet2.setRowGroupCollapsed(4, true);
+
+ FileOutputStream fileOut = new FileOutputStream("outlining_collapsed.xlsx");
+ try {
+ wb2.write(fileOut);
+ } finally {
+ fileOut.close();
+ wb2.dispose();
+ wb2.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/AligningCells.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/AligningCells.java
new file mode 100644
index 000000000..b210c1301
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/AligningCells.java
@@ -0,0 +1,139 @@
+/* ====================================================================
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTRowImpl;
+
+/**
+ * Shows how various alignment options work.
+ *
+ * Modified by Cristian Petrula, Romania on May 26, 2010
+ * New method was added centerAcrossSelection to center a column content over
+ * one selection using {@link HorizontalAlignment#CENTER_SELECTION}
+ * To create this method example was change for XSSF only and the previous
+ * AligningCells.java example has been moved into the SS examples folder.
+ */
+public class AligningCells {
+
+ public static void main(String[] args) throws IOException {
+ XSSFWorkbook wb = new XSSFWorkbook();
+
+ XSSFSheet sheet = wb.createSheet();
+ XSSFRow row = sheet.createRow((short) 2);
+ row.setHeightInPoints(30);
+ for (int i = 0; i < 8; i++) {
+ //column width is set in units of 1/256th of a character width
+ sheet.setColumnWidth(i, 256 * 15);
+ }
+
+ createCell(wb, row, (short) 0, HorizontalAlignment.CENTER, VerticalAlignment.BOTTOM);
+ createCell(wb, row, (short) 1, HorizontalAlignment.CENTER_SELECTION, VerticalAlignment.BOTTOM);
+ createCell(wb, row, (short) 2, HorizontalAlignment.FILL, VerticalAlignment.CENTER);
+ createCell(wb, row, (short) 3, HorizontalAlignment.GENERAL, VerticalAlignment.CENTER);
+ createCell(wb, row, (short) 4, HorizontalAlignment.JUSTIFY, VerticalAlignment.JUSTIFY);
+ createCell(wb, row, (short) 5, HorizontalAlignment.LEFT, VerticalAlignment.TOP);
+ createCell(wb, row, (short) 6, HorizontalAlignment.RIGHT, VerticalAlignment.TOP);
+
+ //center text over B4, C4, D4
+ row = sheet.createRow((short) 3);
+ centerAcrossSelection(wb, row, (short) 1, (short) 3, VerticalAlignment.CENTER);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("xssf-align.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ wb.close();
+ }
+
+ /**
+ * Creates a cell and aligns it a certain way.
+ *
+ * @param wb the workbook
+ * @param row the row to create the cell in
+ * @param column the column number to create the cell in
+ * @param halign the horizontal alignment for the cell.
+ */
+ private static void createCell(XSSFWorkbook wb, XSSFRow row, short column,
+ HorizontalAlignment halign, VerticalAlignment valign) {
+ XSSFCell cell = row.createCell(column);
+ cell.setCellValue(new XSSFRichTextString("Align It"));
+ CellStyle cellStyle = wb.createCellStyle();
+ cellStyle.setAlignment(halign);
+ cellStyle.setVerticalAlignment(valign);
+ cell.setCellStyle(cellStyle);
+ }
+
+ /**
+ * Center a text over multiple columns using ALIGN_CENTER_SELECTION
+ *
+ * @param wb the workbook
+ * @param row the row to create the cell in
+ * @param start_column the column number to create the cell in and where the selection starts
+ * @param end_column the column number where the selection ends
+ * @param valign the horizontal alignment for the cell.
+ *
+ * @author Cristian Petrula, Romania
+ */
+ private static void centerAcrossSelection(XSSFWorkbook wb, XSSFRow row,
+ short start_column, short end_column, VerticalAlignment valign) {
+
+ // Create cell style with ALIGN_CENTER_SELECTION
+ XSSFCellStyle cellStyle = wb.createCellStyle();
+ cellStyle.setAlignment(HorizontalAlignment.CENTER_SELECTION);
+ cellStyle.setVerticalAlignment(valign);
+
+ // Create cells over the selected area
+ for (int i = start_column; i <= end_column; i++) {
+ XSSFCell cell = row.createCell(i);
+ cell.setCellStyle(cellStyle);
+ }
+
+ // Set value to the first cell
+ XSSFCell cell = row.getCell(start_column);
+ cell.setCellValue(new XSSFRichTextString("Align It"));
+
+ // Make the selection
+ CTRowImpl ctRow = (CTRowImpl) row.getCTRow();
+
+ // Add object with format start_coll:end_coll. For example 1:3 will span from
+ // cell 1 to cell 3, where the column index starts with 0
+ //
+ // You can add multiple spans for one row
+ Object span = start_column + ":" + end_column;
+
+ List spanList = new ArrayList();
+ spanList.add(span);
+
+ //add spns to the row
+ ctRow.setSpans(spanList);
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/BigGridDemo.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/BigGridDemo.java
new file mode 100644
index 000000000..66bb59762
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/BigGridDemo.java
@@ -0,0 +1,300 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.poi.openxml4j.opc.internal.ZipHelper;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFDataFormat;
+import org.apache.poi.xssf.usermodel.XSSFFont;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Demonstrates a workaround you can use to generate large workbooks and avoid OutOfMemory exception.
+ *
+ * Note - You probably don't want to use this approach any more! POI
+ * now includes the SXSSF which handles all of this for you, you should
+ * be using that instead! This code remains mostly for historical interest.
+ *
+ * If you really want to use this approach, which is also the one that SXSSF
+ * does for you, it works as follows:
+ *
+ * 1. create a template workbook, create sheets and global objects such as cell styles, number formats, etc.
+ * 2. create an application that streams data in a text file
+ * 3. Substitute the sheet in the template with the generated data
+ *
+ *
+ * Since 3.8 POI provides a low-memory footprint SXSSF API, which implements
+ * ths "BigGridDemo" strategy. SXSSF is an API-compatible streaming extension
+ * of XSSF to be used when very large spreadsheets have to be produced, and
+ * heap space is limited. SXSSF achieves its low memory footprint by limiting
+ * access to the rows that are within a sliding window, while XSSF gives access
+ * to all rows in the document. Older rows that are no longer in the window
+ * become inaccessible, as they are written to the disk.
+ *
+ * See
+ * http://poi.apache.org/spreadsheet/how-to.html#sxssf.
+
+ *
+ * @author Yegor Kozlov
+ */
+public class BigGridDemo {
+ private static final String XML_ENCODING = "UTF-8";
+
+ public static void main(String[] args) throws Exception {
+
+ // Step 1. Create a template file. Setup sheets and workbook-level objects such as
+ // cell styles, number formats, etc.
+
+ XSSFWorkbook wb = new XSSFWorkbook();
+ XSSFSheet sheet = wb.createSheet("Big Grid");
+
+ Map styles = createStyles(wb);
+ //name of the zip entry holding sheet data, e.g. /xl/worksheets/sheet1.xml
+ String sheetRef = sheet.getPackagePart().getPartName().getName();
+
+ //save the template
+ FileOutputStream os = new FileOutputStream("template.xlsx");
+ wb.write(os);
+ os.close();
+
+ //Step 2. Generate XML file.
+ File tmp = File.createTempFile("sheet", ".xml");
+ Writer fw = new OutputStreamWriter(new FileOutputStream(tmp), XML_ENCODING);
+ generate(fw, styles);
+ fw.close();
+
+ //Step 3. Substitute the template entry with the generated data
+ FileOutputStream out = new FileOutputStream("big-grid.xlsx");
+ substitute(new File("template.xlsx"), tmp, sheetRef.substring(1), out);
+ out.close();
+
+ wb.close();
+ }
+
+ /**
+ * Create a library of cell styles.
+ */
+ private static Map createStyles(XSSFWorkbook wb){
+ Map styles = new HashMap();
+ XSSFDataFormat fmt = wb.createDataFormat();
+
+ XSSFCellStyle style1 = wb.createCellStyle();
+ style1.setAlignment(HorizontalAlignment.RIGHT);
+ style1.setDataFormat(fmt.getFormat("0.0%"));
+ styles.put("percent", style1);
+
+ XSSFCellStyle style2 = wb.createCellStyle();
+ style2.setAlignment(HorizontalAlignment.CENTER);
+ style2.setDataFormat(fmt.getFormat("0.0X"));
+ styles.put("coeff", style2);
+
+ XSSFCellStyle style3 = wb.createCellStyle();
+ style3.setAlignment(HorizontalAlignment.RIGHT);
+ style3.setDataFormat(fmt.getFormat("$#,##0.00"));
+ styles.put("currency", style3);
+
+ XSSFCellStyle style4 = wb.createCellStyle();
+ style4.setAlignment(HorizontalAlignment.RIGHT);
+ style4.setDataFormat(fmt.getFormat("mmm dd"));
+ styles.put("date", style4);
+
+ XSSFCellStyle style5 = wb.createCellStyle();
+ XSSFFont headerFont = wb.createFont();
+ headerFont.setBold(true);
+ style5.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+ style5.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style5.setFont(headerFont);
+ styles.put("header", style5);
+
+ return styles;
+ }
+
+ private static void generate(Writer out, Map styles) throws Exception {
+
+ Random rnd = new Random();
+ Calendar calendar = Calendar.getInstance();
+
+ SpreadsheetWriter sw = new SpreadsheetWriter(out);
+ sw.beginSheet();
+
+ //insert header row
+ sw.insertRow(0);
+ int styleIndex = styles.get("header").getIndex();
+ sw.createCell(0, "Title", styleIndex);
+ sw.createCell(1, "% Change", styleIndex);
+ sw.createCell(2, "Ratio", styleIndex);
+ sw.createCell(3, "Expenses", styleIndex);
+ sw.createCell(4, "Date", styleIndex);
+
+ sw.endRow();
+
+ //write data rows
+ for (int rownum = 1; rownum < 100000; rownum++) {
+ sw.insertRow(rownum);
+
+ sw.createCell(0, "Hello, " + rownum + "!");
+ sw.createCell(1, (double)rnd.nextInt(100)/100, styles.get("percent").getIndex());
+ sw.createCell(2, (double)rnd.nextInt(10)/10, styles.get("coeff").getIndex());
+ sw.createCell(3, rnd.nextInt(10000), styles.get("currency").getIndex());
+ sw.createCell(4, calendar, styles.get("date").getIndex());
+
+ sw.endRow();
+
+ calendar.roll(Calendar.DAY_OF_YEAR, 1);
+ }
+ sw.endSheet();
+ }
+
+ /**
+ *
+ * @param zipfile the template file
+ * @param tmpfile the XML file with the sheet data
+ * @param entry the name of the sheet entry to substitute, e.g. xl/worksheets/sheet1.xml
+ * @param out the stream to write the result to
+ */
+ private static void substitute(File zipfile, File tmpfile, String entry, OutputStream out) throws IOException {
+ ZipFile zip = ZipHelper.openZipFile(zipfile);
+ try {
+ ZipOutputStream zos = new ZipOutputStream(out);
+
+ Enumeration extends ZipEntry> en = zip.entries();
+ while (en.hasMoreElements()) {
+ ZipEntry ze = en.nextElement();
+ if(!ze.getName().equals(entry)){
+ zos.putNextEntry(new ZipEntry(ze.getName()));
+ InputStream is = zip.getInputStream(ze);
+ copyStream(is, zos);
+ is.close();
+ }
+ }
+ zos.putNextEntry(new ZipEntry(entry));
+ InputStream is = new FileInputStream(tmpfile);
+ copyStream(is, zos);
+ is.close();
+
+ zos.close();
+ } finally {
+ zip.close();
+ }
+ }
+
+ private static void copyStream(InputStream in, OutputStream out) throws IOException {
+ byte[] chunk = new byte[1024];
+ int count;
+ while ((count = in.read(chunk)) >=0 ) {
+ out.write(chunk,0,count);
+ }
+ }
+
+ /**
+ * Writes spreadsheet data in a Writer.
+ * (YK: in future it may evolve in a full-featured API for streaming data in Excel)
+ */
+ public static class SpreadsheetWriter {
+ private final Writer _out;
+ private int _rownum;
+
+ public SpreadsheetWriter(Writer out){
+ _out = out;
+ }
+
+ public void beginSheet() throws IOException {
+ _out.write("" +
+ "" );
+ _out.write("\n");
+ }
+
+ public void endSheet() throws IOException {
+ _out.write("");
+ _out.write("");
+ }
+
+ /**
+ * Insert a new row
+ *
+ * @param rownum 0-based row number
+ */
+ public void insertRow(int rownum) throws IOException {
+ _out.write("\n");
+ this._rownum = rownum;
+ }
+
+ /**
+ * Insert row end marker
+ */
+ public void endRow() throws IOException {
+ _out.write("\n");
+ }
+
+ public void createCell(int columnIndex, String value, int styleIndex) throws IOException {
+ String ref = new CellReference(_rownum, columnIndex).formatAsString();
+ _out.write("");
+ _out.write(""+value+"");
+ _out.write("");
+ }
+
+ public void createCell(int columnIndex, String value) throws IOException {
+ createCell(columnIndex, value, -1);
+ }
+
+ public void createCell(int columnIndex, double value, int styleIndex) throws IOException {
+ String ref = new CellReference(_rownum, columnIndex).formatAsString();
+ _out.write("");
+ _out.write(""+value+"");
+ _out.write("");
+ }
+
+ public void createCell(int columnIndex, double value) throws IOException {
+ createCell(columnIndex, value, -1);
+ }
+
+ public void createCell(int columnIndex, Calendar value, int styleIndex) throws IOException {
+ createCell(columnIndex, DateUtil.getExcelDate(value, false), styleIndex);
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CalendarDemo.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CalendarDemo.java
new file mode 100644
index 000000000..48e3fd122
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CalendarDemo.java
@@ -0,0 +1,230 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.xssf.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.usermodel.*;
+
+import java.io.FileOutputStream;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * A monthly calendar created using Apache POI. Each month is on a separate sheet.
+ * This is a version of org.apache.poi.ss.examples.CalendarDemo that demonstrates
+ * some XSSF features not avaiable when using common HSSF-XSSF interfaces.
+ *
+ *
+ * Usage:
+ * CalendarDemo
+ *
+ *
+ * @author Yegor Kozlov
+ */
+public class CalendarDemo {
+
+ private static final String[] days = {
+ "Sunday", "Monday", "Tuesday",
+ "Wednesday", "Thursday", "Friday", "Saturday"};
+
+ private static final String[] months = {
+ "January", "February", "March","April", "May", "June","July", "August",
+ "September","October", "November", "December"};
+
+ public static void main(String[] args) throws Exception {
+
+ Calendar calendar = Calendar.getInstance();
+ if(args.length > 0) calendar.set(Calendar.YEAR, Integer.parseInt(args[0]));
+
+ int year = calendar.get(Calendar.YEAR);
+
+ XSSFWorkbook wb = new XSSFWorkbook();
+ Map styles = createStyles(wb);
+
+ for (int month = 0; month < 12; month++) {
+ calendar.set(Calendar.MONTH, month);
+ calendar.set(Calendar.DAY_OF_MONTH, 1);
+ //create a sheet for each month
+ XSSFSheet sheet = wb.createSheet(months[month]);
+
+ //turn off gridlines
+ sheet.setDisplayGridlines(false);
+ sheet.setPrintGridlines(false);
+ XSSFPrintSetup printSetup = sheet.getPrintSetup();
+ printSetup.setOrientation(PrintOrientation.LANDSCAPE);
+ sheet.setFitToPage(true);
+ sheet.setHorizontallyCenter(true);
+
+ //the header row: centered text in 48pt font
+ XSSFRow headerRow = sheet.createRow(0);
+ headerRow.setHeightInPoints(80);
+ XSSFCell titleCell = headerRow.createCell(0);
+ titleCell.setCellValue(months[month] + " " + year);
+ titleCell.setCellStyle(styles.get("title"));
+ sheet.addMergedRegion(CellRangeAddress.valueOf("$A$1:$N$1"));
+
+ //header with month titles
+ XSSFRow monthRow = sheet.createRow(1);
+ for (int i = 0; i < days.length; i++) {
+ //for compatibility with HSSF we have to set column width in units of 1/256th of a character width
+ sheet.setColumnWidth(i*2, 5*256); //the column is 5 characters wide
+ sheet.setColumnWidth(i*2 + 1, 13*256); //the column is 13 characters wide
+ sheet.addMergedRegion(new CellRangeAddress(1, 1, i*2, i*2+1));
+ XSSFCell monthCell = monthRow.createCell(i*2);
+ monthCell.setCellValue(days[i]);
+ monthCell.setCellStyle(styles.get("month"));
+ }
+
+ int cnt = 1, day=1;
+ int rownum = 2;
+ for (int j = 0; j < 6; j++) {
+ XSSFRow row = sheet.createRow(rownum++);
+ row.setHeightInPoints(100);
+ for (int i = 0; i < days.length; i++) {
+ XSSFCell dayCell_1 = row.createCell(i*2);
+ XSSFCell dayCell_2 = row.createCell(i*2 + 1);
+
+ int day_of_week = calendar.get(Calendar.DAY_OF_WEEK);
+ if(cnt >= day_of_week && calendar.get(Calendar.MONTH) == month) {
+ dayCell_1.setCellValue(day);
+ calendar.set(Calendar.DAY_OF_MONTH, ++day);
+
+ if(i == 0 || i == days.length-1) {
+ dayCell_1.setCellStyle(styles.get("weekend_left"));
+ dayCell_2.setCellStyle(styles.get("weekend_right"));
+ } else {
+ dayCell_1.setCellStyle(styles.get("workday_left"));
+ dayCell_2.setCellStyle(styles.get("workday_right"));
+ }
+ } else {
+ dayCell_1.setCellStyle(styles.get("grey_left"));
+ dayCell_2.setCellStyle(styles.get("grey_right"));
+ }
+ cnt++;
+ }
+ if(calendar.get(Calendar.MONTH) > month) break;
+ }
+ }
+
+ // Write the output to a file
+ FileOutputStream out = new FileOutputStream("calendar-"+year+".xlsx");
+ wb.write(out);
+ out.close();
+
+ wb.close();
+ }
+
+ /**
+ * cell styles used for formatting calendar sheets
+ */
+ private static Map createStyles(XSSFWorkbook wb){
+ Map styles = new HashMap();
+
+ XSSFCellStyle style;
+ XSSFFont titleFont = wb.createFont();
+ titleFont.setFontHeightInPoints((short)48);
+ titleFont.setColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFont(titleFont);
+ styles.put("title", style);
+
+ XSSFFont monthFont = wb.createFont();
+ monthFont.setFontHeightInPoints((short)12);
+ monthFont.setColor(new XSSFColor(new java.awt.Color(255, 255, 255)));
+ monthFont.setBold(true);
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setFont(monthFont);
+ styles.put("month", style);
+
+ XSSFFont dayFont = wb.createFont();
+ dayFont.setFontHeightInPoints((short)14);
+ dayFont.setBold(true);
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.LEFT);
+ style.setVerticalAlignment(VerticalAlignment.TOP);
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(228, 232, 243)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setBorderLeft(BorderStyle.THIN);
+ style.setLeftBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setFont(dayFont);
+ styles.put("weekend_left", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.TOP);
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(228, 232, 243)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setBorderRight(BorderStyle.THIN);
+ style.setRightBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ styles.put("weekend_right", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.LEFT);
+ style.setVerticalAlignment(VerticalAlignment.TOP);
+ style.setBorderLeft(BorderStyle.THIN);
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(255, 255, 255)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setLeftBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setFont(dayFont);
+ styles.put("workday_left", style);
+
+ style = wb.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.TOP);
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(255, 255, 255)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setBorderRight(BorderStyle.THIN);
+ style.setRightBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ styles.put("workday_right", style);
+
+ style = wb.createCellStyle();
+ style.setBorderLeft(BorderStyle.THIN);
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(234, 234, 234)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ styles.put("grey_left", style);
+
+ style = wb.createCellStyle();
+ style.setFillForegroundColor(new XSSFColor(new java.awt.Color(234, 234, 234)));
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ style.setBorderRight(BorderStyle.THIN);
+ style.setRightBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ style.setBorderBottom(BorderStyle.THIN);
+ style.setBottomBorderColor(new XSSFColor(new java.awt.Color(39, 51, 89)));
+ styles.put("grey_right", style);
+
+ return styles;
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CellComments.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CellComments.java
new file mode 100644
index 000000000..a94e58a34
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CellComments.java
@@ -0,0 +1,81 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.io.IOException;
+import java.io.FileOutputStream;
+
+/**
+ * Demonstrates how to work with excel cell comments.
+ *
+ *
+ * Excel comment is a kind of a text shape,
+ * so inserting a comment is very similar to placing a text box in a worksheet
+ *
+ *
+ * @author Yegor Kozlov
+ */
+public class CellComments {
+ public static void main(String[] args) throws IOException {
+ Workbook wb = new XSSFWorkbook();
+
+ CreationHelper factory = wb.getCreationHelper();
+
+ Sheet sheet = wb.createSheet();
+
+ Cell cell1 = sheet.createRow(3).createCell(5);
+ cell1.setCellValue("F4");
+
+ Drawing drawing = sheet.createDrawingPatriarch();
+
+ ClientAnchor anchor = factory.createClientAnchor();
+
+ Comment comment1 = drawing.createCellComment(anchor);
+ RichTextString str1 = factory.createRichTextString("Hello, World!");
+ comment1.setString(str1);
+ comment1.setAuthor("Apache POI");
+ cell1.setCellComment(comment1);
+
+ Cell cell2 = sheet.createRow(2).createCell(2);
+ cell2.setCellValue("C3");
+
+ Comment comment2 = drawing.createCellComment(anchor);
+ RichTextString str2 = factory.createRichTextString("XSSF can set cell comments");
+ //apply custom font to the text in the comment
+ Font font = wb.createFont();
+ font.setFontName("Arial");
+ font.setFontHeightInPoints((short)14);
+ font.setBoldweight(Font.BOLDWEIGHT_BOLD);
+ font.setColor(IndexedColors.RED.getIndex());
+ str2.applyFont(font);
+
+ comment2.setString(str2);
+ comment2.setAuthor("Apache POI");
+ comment2.setAddress(new CellAddress("C3"));
+
+ String fname = "comments.xlsx";
+ FileOutputStream out = new FileOutputStream(fname);
+ wb.write(out);
+ out.close();
+
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateCell.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateCell.java
new file mode 100644
index 000000000..3e6736014
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateCell.java
@@ -0,0 +1,80 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.util.Date;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Illustrates how to create cell and set values of different types.
+ */
+public class CreateCell {
+
+
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ CreationHelper creationHelper = wb.getCreationHelper();
+ Sheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ Row row = sheet.createRow((short)0);
+ // Create a cell and put a value in it.
+ Cell cell = row.createCell((short)0);
+ cell.setCellValue(1);
+
+ //numeric value
+ row.createCell(1).setCellValue(1.2);
+
+ //plain string value
+ row.createCell(2).setCellValue("This is a string cell");
+
+ //rich text string
+ RichTextString str = creationHelper.createRichTextString("Apache");
+ Font font = wb.createFont();
+ font.setItalic(true);
+ font.setUnderline(Font.U_SINGLE);
+ str.applyFont(font);
+ row.createCell(3).setCellValue(str);
+
+ //boolean value
+ row.createCell(4).setCellValue(true);
+
+ //formula
+ row.createCell(5).setCellFormula("SUM(A1:B1)");
+
+ //date
+ CellStyle style = wb.createCellStyle();
+ style.setDataFormat(creationHelper.createDataFormat().getFormat("m/d/yy h:mm"));
+ cell = row.createCell(6);
+ cell.setCellValue(new Date());
+ cell.setCellStyle(style);
+
+ //hyperlink
+ row.createCell(7).setCellFormula("SUM(A1:B1)");
+ cell.setCellFormula("HYPERLINK(\"http://google.com\",\"Google\")");
+
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("ooxml-cell.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreatePivotTable.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreatePivotTable.java
new file mode 100644
index 000000000..518ec37f4
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreatePivotTable.java
@@ -0,0 +1,101 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.DataConsolidateFunction;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.util.AreaReference;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.usermodel.XSSFPivotTable;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+public class CreatePivotTable {
+
+ public static void main(String[] args) throws FileNotFoundException, IOException, InvalidFormatException {
+ XSSFWorkbook wb = new XSSFWorkbook();
+ XSSFSheet sheet = wb.createSheet();
+
+ //Create some data to build the pivot table on
+ setCellData(sheet);
+
+ XSSFPivotTable pivotTable = sheet.createPivotTable(new AreaReference("A1:D4"), new CellReference("H5"));
+ //Configure the pivot table
+ //Use first column as row label
+ pivotTable.addRowLabel(0);
+ //Sum up the second column
+ pivotTable.addColumnLabel(DataConsolidateFunction.SUM, 1);
+ //Set the third column as filter
+ pivotTable.addColumnLabel(DataConsolidateFunction.AVERAGE, 2);
+ //Add filter on forth column
+ pivotTable.addReportFilter(3);
+
+ FileOutputStream fileOut = new FileOutputStream("ooxml-pivottable.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ wb.close();
+ }
+
+ public static void setCellData(XSSFSheet sheet){
+ Row row1 = sheet.createRow(0);
+ // Create a cell and put a value in it.
+ Cell cell11 = row1.createCell(0);
+ cell11.setCellValue("Names");
+ Cell cell12 = row1.createCell(1);
+ cell12.setCellValue("#");
+ Cell cell13 = row1.createCell(2);
+ cell13.setCellValue("%");
+ Cell cell14 = row1.createCell(3);
+ cell14.setCellValue("Human");
+
+ Row row2 = sheet.createRow(1);
+ Cell cell21 = row2.createCell(0);
+ cell21.setCellValue("Jane");
+ Cell cell22 = row2.createCell(1);
+ cell22.setCellValue(10);
+ Cell cell23 = row2.createCell(2);
+ cell23.setCellValue(100);
+ Cell cell24 = row2.createCell(3);
+ cell24.setCellValue("Yes");
+
+ Row row3 = sheet.createRow(2);
+ Cell cell31 = row3.createCell(0);
+ cell31.setCellValue("Tarzan");
+ Cell cell32 = row3.createCell(1);
+ cell32.setCellValue(5);
+ Cell cell33 = row3.createCell(2);
+ cell33.setCellValue(90);
+ Cell cell34 = row3.createCell(3);
+ cell34.setCellValue("Yes");
+
+ Row row4 = sheet.createRow(3);
+ Cell cell41 = row4.createCell(0);
+ cell41.setCellValue("Terk");
+ Cell cell42 = row4.createCell(1);
+ cell42.setCellValue(10);
+ Cell cell43 = row4.createCell(2);
+ cell43.setCellValue(90);
+ Cell cell44 = row4.createCell(3);
+ cell44.setCellValue("No");
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java
new file mode 100644
index 000000000..c26ff067c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateTable.java
@@ -0,0 +1,92 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.AreaReference;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFTable;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableStyleInfo;
+
+/**
+ * Demonstrates how to create a simple table using Apache POI.
+ */
+public class CreateTable {
+
+ public static void main(String[] args) throws FileNotFoundException,
+ IOException {
+
+ Workbook wb = new XSSFWorkbook();
+ XSSFSheet sheet = (XSSFSheet) wb.createSheet();
+
+ //Create
+ XSSFTable table = sheet.createTable();
+ table.setDisplayName("Test");
+ CTTable cttable = table.getCTTable();
+
+ //Style configurations
+ CTTableStyleInfo style = cttable.addNewTableStyleInfo();
+ style.setName("TableStyleMedium2");
+ style.setShowColumnStripes(false);
+ style.setShowRowStripes(true);
+
+ //Set which area the table should be placed in
+ AreaReference reference = new AreaReference(new CellReference(0, 0),
+ new CellReference(2,2));
+ cttable.setRef(reference.formatAsString());
+ cttable.setId(1);
+ cttable.setName("Test");
+ cttable.setTotalsRowCount(1);
+
+ CTTableColumns columns = cttable.addNewTableColumns();
+ columns.setCount(3);
+ CTTableColumn column;
+ XSSFRow row;
+ XSSFCell cell;
+ for(int i=0; i<3; i++) {
+ //Create column
+ column = columns.addNewTableColumn();
+ column.setName("Column");
+ column.setId(i+1);
+ //Create row
+ row = sheet.createRow(i);
+ for(int j=0; j<3; j++) {
+ //Create cell
+ cell = row.createCell(j);
+ if(i == 0) {
+ cell.setCellValue("Column"+j);
+ } else {
+ cell.setCellValue("0");
+ }
+ }
+ }
+
+ FileOutputStream fileOut = new FileOutputStream("ooxml-table.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateUserDefinedDataFormats.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateUserDefinedDataFormats.java
new file mode 100644
index 000000000..4ebaa46fc
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CreateUserDefinedDataFormats.java
@@ -0,0 +1,68 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.DataFormat;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * How to set user-defined date formats
+ */
+public class CreateUserDefinedDataFormats {
+
+
+ public static void main(String[]args) throws IOException {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("format sheet");
+ CellStyle style;
+ DataFormat format = wb.createDataFormat();
+ Row row;
+ Cell cell;
+ short rowNum = 0;
+ short colNum = 0;
+
+ row = sheet.createRow(rowNum);
+ cell = row.createCell(colNum);
+ cell.setCellValue(11111.25);
+ style = wb.createCellStyle();
+ style.setDataFormat(format.getFormat("0.0"));
+ cell.setCellStyle(style);
+
+ row = sheet.createRow(++rowNum);
+ cell = row.createCell(colNum);
+ cell.setCellValue(11111.25);
+ style = wb.createCellStyle();
+ style.setDataFormat(format.getFormat("#,##0.0000"));
+ cell.setCellStyle(style);
+
+ FileOutputStream fileOut = new FileOutputStream("ooxml_dataFormat.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ wb.close();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CustomXMLMapping.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CustomXMLMapping.java
new file mode 100644
index 000000000..1add0d2fb
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/CustomXMLMapping.java
@@ -0,0 +1,45 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.xssf.extractor.XSSFExportToXml;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFMap;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Print all custom XML mappings registered in the given workbook
+ */
+public class CustomXMLMapping {
+
+ public static void main(String[] args) throws Exception {
+ OPCPackage pkg = OPCPackage.open(args[0]);
+ XSSFWorkbook wb = new XSSFWorkbook(pkg);
+
+ for (XSSFMap map : wb.getCustomXMLMappings()) {
+ XSSFExportToXml exporter = new XSSFExportToXml(map);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ exporter.exportToXML(os, true);
+ String xml = os.toString("UTF-8");
+ System.out.println(xml);
+ }
+ pkg.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/EmbeddedObjects.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/EmbeddedObjects.java
new file mode 100644
index 000000000..3f044c839
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/EmbeddedObjects.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.xslf.usermodel.XSLFSlideShow;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl;
+import org.apache.poi.hwpf.HWPFDocument;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+import java.io.InputStream;
+
+/**
+ * Demonstrates how you can extract embedded data from a .xlsx file
+ */
+public class EmbeddedObjects {
+ public static void main(String[] args) throws Exception {
+ OPCPackage pkg = OPCPackage.open(args[0]);
+ XSSFWorkbook workbook = new XSSFWorkbook(pkg);
+ for (PackagePart pPart : workbook.getAllEmbedds()) {
+ String contentType = pPart.getContentType();
+ // Excel Workbook - either binary or OpenXML
+ if (contentType.equals("application/vnd.ms-excel")) {
+ HSSFWorkbook embeddedWorkbook = new HSSFWorkbook(pPart.getInputStream());
+ }
+ // Excel Workbook - OpenXML file format
+ else if (contentType.equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) {
+ XSSFWorkbook embeddedWorkbook = new XSSFWorkbook(pPart.getInputStream());
+ }
+ // Word Document - binary (OLE2CDF) file format
+ else if (contentType.equals("application/msword")) {
+ HWPFDocument document = new HWPFDocument(pPart.getInputStream());
+ }
+ // Word Document - OpenXML file format
+ else if (contentType.equals("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) {
+ XWPFDocument document = new XWPFDocument(pPart.getInputStream());
+ }
+ // PowerPoint Document - binary file format
+ else if (contentType.equals("application/vnd.ms-powerpoint")) {
+ HSLFSlideShowImpl slideShow = new HSLFSlideShowImpl(pPart.getInputStream());
+ }
+ // PowerPoint Document - OpenXML file format
+ else if (contentType.equals("application/vnd.openxmlformats-officedocument.presentationml.presentation")) {
+ OPCPackage docPackage = OPCPackage.open(pPart.getInputStream());
+ XSLFSlideShow slideShow = new XSLFSlideShow(docPackage);
+ }
+ // Any other type of embedded object.
+ else {
+ System.out.println("Unknown Embedded Document: " + contentType);
+ InputStream inputStream = pPart.getInputStream();
+ }
+ }
+ pkg.close();
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/FillsAndColors.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/FillsAndColors.java
new file mode 100644
index 000000000..019e14d1c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/FillsAndColors.java
@@ -0,0 +1,59 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Fills and Colors
+ */
+public class FillsAndColors {
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("new sheet");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ Row row = sheet.createRow((short) 1);
+
+ // Aqua background
+ CellStyle style = wb.createCellStyle();
+ style.setFillBackgroundColor(IndexedColors.AQUA.getIndex());
+ style.setFillPattern(CellStyle.BIG_SPOTS);
+ Cell cell = row.createCell((short) 1);
+ cell.setCellValue(new XSSFRichTextString("X"));
+ cell.setCellStyle(style);
+
+ // Orange "foreground", foreground being the fill foreground not the font color.
+ style = wb.createCellStyle();
+ style.setFillForegroundColor(IndexedColors.ORANGE.getIndex());
+ style.setFillPattern(CellStyle.SOLID_FOREGROUND);
+ cell = row.createCell((short) 2);
+ cell.setCellValue(new XSSFRichTextString("X"));
+ cell.setCellStyle(style);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("fill_colors.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/FitSheetToOnePage.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/FitSheetToOnePage.java
new file mode 100644
index 000000000..a781688c3
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/FitSheetToOnePage.java
@@ -0,0 +1,46 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.PrintSetup;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+public class FitSheetToOnePage {
+
+
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("format sheet");
+ PrintSetup ps = sheet.getPrintSetup();
+
+ sheet.setAutobreaks(true);
+
+ ps.setFitHeight((short) 1);
+ ps.setFitWidth((short) 1);
+
+ // Create various cells and rows for spreadsheet.
+
+ FileOutputStream fileOut = new FileOutputStream("fitSheetToOnePage.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/HeadersAndFooters.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/HeadersAndFooters.java
new file mode 100644
index 000000000..8b95fe63c
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/HeadersAndFooters.java
@@ -0,0 +1,84 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.Footer;
+import org.apache.poi.ss.usermodel.Header;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+public class HeadersAndFooters {
+
+
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("first-header - format sheet");
+ sheet.createRow(0).createCell(0).setCellValue(123);
+
+ //set page numbers in the footer
+ Footer footer = sheet.getFooter();
+ //&P == current page number
+ //&N == page numbers
+ footer.setRight("Page &P of &N");
+
+
+ Header firstHeader=((XSSFSheet)sheet).getFirstHeader();
+ //&F == workbook file name
+ firstHeader.setLeft("&F ......... first header");
+
+ for(int i=0;i<100;i=i+10){
+ sheet.createRow(i).createCell(0).setCellValue(123);
+ }
+
+
+ XSSFSheet sheet2 = (XSSFSheet)wb.createSheet("odd header-even footer");
+ Header oddHeader=sheet2.getOddHeader();
+ //&B == bold
+ //&E == double underline
+ //&D == date
+ oddHeader.setCenter("&B &E oddHeader &D ");
+
+ Footer evenFooter=sheet2.getEvenFooter();
+ evenFooter.setRight("even footer &P");
+ sheet2.createRow(10).createCell(0).setCellValue("Second sheet with an oddHeader and an evenFooter");
+
+ for(int i=0;i<200;i=i+10){
+ sheet2.createRow(i).createCell(0).setCellValue(123);
+ }
+
+ XSSFSheet sheet3 = (XSSFSheet)wb.createSheet("odd header- odd footer");
+ sheet3.createRow(10).createCell(0).setCellValue("Third sheet with oddHeader and oddFooter");
+ Header oddH=sheet3.getOddHeader();
+ //&C == centered
+ oddH.setCenter("centered oddHeader");
+ oddH.setLeft("left ");
+ oddH.setRight("right ");
+
+ Footer oddF=sheet3.getOddFooter();
+ oddF.setLeft("Page &P");
+ oddF.setRight("Pages &N ");
+
+ FileOutputStream fileOut = new FileOutputStream("headerFooter.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/HyperlinkExample.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/HyperlinkExample.java
new file mode 100644
index 000000000..a84d32a19
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/HyperlinkExample.java
@@ -0,0 +1,98 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.common.usermodel.HyperlinkType;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CreationHelper;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.Hyperlink;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Demonstrates how to create hyperlinks.
+ */
+public class HyperlinkExample {
+
+
+ public static void main(String[]args) throws IOException {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ CreationHelper createHelper = wb.getCreationHelper();
+
+ //cell style for hyperlinks
+ //by default hyperlinks are blue and underlined
+ CellStyle hlink_style = wb.createCellStyle();
+ Font hlink_font = wb.createFont();
+ hlink_font.setUnderline(Font.U_SINGLE);
+ hlink_font.setColor(IndexedColors.BLUE.getIndex());
+ hlink_style.setFont(hlink_font);
+
+ Cell cell;
+ Sheet sheet = wb.createSheet("Hyperlinks");
+ //URL
+ cell = sheet.createRow(0).createCell(0);
+ cell.setCellValue("URL Link");
+
+ Hyperlink link = createHelper.createHyperlink(HyperlinkType.URL);
+ link.setAddress("http://poi.apache.org/");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //link to a file in the current directory
+ cell = sheet.createRow(1).createCell(0);
+ cell.setCellValue("File Link");
+ link = createHelper.createHyperlink(HyperlinkType.FILE);
+ link.setAddress("link1.xls");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //e-mail link
+ cell = sheet.createRow(2).createCell(0);
+ cell.setCellValue("Email Link");
+ link = createHelper.createHyperlink(HyperlinkType.EMAIL);
+ //note, if subject contains white spaces, make sure they are url-encoded
+ link.setAddress("mailto:poi@apache.org?subject=Hyperlinks");
+ cell.setHyperlink(link);
+ cell.setCellStyle(hlink_style);
+
+ //link to a place in this workbook
+
+ //create a target sheet and cell
+ Sheet sheet2 = wb.createSheet("Target Sheet");
+ sheet2.createRow(0).createCell(0).setCellValue("Target Cell");
+
+ cell = sheet.createRow(3).createCell(0);
+ cell.setCellValue("Worksheet Link");
+ Hyperlink link2 = createHelper.createHyperlink(HyperlinkType.DOCUMENT);
+ link2.setAddress("'Target Sheet'!A1");
+ cell.setHyperlink(link2);
+ cell.setCellStyle(hlink_style);
+
+ FileOutputStream out = new FileOutputStream("hyperinks.xlsx");
+ wb.write(out);
+ out.close();
+
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/IterateCells.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/IterateCells.java
new file mode 100644
index 000000000..99d1cacf6
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/IterateCells.java
@@ -0,0 +1,46 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+
+import java.io.FileInputStream;
+
+/**
+ * Iterate over rows and cells
+ */
+public class IterateCells {
+
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook(new FileInputStream(args[0]));
+ for (int i = 0; i < wb.getNumberOfSheets(); i++) {
+ Sheet sheet = wb.getSheetAt(i);
+ System.out.println(wb.getSheetName(i));
+ for (Row row : sheet) {
+ System.out.println("rownum: " + row.getRowNum());
+ for (Cell cell : row) {
+ System.out.println(cell.toString());
+ }
+ }
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/LineChart.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/LineChart.java
new file mode 100644
index 000000000..d8564c523
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/LineChart.java
@@ -0,0 +1,91 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Chart;
+import org.apache.poi.ss.usermodel.ClientAnchor;
+import org.apache.poi.ss.usermodel.Drawing;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.charts.AxisCrosses;
+import org.apache.poi.ss.usermodel.charts.AxisPosition;
+import org.apache.poi.ss.usermodel.charts.ChartAxis;
+import org.apache.poi.ss.usermodel.charts.ChartDataSource;
+import org.apache.poi.ss.usermodel.charts.ChartLegend;
+import org.apache.poi.ss.usermodel.charts.DataSources;
+import org.apache.poi.ss.usermodel.charts.LegendPosition;
+import org.apache.poi.ss.usermodel.charts.LineChartData;
+import org.apache.poi.ss.usermodel.charts.ValueAxis;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Line chart example.
+ */
+public class LineChart {
+
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook();
+ Sheet sheet = wb.createSheet("linechart");
+ final int NUM_OF_ROWS = 3;
+ final int NUM_OF_COLUMNS = 10;
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ Row row;
+ Cell cell;
+ for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
+ row = sheet.createRow((short) rowIndex);
+ for (int colIndex = 0; colIndex < NUM_OF_COLUMNS; colIndex++) {
+ cell = row.createCell((short) colIndex);
+ cell.setCellValue(colIndex * (rowIndex + 1));
+ }
+ }
+
+ Drawing drawing = sheet.createDrawingPatriarch();
+ ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 10, 15);
+
+ Chart chart = drawing.createChart(anchor);
+ ChartLegend legend = chart.getOrCreateLegend();
+ legend.setPosition(LegendPosition.TOP_RIGHT);
+
+ LineChartData data = chart.getChartDataFactory().createLineChartData();
+
+ // Use a category axis for the bottom axis.
+ ChartAxis bottomAxis = chart.getChartAxisFactory().createCategoryAxis(AxisPosition.BOTTOM);
+ ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
+ leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
+
+ ChartDataSource xs = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 0, 0, NUM_OF_COLUMNS - 1));
+ ChartDataSource ys1 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(1, 1, 0, NUM_OF_COLUMNS - 1));
+ ChartDataSource ys2 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(2, 2, 0, NUM_OF_COLUMNS - 1));
+
+
+ data.addSeries(xs, ys1);
+ data.addSeries(xs, ys2);
+
+ chart.plot(data, bottomAxis, leftAxis);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("ooxml-line-chart.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/MergingCells.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/MergingCells.java
new file mode 100644
index 000000000..9d63268fc
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/MergingCells.java
@@ -0,0 +1,49 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+
+import java.io.FileOutputStream;
+
+/**
+ * An example of how to merge regions of cells.
+ */
+public class MergingCells {
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("new sheet");
+
+ Row row = sheet.createRow((short) 1);
+ Cell cell = row.createCell((short) 1);
+ cell.setCellValue(new XSSFRichTextString("This is a test of merging"));
+
+ sheet.addMergedRegion(new CellRangeAddress(1, 1, 1, 2));
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("merging_cells.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/NewLinesInCells.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/NewLinesInCells.java
new file mode 100644
index 000000000..eb179357d
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/NewLinesInCells.java
@@ -0,0 +1,57 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * How to use newlines in cells
+ */
+public class NewLinesInCells {
+
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet();
+
+ Row row = sheet.createRow(2);
+ Cell cell = row.createCell(2);
+ cell.setCellValue("Use \n with word wrap on to create a new line");
+
+ //to enable newlines you need set a cell styles with wrap=true
+ CellStyle cs = wb.createCellStyle();
+ cs.setWrapText(true);
+ cell.setCellStyle(cs);
+
+ //increase row height to accomodate two lines of text
+ row.setHeightInPoints((2*sheet.getDefaultRowHeightInPoints()));
+
+ //adjust column width to fit the content
+ sheet.autoSizeColumn(2);
+
+ FileOutputStream fileOut = new FileOutputStream("ooxml-newlines.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/Outlining.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/Outlining.java
new file mode 100644
index 000000000..869316f08
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/Outlining.java
@@ -0,0 +1,81 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+public class Outlining {
+
+ public static void main(String[] args) throws Exception {
+ Outlining o=new Outlining();
+ o.groupRowColumn();
+ o.collapseExpandRowColumn();
+ }
+
+
+ private void groupRowColumn() throws Exception {
+ Workbook wb = new XSSFWorkbook();
+ Sheet sheet1 = wb.createSheet("new sheet");
+
+ sheet1.groupRow( 5, 14 );
+ sheet1.groupRow( 7, 14 );
+ sheet1.groupRow( 16, 19 );
+
+ sheet1.groupColumn( (short)4, (short)7 );
+ sheet1.groupColumn( (short)9, (short)12 );
+ sheet1.groupColumn( (short)10, (short)11 );
+
+ OutputStream fileOut = new FileOutputStream("outlining.xlsx");
+ try {
+ wb.write(fileOut);
+ } finally {
+ fileOut.close();
+ }
+ }
+
+ private void collapseExpandRowColumn() throws Exception {
+ Workbook wb2 = new XSSFWorkbook();
+ Sheet sheet2 = wb2.createSheet("new sheet");
+ sheet2.groupRow( 5, 14 );
+ sheet2.groupRow( 7, 14 );
+ sheet2.groupRow( 16, 19 );
+
+ sheet2.groupColumn( (short)4, (short)7 );
+ sheet2.groupColumn( (short)9, (short)12 );
+ sheet2.groupColumn( (short)10, (short)11 );
+
+
+ sheet2.setRowGroupCollapsed( 7, true );
+ //sheet1.setRowGroupCollapsed(7,false);
+
+ sheet2.setColumnGroupCollapsed( (short)4, true );
+ sheet2.setColumnGroupCollapsed( (short)4, false );
+
+ OutputStream fileOut = new FileOutputStream("outlining_collapsed.xlsx");
+ try {
+ wb2.write(fileOut);
+ } finally {
+ fileOut.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/ScatterChart.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/ScatterChart.java
new file mode 100644
index 000000000..f0a94777f
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/ScatterChart.java
@@ -0,0 +1,80 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.*;
+import org.apache.poi.ss.usermodel.charts.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Illustrates how to create a simple scatter chart.
+ *
+ * @author Roman Kashitsyn
+ */
+public class ScatterChart {
+
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook();
+ Sheet sheet = wb.createSheet("Sheet 1");
+ final int NUM_OF_ROWS = 3;
+ final int NUM_OF_COLUMNS = 10;
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ Row row;
+ Cell cell;
+ for (int rowIndex = 0; rowIndex < NUM_OF_ROWS; rowIndex++) {
+ row = sheet.createRow((short) rowIndex);
+ for (int colIndex = 0; colIndex < NUM_OF_COLUMNS; colIndex++) {
+ cell = row.createCell((short) colIndex);
+ cell.setCellValue(colIndex * (rowIndex + 1));
+ }
+ }
+
+ Drawing drawing = sheet.createDrawingPatriarch();
+ ClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 5, 10, 15);
+
+ Chart chart = drawing.createChart(anchor);
+ ChartLegend legend = chart.getOrCreateLegend();
+ legend.setPosition(LegendPosition.TOP_RIGHT);
+
+ ScatterChartData data = chart.getChartDataFactory().createScatterChartData();
+
+ ValueAxis bottomAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.BOTTOM);
+ ValueAxis leftAxis = chart.getChartAxisFactory().createValueAxis(AxisPosition.LEFT);
+ leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
+
+ ChartDataSource xs = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(0, 0, 0, NUM_OF_COLUMNS - 1));
+ ChartDataSource ys1 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(1, 1, 0, NUM_OF_COLUMNS - 1));
+ ChartDataSource ys2 = DataSources.fromNumericCellRange(sheet, new CellRangeAddress(2, 2, 0, NUM_OF_COLUMNS - 1));
+
+
+ data.addSerie(xs, ys1);
+ data.addSerie(xs, ys2);
+
+ chart.plot(data, bottomAxis, leftAxis);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("ooxml-scatter-chart.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/SelectedSheet.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/SelectedSheet.java
new file mode 100644
index 000000000..43a6b4847
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/SelectedSheet.java
@@ -0,0 +1,46 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+public abstract class SelectedSheet {
+
+ public static void main(String[]args) throws IOException {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+
+ wb.createSheet("row sheet");
+ wb.createSheet("another sheet");
+ Sheet sheet3 = wb.createSheet(" sheet 3 ");
+ sheet3.setSelected(true);
+ wb.setActiveSheet(2);
+
+ // Create various cells and rows for spreadsheet.
+
+ FileOutputStream fileOut = new FileOutputStream("selectedSheet.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ wb.close();
+ }
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/ShiftRows.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/ShiftRows.java
new file mode 100644
index 000000000..ec4bb2139
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/ShiftRows.java
@@ -0,0 +1,61 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * How to shift rows up or down
+ */
+public class ShiftRows {
+
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("Sheet1");
+
+ Row row1 = sheet.createRow(1);
+ row1.createCell(0).setCellValue(1);
+
+ Row row2 = sheet.createRow(4);
+ row2.createCell(1).setCellValue(2);
+
+ Row row3 = sheet.createRow(5);
+ row3.createCell(2).setCellValue(3);
+
+ Row row4 = sheet.createRow(6);
+ row4.createCell(3).setCellValue(4);
+
+ Row row5 = sheet.createRow(9);
+ row5.createCell(4).setCellValue(5);
+
+ // Shift rows 6 - 11 on the spreadsheet to the top (rows 0 - 5)
+ sheet.shiftRows(5, 10, -4);
+
+ FileOutputStream fileOut = new FileOutputStream("shiftRows.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ }
+
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/SplitAndFreezePanes.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/SplitAndFreezePanes.java
new file mode 100644
index 000000000..937086b85
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/SplitAndFreezePanes.java
@@ -0,0 +1,50 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.io.FileOutputStream;
+
+/**
+ * How to set spklit and freeze panes
+ */
+public class SplitAndFreezePanes {
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook();
+ Sheet sheet1 = wb.createSheet("new sheet");
+ Sheet sheet2 = wb.createSheet("second sheet");
+ Sheet sheet3 = wb.createSheet("third sheet");
+ Sheet sheet4 = wb.createSheet("fourth sheet");
+
+ // Freeze just one row
+ sheet1.createFreezePane(0, 1, 0, 1);
+ // Freeze just one column
+ sheet2.createFreezePane(1, 0, 1, 0);
+ // Freeze the columns and rows (forget about scrolling position of the lower right quadrant).
+ sheet3.createFreezePane(2, 2);
+ // Create a split with the lower left side being the active quadrant
+ sheet4.createSplitPane(2000, 2000, 0, 0, Sheet.PANE_LOWER_LEFT);
+
+ FileOutputStream fileOut = new FileOutputStream("splitFreezePane.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkbookProperties.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkbookProperties.java
new file mode 100644
index 000000000..3a8fd56d0
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkbookProperties.java
@@ -0,0 +1,66 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.POIXMLProperties;
+
+/**
+ * How to set extended and custom properties
+ *
+ * @author Yegor Kozlov
+ */
+public class WorkbookProperties {
+
+ public static void main(String[]args) throws Exception {
+
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ workbook.createSheet("Workbook Properties");
+
+ POIXMLProperties props = workbook.getProperties();
+
+ /**
+ * Extended properties are a predefined set of metadata properties
+ * that are specifically applicable to Office Open XML documents.
+ * Extended properties consist of 24 simple properties and 3 complex properties stored in the
+ * part targeted by the relationship of type
+ */
+ POIXMLProperties.ExtendedProperties ext = props.getExtendedProperties();
+ ext.getUnderlyingProperties().setCompany("Apache Software Foundation");
+ ext.getUnderlyingProperties().setTemplate("XSSF");
+
+ /**
+ * Custom properties enable users to define custom metadata properties.
+ */
+
+ POIXMLProperties.CustomProperties cust = props.getCustomProperties();
+ cust.addProperty("Author", "John Smith");
+ cust.addProperty("Year", 2009);
+ cust.addProperty("Price", 45.50);
+ cust.addProperty("Available", true);
+
+ FileOutputStream out = new FileOutputStream("workbook.xlsx");
+ workbook.write(out);
+ out.close();
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithBorders.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithBorders.java
new file mode 100644
index 000000000..6e12fffe7
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithBorders.java
@@ -0,0 +1,58 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.*;
+
+import java.io.FileOutputStream;
+
+/**
+ * Working with borders
+ */
+public class WorkingWithBorders {
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("borders");
+
+ // Create a row and put some cells in it. Rows are 0 based.
+ Row row = sheet.createRow((short) 1);
+
+ // Create a cell and put a value in it.
+ Cell cell = row.createCell((short) 1);
+ cell.setCellValue(4);
+
+ // Style the cell with borders all around.
+ CellStyle style = wb.createCellStyle();
+ style.setBorderBottom(CellStyle.BORDER_THIN);
+ style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
+ style.setBorderLeft(CellStyle.BORDER_THIN);
+ style.setLeftBorderColor(IndexedColors.GREEN.getIndex());
+ style.setBorderRight(CellStyle.BORDER_THIN);
+ style.setRightBorderColor(IndexedColors.BLUE.getIndex());
+ style.setBorderTop(CellStyle.BORDER_MEDIUM_DASHED);
+ style.setTopBorderColor(IndexedColors.BLACK.getIndex());
+ cell.setCellStyle(style);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("xssf-borders.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithFonts.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithFonts.java
new file mode 100644
index 000000000..3d4393ba1
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithFonts.java
@@ -0,0 +1,101 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.ss.usermodel.IndexedColors;
+
+import java.io.FileOutputStream;
+
+/**
+ * Working with Fonts
+ */
+public class WorkingWithFonts {
+ public static void main(String[] args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ Sheet sheet = wb.createSheet("Fonts");
+
+ Font font0 = wb.createFont();
+ font0.setColor(IndexedColors.BROWN.getIndex());
+ CellStyle style0 = wb.createCellStyle();
+ style0.setFont(font0);
+
+ Font font1 = wb.createFont();
+ font1.setFontHeightInPoints((short)14);
+ font1.setFontName("Courier New");
+ font1.setColor(IndexedColors.RED.getIndex());
+ CellStyle style1 = wb.createCellStyle();
+ style1.setFont(font1);
+
+ Font font2 = wb.createFont();
+ font2.setFontHeightInPoints((short)16);
+ font2.setFontName("Arial");
+ font2.setColor(IndexedColors.GREEN.getIndex());
+ CellStyle style2 = wb.createCellStyle();
+ style2.setFont(font2);
+
+ Font font3 = wb.createFont();
+ font3.setFontHeightInPoints((short)18);
+ font3.setFontName("Times New Roman");
+ font3.setColor(IndexedColors.LAVENDER.getIndex());
+ CellStyle style3 = wb.createCellStyle();
+ style3.setFont(font3);
+
+ Font font4 = wb.createFont();
+ font4.setFontHeightInPoints((short)18);
+ font4.setFontName("Wingdings");
+ font4.setColor(IndexedColors.GOLD.getIndex());
+ CellStyle style4 = wb.createCellStyle();
+ style4.setFont(font4);
+
+ Font font5 = wb.createFont();
+ font5.setFontName("Symbol");
+ CellStyle style5 = wb.createCellStyle();
+ style5.setFont(font5);
+
+ Cell cell0 = sheet.createRow(0).createCell(1);
+ cell0.setCellValue("Default");
+ cell0.setCellStyle(style0);
+
+ Cell cell1 = sheet.createRow(1).createCell(1);
+ cell1.setCellValue("Courier");
+ cell1.setCellStyle(style1);
+
+ Cell cell2 = sheet.createRow(2).createCell(1);
+ cell2.setCellValue("Arial");
+ cell2.setCellStyle(style2);
+
+ Cell cell3 = sheet.createRow(3).createCell(1);
+ cell3.setCellValue("Times New Roman");
+ cell3.setCellStyle(style3);
+
+ Cell cell4 = sheet.createRow(4).createCell(1);
+ cell4.setCellValue("Wingdings");
+ cell4.setCellStyle(style4);
+
+ Cell cell5 = sheet.createRow(5).createCell(1);
+ cell5.setCellValue("Symbol");
+ cell5.setCellStyle(style5);
+
+ // Write the output to a file
+ FileOutputStream fileOut = new FileOutputStream("xssf-fonts.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithPageSetup.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithPageSetup.java
new file mode 100644
index 000000000..25f994f95
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithPageSetup.java
@@ -0,0 +1,83 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.usermodel.examples;
+
+import java.io.FileOutputStream;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+/**
+ * Demonstrates various settings avaiable in the Page Setup dialog
+ */
+public class WorkingWithPageSetup {
+
+ public static void main(String[]args) throws Exception {
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+
+ /**
+ * It's possible to set up repeating rows and columns in your printouts by using the setRepeatingRowsAndColumns() function in the Workbook object.
+ *
+ * This function Contains 5 parameters:
+ * The first parameter is the index to the sheet (0 = first sheet).
+ * The second and third parameters specify the range for the columns to repreat.
+ * To stop the columns from repeating pass in -1 as the start and end column.
+ * The fourth and fifth parameters specify the range for the rows to repeat.
+ * To stop the columns from repeating pass in -1 as the start and end rows.
+ */
+ Sheet sheet1 = wb.createSheet("new sheet");
+ Sheet sheet2 = wb.createSheet("second sheet");
+
+ // Set the columns to repeat from column 0 to 2 on the first sheet
+ Row row1 = sheet1.createRow(0);
+ row1.createCell(0).setCellValue(1);
+ row1.createCell(1).setCellValue(2);
+ row1.createCell(2).setCellValue(3);
+ Row row2 = sheet1.createRow(1);
+ row2.createCell(1).setCellValue(4);
+ row2.createCell(2).setCellValue(5);
+
+
+ Row row3 = sheet2.createRow(1);
+ row3.createCell(0).setCellValue(2.1);
+ row3.createCell(4).setCellValue(2.2);
+ row3.createCell(5).setCellValue(2.3);
+ Row row4 = sheet2.createRow(2);
+ row4.createCell(4).setCellValue(2.4);
+ row4.createCell(5).setCellValue(2.5);
+
+ // Set the columns to repeat from column 0 to 2 on the first sheet
+ sheet1.setRepeatingColumns(CellRangeAddress.valueOf("A:C"));
+ // Set the the repeating rows and columns on the second sheet.
+ CellRangeAddress cra = CellRangeAddress.valueOf("E2:F3");
+ sheet2.setRepeatingColumns(cra);
+ sheet2.setRepeatingRows(cra);
+
+ //set the print area for the first sheet
+ wb.setPrintArea(0, 1, 2, 0, 3);
+
+
+ FileOutputStream fileOut = new FileOutputStream("xssf-printsetup.xlsx");
+ wb.write(fileOut);
+ fileOut.close();
+
+ wb.close();
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithPictures.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithPictures.java
new file mode 100644
index 000000000..e7aeaaac1
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithPictures.java
@@ -0,0 +1,77 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.xssf.usermodel.*;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.util.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+/**
+ * Demonstrates how to insert pictures in a SpreadsheetML document
+ *
+ * @author Yegor Kozlov
+ */
+public class WorkingWithPictures {
+ public static void main(String[] args) throws IOException {
+
+ //create a new workbook
+ Workbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ try {
+ CreationHelper helper = wb.getCreationHelper();
+
+ //add a picture in this workbook.
+ InputStream is = new FileInputStream(args[0]);
+ byte[] bytes = IOUtils.toByteArray(is);
+ is.close();
+ int pictureIdx = wb.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
+
+ //create sheet
+ Sheet sheet = wb.createSheet();
+
+ //create drawing
+ Drawing drawing = sheet.createDrawingPatriarch();
+
+ //add a picture shape
+ ClientAnchor anchor = helper.createClientAnchor();
+ anchor.setCol1(1);
+ anchor.setRow1(1);
+ Picture pict = drawing.createPicture(anchor, pictureIdx);
+
+ //auto-size picture
+ pict.resize(2);
+
+ //save workbook
+ String file = "picture.xls";
+ if(wb instanceof XSSFWorkbook) file += "x"; // NOSONAR
+ OutputStream fileOut = new FileOutputStream(file);
+ try {
+ wb.write(fileOut);
+ } finally {
+ fileOut.close();
+ }
+ } finally {
+ wb.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithRichText.java b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithRichText.java
new file mode 100644
index 000000000..09def9446
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xssf/usermodel/examples/WorkingWithRichText.java
@@ -0,0 +1,68 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel.examples;
+
+import org.apache.poi.xssf.usermodel.*;
+
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+/**
+ * Demonstrates how to work with rich text
+ */
+public class WorkingWithRichText {
+
+ public static void main(String[] args) throws Exception {
+
+ XSSFWorkbook wb = new XSSFWorkbook(); //or new HSSFWorkbook();
+ try {
+ XSSFSheet sheet = wb.createSheet();
+ XSSFRow row = sheet.createRow((short) 2);
+
+ XSSFCell cell = row.createCell(1);
+ XSSFRichTextString rt = new XSSFRichTextString("The quick brown fox");
+
+ XSSFFont font1 = wb.createFont();
+ font1.setBold(true);
+ font1.setColor(new XSSFColor(new java.awt.Color(255, 0, 0)));
+ rt.applyFont(0, 10, font1);
+
+ XSSFFont font2 = wb.createFont();
+ font2.setItalic(true);
+ font2.setUnderline(XSSFFont.U_DOUBLE);
+ font2.setColor(new XSSFColor(new java.awt.Color(0, 255, 0)));
+ rt.applyFont(10, 19, font2);
+
+ XSSFFont font3 = wb.createFont();
+ font3.setColor(new XSSFColor(new java.awt.Color(0, 0, 255)));
+ rt.append(" Jumped over the lazy dog", font3);
+
+ cell.setCellValue(rt);
+
+ // Write the output to a file
+ OutputStream fileOut = new FileOutputStream("xssf-richtext.xlsx");
+ try {
+ wb.write(fileOut);
+ } finally {
+ fileOut.close();
+ }
+ } finally {
+ wb.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleDocument.java b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleDocument.java
new file mode 100644
index 000000000..b7e4da2d4
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleDocument.java
@@ -0,0 +1,120 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xwpf.usermodel;
+
+import java.io.FileOutputStream;
+
+/**
+ * A simple WOrdprocessingML document created by POI XWPF API
+ *
+ * @author Yegor Kozlov
+ */
+public class SimpleDocument {
+
+ public static void main(String[] args) throws Exception {
+ XWPFDocument doc = new XWPFDocument();
+
+ XWPFParagraph p1 = doc.createParagraph();
+ p1.setAlignment(ParagraphAlignment.CENTER);
+ p1.setBorderBottom(Borders.DOUBLE);
+ p1.setBorderTop(Borders.DOUBLE);
+
+ p1.setBorderRight(Borders.DOUBLE);
+ p1.setBorderLeft(Borders.DOUBLE);
+ p1.setBorderBetween(Borders.SINGLE);
+
+ p1.setVerticalAlignment(TextAlignment.TOP);
+
+ XWPFRun r1 = p1.createRun();
+ r1.setBold(true);
+ r1.setText("The quick brown fox");
+ r1.setBold(true);
+ r1.setFontFamily("Courier");
+ r1.setUnderline(UnderlinePatterns.DOT_DOT_DASH);
+ r1.setTextPosition(100);
+
+ XWPFParagraph p2 = doc.createParagraph();
+ p2.setAlignment(ParagraphAlignment.RIGHT);
+
+ //BORDERS
+ p2.setBorderBottom(Borders.DOUBLE);
+ p2.setBorderTop(Borders.DOUBLE);
+ p2.setBorderRight(Borders.DOUBLE);
+ p2.setBorderLeft(Borders.DOUBLE);
+ p2.setBorderBetween(Borders.SINGLE);
+
+ XWPFRun r2 = p2.createRun();
+ r2.setText("jumped over the lazy dog");
+ r2.setStrike(true);
+ r2.setFontSize(20);
+
+ XWPFRun r3 = p2.createRun();
+ r3.setText("and went away");
+ r3.setStrike(true);
+ r3.setFontSize(20);
+ r3.setSubscript(VerticalAlign.SUPERSCRIPT);
+
+
+ XWPFParagraph p3 = doc.createParagraph();
+ p3.setWordWrap(true);
+ p3.setPageBreak(true);
+
+ //p3.setAlignment(ParagraphAlignment.DISTRIBUTE);
+ p3.setAlignment(ParagraphAlignment.BOTH);
+ p3.setSpacingLineRule(LineSpacingRule.EXACT);
+
+ p3.setIndentationFirstLine(600);
+
+
+ XWPFRun r4 = p3.createRun();
+ r4.setTextPosition(20);
+ r4.setText("To be, or not to be: that is the question: "
+ + "Whether 'tis nobler in the mind to suffer "
+ + "The slings and arrows of outrageous fortune, "
+ + "Or to take arms against a sea of troubles, "
+ + "And by opposing end them? To die: to sleep; ");
+ r4.addBreak(BreakType.PAGE);
+ r4.setText("No more; and by a sleep to say we end "
+ + "The heart-ache and the thousand natural shocks "
+ + "That flesh is heir to, 'tis a consummation "
+ + "Devoutly to be wish'd. To die, to sleep; "
+ + "To sleep: perchance to dream: ay, there's the rub; "
+ + ".......");
+ r4.setItalic(true);
+//This would imply that this break shall be treated as a simple line break, and break the line after that word:
+
+ XWPFRun r5 = p3.createRun();
+ r5.setTextPosition(-10);
+ r5.setText("For in that sleep of death what dreams may come");
+ r5.addCarriageReturn();
+ r5.setText("When we have shuffled off this mortal coil,"
+ + "Must give us pause: there's the respect"
+ + "That makes calamity of so long life;");
+ r5.addBreak();
+ r5.setText("For who would bear the whips and scorns of time,"
+ + "The oppressor's wrong, the proud man's contumely,");
+
+ r5.addBreak(BreakClear.ALL);
+ r5.setText("The pangs of despised love, the law's delay,"
+ + "The insolence of office and the spurns" + ".......");
+
+ FileOutputStream out = new FileOutputStream("simple.docx");
+ doc.write(out);
+ out.close();
+
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleImages.java b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleImages.java
new file mode 100644
index 000000000..10e48d1ae
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleImages.java
@@ -0,0 +1,71 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+package org.apache.poi.xwpf.usermodel;
+
+import org.apache.poi.util.Units;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+/**
+ * Demonstrates how to add pictures in a .docx document
+ *
+ * @author Yegor Kozlov
+ */
+public class SimpleImages {
+
+ public static void main(String[] args) throws Exception {
+ XWPFDocument doc = new XWPFDocument();
+ XWPFParagraph p = doc.createParagraph();
+
+ XWPFRun r = p.createRun();
+
+ for(String imgFile : args) {
+ int format;
+
+ if(imgFile.endsWith(".emf")) format = XWPFDocument.PICTURE_TYPE_EMF;
+ else if(imgFile.endsWith(".wmf")) format = XWPFDocument.PICTURE_TYPE_WMF;
+ else if(imgFile.endsWith(".pict")) format = XWPFDocument.PICTURE_TYPE_PICT;
+ else if(imgFile.endsWith(".jpeg") || imgFile.endsWith(".jpg")) format = XWPFDocument.PICTURE_TYPE_JPEG;
+ else if(imgFile.endsWith(".png")) format = XWPFDocument.PICTURE_TYPE_PNG;
+ else if(imgFile.endsWith(".dib")) format = XWPFDocument.PICTURE_TYPE_DIB;
+ else if(imgFile.endsWith(".gif")) format = XWPFDocument.PICTURE_TYPE_GIF;
+ else if(imgFile.endsWith(".tiff")) format = XWPFDocument.PICTURE_TYPE_TIFF;
+ else if(imgFile.endsWith(".eps")) format = XWPFDocument.PICTURE_TYPE_EPS;
+ else if(imgFile.endsWith(".bmp")) format = XWPFDocument.PICTURE_TYPE_BMP;
+ else if(imgFile.endsWith(".wpg")) format = XWPFDocument.PICTURE_TYPE_WPG;
+ else {
+ System.err.println("Unsupported picture: " + imgFile +
+ ". Expected emf|wmf|pict|jpeg|png|dib|gif|tiff|eps|bmp|wpg");
+ continue;
+ }
+
+ r.setText(imgFile);
+ r.addBreak();
+ r.addPicture(new FileInputStream(imgFile), format, imgFile, Units.toEMU(200), Units.toEMU(200)); // 200x200 pixels
+ r.addBreak(BreakType.PAGE);
+ }
+
+ FileOutputStream out = new FileOutputStream("images.docx");
+ doc.write(out);
+ out.close();
+ }
+
+
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleTable.java b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleTable.java
new file mode 100644
index 000000000..387ab449b
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/SimpleTable.java
@@ -0,0 +1,206 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xwpf.usermodel;
+
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.List;
+
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHeight;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTShd;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTString;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVerticalJc;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STShd;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
+
+/**
+ * This program creates a simple WordprocessingML table using POI XWPF API, and
+ * a more complex, styled table using both XWPF and ooxml-schema. It's possible
+ * that not all referenced wordprocessingml classes are defined in
+ * poi-ooxml-schemas-3.8-beta4. If this is the case, you'll need to use the full
+ * ooxml-schemas.jar library.
+ *
+ * @author gisella bronzetti (original)
+ * @author Gregg Morris (styled table)
+ */
+public class SimpleTable {
+
+ public static void main(String[] args) throws Exception {
+ try {
+ createSimpleTable();
+ }
+ catch(Exception e) {
+ System.out.println("Error trying to create simple table.");
+ throw(e);
+ }
+ try {
+ createStyledTable();
+ }
+ catch(Exception e) {
+ System.out.println("Error trying to create styled table.");
+ throw(e);
+ }
+ }
+
+ public static void createSimpleTable() throws Exception {
+ XWPFDocument doc = new XWPFDocument();
+
+ try {
+ XWPFTable table = doc.createTable(3, 3);
+
+ table.getRow(1).getCell(1).setText("EXAMPLE OF TABLE");
+
+ // table cells have a list of paragraphs; there is an initial
+ // paragraph created when the cell is created. If you create a
+ // paragraph in the document to put in the cell, it will also
+ // appear in the document following the table, which is probably
+ // not the desired result.
+ XWPFParagraph p1 = table.getRow(0).getCell(0).getParagraphs().get(0);
+
+ XWPFRun r1 = p1.createRun();
+ r1.setBold(true);
+ r1.setText("The quick brown fox");
+ r1.setItalic(true);
+ r1.setFontFamily("Courier");
+ r1.setUnderline(UnderlinePatterns.DOT_DOT_DASH);
+ r1.setTextPosition(100);
+
+ table.getRow(2).getCell(2).setText("only text");
+
+ OutputStream out = new FileOutputStream("simpleTable.docx");
+ try {
+ doc.write(out);
+ } finally {
+ out.close();
+ }
+ } finally {
+ doc.close();
+ }
+ }
+
+ /**
+ * Create a table with some row and column styling. I "manually" add the
+ * style name to the table, but don't check to see if the style actually
+ * exists in the document. Since I'm creating it from scratch, it obviously
+ * won't exist. When opened in MS Word, the table style becomes "Normal".
+ * I manually set alternating row colors. This could be done using Themes,
+ * but that's left as an exercise for the reader. The cells in the last
+ * column of the table have 10pt. "Courier" font.
+ * I make no claims that this is the "right" way to do it, but it worked
+ * for me. Given the scarcity of XWPF examples, I thought this may prove
+ * instructive and give you ideas for your own solutions.
+
+ * @throws Exception
+ */
+ public static void createStyledTable() throws Exception {
+ // Create a new document from scratch
+ XWPFDocument doc = new XWPFDocument();
+
+ try {
+ // -- OR --
+ // open an existing empty document with styles already defined
+ //XWPFDocument doc = new XWPFDocument(new FileInputStream("base_document.docx"));
+
+ // Create a new table with 6 rows and 3 columns
+ int nRows = 6;
+ int nCols = 3;
+ XWPFTable table = doc.createTable(nRows, nCols);
+
+ // Set the table style. If the style is not defined, the table style
+ // will become "Normal".
+ CTTblPr tblPr = table.getCTTbl().getTblPr();
+ CTString styleStr = tblPr.addNewTblStyle();
+ styleStr.setVal("StyledTable");
+
+ // Get a list of the rows in the table
+ List rows = table.getRows();
+ int rowCt = 0;
+ int colCt = 0;
+ for (XWPFTableRow row : rows) {
+ // get table row properties (trPr)
+ CTTrPr trPr = row.getCtRow().addNewTrPr();
+ // set row height; units = twentieth of a point, 360 = 0.25"
+ CTHeight ht = trPr.addNewTrHeight();
+ ht.setVal(BigInteger.valueOf(360));
+
+ // get the cells in this row
+ List cells = row.getTableCells();
+ // add content to each cell
+ for (XWPFTableCell cell : cells) {
+ // get a table cell properties element (tcPr)
+ CTTcPr tcpr = cell.getCTTc().addNewTcPr();
+ // set vertical alignment to "center"
+ CTVerticalJc va = tcpr.addNewVAlign();
+ va.setVal(STVerticalJc.CENTER);
+
+ // create cell color element
+ CTShd ctshd = tcpr.addNewShd();
+ ctshd.setColor("auto");
+ ctshd.setVal(STShd.CLEAR);
+ if (rowCt == 0) {
+ // header row
+ ctshd.setFill("A7BFDE");
+ } else if (rowCt % 2 == 0) {
+ // even row
+ ctshd.setFill("D3DFEE");
+ } else {
+ // odd row
+ ctshd.setFill("EDF2F8");
+ }
+
+ // get 1st paragraph in cell's paragraph list
+ XWPFParagraph para = cell.getParagraphs().get(0);
+ // create a run to contain the content
+ XWPFRun rh = para.createRun();
+ // style cell as desired
+ if (colCt == nCols - 1) {
+ // last column is 10pt Courier
+ rh.setFontSize(10);
+ rh.setFontFamily("Courier");
+ }
+ if (rowCt == 0) {
+ // header row
+ rh.setText("header row, col " + colCt);
+ rh.setBold(true);
+ para.setAlignment(ParagraphAlignment.CENTER);
+ } else {
+ // other rows
+ rh.setText("row " + rowCt + ", col " + colCt);
+ para.setAlignment(ParagraphAlignment.LEFT);
+ }
+ colCt++;
+ } // for cell
+ colCt = 0;
+ rowCt++;
+ } // for row
+
+ // write the file
+ OutputStream out = new FileOutputStream("styledTable.docx");
+ try {
+ doc.write(out);
+ } finally {
+ out.close();
+ }
+ } finally {
+ doc.close();
+ }
+ }
+}
diff --git a/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/UpdateEmbeddedDoc.java b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/UpdateEmbeddedDoc.java
new file mode 100644
index 000000000..06493ce35
--- /dev/null
+++ b/trunk/src/examples/src/org/apache/poi/xwpf/usermodel/UpdateEmbeddedDoc.java
@@ -0,0 +1,220 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.xwpf.usermodel;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.Iterator;
+
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Cell;
+
+/**
+ * Tests whether it is possible to successfully update an Excel workbook that is
+ * embedded into a WordprocessingML document. Note that the test has currently
+ * only been conducted with a binary Excel workbook and NOT yet with a
+ * SpreadsheetML workbook embedded into the document.
+ *
+ *
+ * This code was successfully tested with the following file from the POI test collection:
+ * http://svn.apache.org/repos/asf/poi/trunk/test-data/document/EmbeddedDocument.docx
+ *
+ *
+ * @author Mark B
+ */
+public class UpdateEmbeddedDoc {
+
+ private XWPFDocument doc = null;
+ private File docFile = null;
+
+ private static final int SHEET_NUM = 0;
+ private static final int ROW_NUM = 0;
+ private static final int CELL_NUM = 0;
+ private static final double NEW_VALUE = 100.98D;
+ private static final String BINARY_EXTENSION = "xls";
+ private static final String OPENXML_EXTENSION = "xlsx";
+
+ /**
+ * Create a new instance of the UpdateEmbeddedDoc class using the following
+ * parameters;
+ *
+ * @param filename An instance of the String class that encapsulates the name
+ * of and path to a WordprocessingML Word document that contains an
+ * embedded binary Excel workbook.
+ * @throws java.io.FileNotFoundException Thrown if the file cannot be found
+ * on the underlying file system.
+ * @throws java.io.IOException Thrown if a problem occurs in the underlying
+ * file system.
+ */
+ public UpdateEmbeddedDoc(String filename) throws FileNotFoundException, IOException {
+ this.docFile = new File(filename);
+ FileInputStream fis = null;
+ if (!this.docFile.exists()) {
+ throw new FileNotFoundException("The Word dcoument " +
+ filename +
+ " does not exist.");
+ }
+ try {
+
+ // Open the Word document file and instantiate the XWPFDocument
+ // class.
+ fis = new FileInputStream(this.docFile);
+ this.doc = new XWPFDocument(fis);
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ fis = null;
+ } catch (IOException ioEx) {
+ System.out.println("IOException caught trying to close " +
+ "FileInputStream in the constructor of " +
+ "UpdateEmbeddedDoc.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Called to update the embedded Excel workbook. As the format and structire
+ * of the workbook are known in advance, all this code attempts to do is
+ * write a new value into the first cell on the first row of the first
+ * worksheet. Prior to executing this method, that cell will contain the
+ * value 1.
+ *
+ * @throws org.apache.poi.openxml4j.exceptions.OpenXML4JException
+ * Rather
+ * than use the specific classes (HSSF/XSSF) to handle the embedded
+ * workbook this method uses those defeined in the SS stream. As
+ * a result, it might be the case that a SpreadsheetML file is
+ * opened for processing, throwing this exception if that file is
+ * invalid.
+ * @throws java.io.IOException Thrown if a problem occurs in the underlying
+ * file system.
+ */
+ public void updateEmbeddedDoc() throws OpenXML4JException, IOException {
+ Workbook workbook = null;
+ Sheet sheet = null;
+ Row row = null;
+ Cell cell = null;
+ PackagePart pPart = null;
+ Iterator pIter = null;
+ List embeddedDocs = this.doc.getAllEmbedds();
+ if (embeddedDocs != null && !embeddedDocs.isEmpty()) {
+ pIter = embeddedDocs.iterator();
+ while (pIter.hasNext()) {
+ pPart = pIter.next();
+ if (pPart.getPartName().getExtension().equals(BINARY_EXTENSION) ||
+ pPart.getPartName().getExtension().equals(OPENXML_EXTENSION)) {
+
+ // Get an InputStream from the pacage part and pass that
+ // to the create method of the WorkbookFactory class. Update
+ // the resulting Workbook and then stream that out again
+ // using an OutputStream obtained from the same PackagePart.
+ workbook = WorkbookFactory.create(pPart.getInputStream());
+ sheet = workbook.getSheetAt(SHEET_NUM);
+ row = sheet.getRow(ROW_NUM);
+ cell = row.getCell(CELL_NUM);
+ cell.setCellValue(NEW_VALUE);
+ workbook.write(pPart.getOutputStream());
+ }
+ }
+
+ // Finally, write the newly modified Word document out to file.
+ this.doc.write(new FileOutputStream(this.docFile));
+ }
+ }
+
+ /**
+ * Called to test whether or not the embedded workbook was correctly
+ * updated. This method simply recovers the first cell from the first row
+ * of the first workbook and tests the value it contains.
+ *
+ * Note that execution will not continue up to the assertion as the
+ * embedded workbook is now corrupted and causes an IllegalArgumentException
+ * with the following message
+ *
+ * java.lang.IllegalArgumentException: Your InputStream was neither an
+ * OLE2 stream, nor an OOXML stream
+ *
+ * to be thrown when the WorkbookFactory.createWorkbook(InputStream) method
+ * is executed.
+ *
+ * @throws org.apache.poi.openxml4j.exceptions.OpenXML4JException
+ * Rather
+ * than use the specific classes (HSSF/XSSF) to handle the embedded
+ * workbook this method uses those defeined in the SS stream. As
+ * a result, it might be the case that a SpreadsheetML file is
+ * opened for processing, throwing this exception if that file is
+ * invalid.
+ * @throws java.io.IOException Thrown if a problem occurs in the underlying
+ * file system.
+ */
+ public void checkUpdatedDoc() throws OpenXML4JException, IOException {
+ Workbook workbook = null;
+ Sheet sheet = null;
+ Row row = null;
+ Cell cell = null;
+ PackagePart pPart = null;
+ Iterator pIter = null;
+ List embeddedDocs = this.doc.getAllEmbedds();
+ if (embeddedDocs != null && !embeddedDocs.isEmpty()) {
+ pIter = embeddedDocs.iterator();
+ while (pIter.hasNext()) {
+ pPart = pIter.next();
+ if (pPart.getPartName().getExtension().equals(BINARY_EXTENSION) ||
+ pPart.getPartName().getExtension().equals(OPENXML_EXTENSION)) {
+ workbook = WorkbookFactory.create(pPart.getInputStream());
+ sheet = workbook.getSheetAt(SHEET_NUM);
+ row = sheet.getRow(ROW_NUM);
+ cell = row.getCell(CELL_NUM);
+ assertEquals(cell.getNumericCellValue(), NEW_VALUE, 0.0001);
+ }
+ }
+ }
+ }
+
+ /**
+ * Code to test updating of the embedded Excel workbook.
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ try {
+ UpdateEmbeddedDoc ued = new UpdateEmbeddedDoc(args[0]);
+ ued.updateEmbeddedDoc();
+ ued.checkUpdatedDoc();
+ } catch (Exception ex) {
+ System.out.println(ex.getClass().getName());
+ System.out.println(ex.getMessage());
+ ex.printStackTrace(System.out);
+ }
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java
new file mode 100644
index 000000000..55e51975f
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java
@@ -0,0 +1,144 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.poi.ss.excelant.util.ExcelAntEvaluationResult;
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+/**
+ * Instances of this class are used to evaluate a single cell. This is usually
+ * after some values have been set. The evaluation is actually performed
+ * by a WorkbookUtil instance. The evaluate() method of the WorkbookUtil
+ * class returns an EvaluationResult which encapsulates the results and
+ * information from the evaluation.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+
+ *
+ */
+public class ExcelAntEvaluateCell extends Task {
+
+ private String cell ;
+ private double expectedValue ;
+ private double precision ;
+ private double precisionToUse ;
+ private double globalPrecision ;
+ private boolean requiredToPass = false ;
+
+
+ private ExcelAntEvaluationResult result ;
+
+ private ExcelAntWorkbookUtil wbUtil ;
+
+ private boolean showDelta = false ;
+
+
+ public ExcelAntEvaluateCell() {}
+
+ protected void setWorkbookUtil( ExcelAntWorkbookUtil wb ) {
+ wbUtil = wb ;
+ }
+
+ public void setShowDelta( boolean value ) {
+ showDelta = value ;
+ }
+
+ protected boolean showDelta() {
+ return showDelta ;
+ }
+
+ public void setCell(String cell) {
+ this.cell = cell;
+ }
+
+ public void setRequiredToPass( boolean val ) {
+ requiredToPass = val ;
+ }
+
+ protected boolean requiredToPass() {
+ return requiredToPass ;
+ }
+
+ public void setExpectedValue(double expectedValue) {
+ this.expectedValue = expectedValue;
+ }
+
+ public void setPrecision(double precision) {
+ this.precision = precision;
+ }
+
+ protected void setGlobalPrecision( double prec ) {
+ globalPrecision = prec ;
+ }
+
+ protected String getCell() {
+ return cell;
+ }
+
+ protected double getExpectedValue() {
+ return expectedValue;
+ }
+
+ protected double getPrecision() {
+ return precisionToUse;
+ }
+
+ public void execute() throws BuildException {
+
+ precisionToUse = 0 ;
+
+ // if there is a globalPrecision we will use it unless there is also
+ // precision set at the evaluate level, then we use that. If there
+ // is not a globalPrecision, we will use the local precision.
+ log( "test precision = " + precision + "\tglobal precision = " + globalPrecision, Project.MSG_VERBOSE ) ;
+ if( globalPrecision > 0 ) {
+ if( precision > 0 ) {
+ precisionToUse = precision ;
+ log( "Using evaluate precision of " + precision + " over the " +
+ "global precision of " + globalPrecision, Project.MSG_VERBOSE ) ;
+ } else {
+ precisionToUse = globalPrecision ;
+ log( "Using global precision of " + globalPrecision, Project.MSG_VERBOSE ) ;
+ }
+ } else {
+ precisionToUse = precision ;
+ log( "Using evaluate precision of " + precision, Project.MSG_VERBOSE ) ;
+ }
+ result = wbUtil.evaluateCell(cell, expectedValue, precisionToUse ) ;
+
+ StringBuilder sb = new StringBuilder() ;
+ sb.append( "evaluation of cell " ) ;
+ sb.append( cell ) ;
+ sb.append( " resulted in " ) ;
+ sb.append( result.getReturnValue() ) ;
+ if(showDelta) {
+ sb.append(" with a delta of ").append(result.getDelta());
+ }
+
+ log( sb.toString(), Project.MSG_DEBUG) ;
+
+ }
+
+ public ExcelAntEvaluationResult getResult() {
+ return result ;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java
new file mode 100644
index 000000000..eaf04007c
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java
@@ -0,0 +1,75 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+/**
+ * This is the class that backs the tag in the Ant task.
+ *
+ * Its purpose is to provide a way to manipulate a workbook in the course
+ * of an ExcelAnt task. The idea being to model a way for test writers to
+ * simulate the behaviors of the workbook.
+ *
+ * Suppose, for example, you have a workbook that has a worksheet that
+ * reacts to values entered or selected by the user. It's possible in
+ * Excel to change other cells based on this but this isn't easily possible
+ * in POI. In ExcelAnt we handle this using the Handler, which is a Java
+ * class you write to manipulate the workbook.
+ *
+ * In order to use this tag you must write a class that implements the
+ * IExcelAntWorkbookHandler interface. After writing the
+ * class you should package it and it's dependencies into a jar file to
+ * add as library in your Ant build file.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntHandlerTask extends Task {
+
+ private String className ;
+
+ private ExcelAntWorkbookUtil wbUtil ;
+
+ public void setClassName( String cName ) {
+ className = cName ;
+ }
+
+ protected void setEAWorkbookUtil( ExcelAntWorkbookUtil wkbkUtil ) {
+ wbUtil = wkbkUtil ;
+ }
+
+ public void execute() throws BuildException {
+ log( "handling the workbook with class " + className, Project.MSG_INFO ) ;
+ try {
+ Class> clazz = Class.forName( className ) ;
+ Object handlerObj = clazz.newInstance() ;
+ if( handlerObj instanceof IExcelAntWorkbookHandler ) {
+ IExcelAntWorkbookHandler iHandler = (IExcelAntWorkbookHandler)handlerObj ;
+ iHandler.setWorkbook( wbUtil.getWorkbook() ) ;
+ iHandler.execute() ;
+ }
+ } catch( Exception e ) {
+ throw new BuildException( e.getMessage(), e ) ;
+ }
+ }
+ }
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java
new file mode 100644
index 000000000..0739ef583
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java
@@ -0,0 +1,39 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.tools.ant.taskdefs.Typedef;
+
+/**
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntPrecision extends Typedef {
+
+ private double value ;
+
+ public void setValue( double precision ) {
+ value = precision ;
+ }
+
+ public double getValue() {
+ return value ;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java
new file mode 100644
index 000000000..974b1b9e4
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java
@@ -0,0 +1,48 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.tools.ant.Task;
+
+/**
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public abstract class ExcelAntSet extends Task {
+
+ protected String cellStr ;
+
+ protected ExcelAntWorkbookUtil wbUtil ;
+
+ public void setCell( String cellName ) {
+ cellStr = cellName ;
+ }
+
+ public String getCell() {
+ return cellStr ;
+ }
+
+
+ public void setWorkbookUtil( ExcelAntWorkbookUtil wb ) {
+ wbUtil = wb ;
+ }
+
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java
new file mode 100644
index 000000000..3e6c4cd60
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java
@@ -0,0 +1,58 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Class for use in an Ant build script that sets the value of an Excel
+ * sheet cell using the cell id ('Sheet Name'!cellId).
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntSetDoubleCell extends ExcelAntSet {
+ private double cellValue;
+
+ public ExcelAntSetDoubleCell() {}
+
+ /**
+ * Set the value of the specified cell as the double passed in.
+ * @param value The double-value that should be set when this task is executed.
+ */
+ public void setValue( double value ) {
+ cellValue = value ;
+ }
+
+ /**
+ * Return the cell value as a double.
+ * @return The double-value of the cell as populated via setValue(), null
+ * if the value was not set yet.
+ */
+ public double getCellValue() {
+ return cellValue;
+ }
+
+ public void execute() throws BuildException {
+ wbUtil.setDoubleValue(cellStr, cellValue ) ;
+
+ log( "set cell " + cellStr + " to value " + cellValue + " as double.", Project.MSG_DEBUG ) ;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java
new file mode 100644
index 000000000..1b9d5f053
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java
@@ -0,0 +1,52 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Class for use in an Ant build script that sets the formula of an Excel
+ * sheet cell using the cell id ('Sheet Name'!cellId).
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntSetFormulaCell extends ExcelAntSet {
+
+
+ private String cellValue ;
+
+ public ExcelAntSetFormulaCell() {}
+
+ public void setValue( String value ) {
+ cellValue = value ;
+ }
+
+ protected String getCellValue() {
+ return cellValue;
+ }
+
+ public void execute() throws BuildException {
+
+ wbUtil.setFormulaValue( cellStr, cellValue ) ;
+
+ log( "set cell " + cellStr + " to formula " + cellValue, Project.MSG_DEBUG ) ;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java
new file mode 100644
index 000000000..047e544ea
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java
@@ -0,0 +1,58 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+
+/**
+ * Class for use in an Ant build script that sets the value of an Excel
+ * sheet cell using the cell id ('Sheet Name'!cellId).
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntSetStringCell extends ExcelAntSet {
+ private String stringValue ;
+
+ public ExcelAntSetStringCell() {}
+
+ /**
+ * Set the value of the cell to the String passed in.
+ * @param value The string-value that should be set when this task is executed.
+ */
+ public void setValue(String value ) {
+ stringValue = value ;
+ }
+
+ /**
+ * Return the value that will be set into the cell.
+ * @return The string-value of the cell as populated via setValue(), null
+ * if the value was not set yet.
+ */
+ public String getCellValue() {
+ return stringValue;
+ }
+
+ public void execute() throws BuildException {
+ wbUtil.setStringValue(cellStr, stringValue ) ;
+
+ log( "set cell " + cellStr + " to value " + stringValue + " as String.", Project.MSG_DEBUG ) ;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java
new file mode 100644
index 000000000..13271611b
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java
@@ -0,0 +1,182 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtilFactory;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.Locale;
+
+/**
+ * Ant task class for testing Excel workbook cells.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntTask extends Task {
+
+ public static final String VERSION = "0.5.0" ;
+
+ private String excelFileName ;
+
+ private boolean failOnError = false ;
+
+ private ExcelAntWorkbookUtil workbookUtil ;
+
+ private ExcelAntPrecision precision ;
+
+ private LinkedList tests ;
+ private LinkedList functions ;
+
+ public ExcelAntTask() {
+ tests = new LinkedList() ;
+ functions = new LinkedList() ;
+ }
+
+ public void addPrecision( ExcelAntPrecision prec ) {
+ precision = prec ;
+ }
+
+ public void setFailOnError( boolean value ) {
+ failOnError = value ;
+ }
+ public void setFileName( String fileName ) {
+ excelFileName = fileName ;
+ }
+
+ public void addTest( ExcelAntTest testElement ) {
+ tests.add( testElement ) ;
+ }
+
+ public void addUdf( ExcelAntUserDefinedFunction def ) {
+ functions.add( def ) ;
+ }
+
+ public void execute() throws BuildException {
+ checkClassPath();
+
+ int totalCount = 0 ;
+ int successCount = 0 ;
+
+ StringBuilder versionBffr = new StringBuilder() ;
+ versionBffr.append( "ExcelAnt version " ) ;
+ versionBffr.append( VERSION ) ;
+ versionBffr.append( " Copyright 2011" ) ;
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyy", Locale.ROOT ) ;
+ double currYear = Double.parseDouble( sdf.format( new Date() ) );
+ if( currYear > 2011 ) {
+ versionBffr.append( "-" ) ;
+ versionBffr.append( currYear ) ;
+ }
+ log( versionBffr.toString(), Project.MSG_INFO ) ;
+
+ log( "Using input file: " + excelFileName, Project.MSG_INFO ) ;
+
+ Workbook targetWorkbook = loadWorkbook() ;
+ if( targetWorkbook == null ) {
+ log( "Unable to load " + excelFileName +
+ ". Verify the file exists and can be read.",
+ Project.MSG_ERR ) ;
+ return ;
+ }
+ if( tests.size() > 0 ) {
+
+ for (ExcelAntTest test : tests) {
+ log("executing test: " + test.getName(), Project.MSG_DEBUG);
+
+ workbookUtil = ExcelAntWorkbookUtilFactory.getInstance(excelFileName);
+
+ for (ExcelAntUserDefinedFunction eaUdf : functions) {
+ try {
+ workbookUtil.addFunction(eaUdf.getFunctionAlias(), eaUdf.getClassName());
+ } catch (Exception e) {
+ throw new BuildException(e.getMessage(), e);
+ }
+ }
+ test.setWorkbookUtil(workbookUtil);
+
+ if (precision != null && precision.getValue() > 0) {
+ log("setting precision for the test " + test.getName(), Project.MSG_VERBOSE);
+ test.setPrecision(precision.getValue());
+ }
+
+ test.execute();
+
+ if (test.didTestPass()) {
+ successCount++;
+ } else {
+ if (failOnError) {
+ throw new BuildException("Test " + test.getName() + " failed.");
+ }
+ }
+ totalCount++;
+
+ workbookUtil = null;
+ }
+ log( successCount + "/" + totalCount + " tests passed.", Project.MSG_INFO ) ;
+ workbookUtil = null ;
+ }
+ }
+
+
+ private Workbook loadWorkbook() {
+ if (excelFileName == null) {
+ throw new BuildException("fileName attribute must be set!",
+ getLocation());
+ }
+
+ File workbookFile = new File( excelFileName ) ;
+ try {
+ FileInputStream fis = new FileInputStream( workbookFile ) ;
+ return WorkbookFactory.create( fis ) ;
+ } catch (Exception e) {
+ throw new BuildException("Cannot load file " + excelFileName
+ + ". Make sure the path and file permissions are correct.", e, getLocation());
+ }
+ }
+
+
+ /**
+ * ExcelAnt depends on external libraries not included in the Ant distribution.
+ * Give user a sensible message if any if the required jars are missing.
+ */
+ private void checkClassPath(){
+ try {
+ Class.forName("org.apache.poi.hssf.usermodel.HSSFWorkbook");
+ Class.forName("org.apache.poi.ss.usermodel.WorkbookFactory");
+ } catch (Exception e) {
+ throw new BuildException(
+ "The for must include poi.jar and poi-ooxml.jar " +
+ "if not in Ant's own classpath. Processing .xlsx spreadsheets requires " +
+ "additional poi-ooxml-schemas.jar, xmlbeans.jar" ,
+ e, getLocation());
+ }
+
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java
new file mode 100644
index 000000000..36b9b6d9e
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java
@@ -0,0 +1,220 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.apache.poi.ss.excelant.util.ExcelAntEvaluationResult;
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+
+/**
+ * This class represents a single test. In order for the test any and all
+ * ExcelAntEvaluateCell evaluations must pass. Therefore it is recommended
+ * that you use only 1 evaluator but you can use more if you choose.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntTest extends Task{
+ private LinkedList evaluators;
+
+ private LinkedList testTasks;
+
+ private String name;
+
+ private double globalPrecision;
+
+ private boolean showSuccessDetails = false;
+
+ private boolean showFailureDetail = false;
+ LinkedList failureMessages;
+
+
+ private ExcelAntWorkbookUtil workbookUtil;
+
+ private boolean passed = true;
+
+
+ public ExcelAntTest() {
+ evaluators = new LinkedList();
+ failureMessages = new LinkedList();
+ testTasks = new LinkedList();
+ }
+
+ public void setPrecision( double precision ) {
+ globalPrecision = precision;
+ }
+
+ public void setWorkbookUtil( ExcelAntWorkbookUtil wbUtil ) {
+ workbookUtil = wbUtil;
+ }
+
+
+ public void setShowFailureDetail( boolean value ) {
+ showFailureDetail = value;
+ }
+
+ public void setName( String nm ) {
+ name = nm;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setShowSuccessDetails( boolean details ) {
+ showSuccessDetails = details;
+ }
+
+ public boolean showSuccessDetails() {
+ return showSuccessDetails;
+ }
+
+ public void addSetDouble( ExcelAntSetDoubleCell setter ) {
+ addSetter( setter );
+ }
+
+ public void addSetString( ExcelAntSetStringCell setter ){
+ addSetter( setter );
+ }
+
+ public void addSetFormula( ExcelAntSetFormulaCell setter ) {
+ addSetter( setter );
+ }
+
+ public void addHandler( ExcelAntHandlerTask handler ) {
+ testTasks.add( handler );
+ }
+
+ private void addSetter( ExcelAntSet setter ) {
+// setters.add( setter );
+ testTasks.add( setter );
+ }
+
+ public void addEvaluate( ExcelAntEvaluateCell evaluator ) {
+// evaluators.add( evaluator );
+ testTasks.add( evaluator );
+ }
+
+// public LinkedList getSetters() {
+// return setters;
+// }
+
+ protected LinkedList getEvaluators() {
+ return evaluators;
+ }
+
+ public void execute() throws BuildException {
+
+ Iterator taskIt = testTasks.iterator();
+
+ int testCount = evaluators.size();
+ int failureCount = 0;
+
+ // roll over all sub task elements in one loop. This allows the
+ // ordering of the sub elements to be considered.
+ while( taskIt.hasNext() ) {
+ Task task = taskIt.next();
+
+ // log( task.getClass().getName(), Project.MSG_INFO );
+
+ if( task instanceof ExcelAntSet ) {
+ ExcelAntSet set = (ExcelAntSet) task;
+ set.setWorkbookUtil(workbookUtil);
+ set.execute();
+ }
+
+ if( task instanceof ExcelAntHandlerTask ) {
+ ExcelAntHandlerTask handler = (ExcelAntHandlerTask)task;
+ handler.setEAWorkbookUtil(workbookUtil );
+ handler.execute();
+ }
+
+ if (task instanceof ExcelAntEvaluateCell ) {
+ ExcelAntEvaluateCell eval = (ExcelAntEvaluateCell)task;
+ eval.setWorkbookUtil( workbookUtil );
+
+ if( globalPrecision > 0 ) {
+ log( "setting globalPrecision to " + globalPrecision + " in the evaluator", Project.MSG_VERBOSE );
+ eval.setGlobalPrecision( globalPrecision );
+ }
+
+ try {
+ eval.execute();
+ ExcelAntEvaluationResult result = eval.getResult();
+ if( result.didTestPass() &&
+ !result.evaluationCompleteWithError()) {
+ if(showSuccessDetails) {
+ log("Succeeded when evaluating " +
+ result.getCellName() + ". It evaluated to " +
+ result.getReturnValue() + " when the value of " +
+ eval.getExpectedValue() + " with precision of " +
+ eval.getPrecision(), Project.MSG_INFO );
+ }
+ } else {
+ if(showFailureDetail) {
+ failureMessages.add( "\tFailed to evaluate cell " +
+ result.getCellName() + ". It evaluated to " +
+ result.getReturnValue() + " when the value of " +
+ eval.getExpectedValue() + " with precision of " +
+ eval.getPrecision() + " was expected." );
+
+ }
+ passed = false;
+ failureCount++;
+
+ if(eval.requiredToPass()) {
+ throw new BuildException( "\tFailed to evaluate cell " +
+ result.getCellName() + ". It evaluated to " +
+ result.getReturnValue() + " when the value of " +
+ eval.getExpectedValue() + " with precision of " +
+ eval.getPrecision() + " was expected." );
+ }
+ }
+ } catch( NullPointerException npe ) {
+ // this means the cell reference in the test is bad.
+ log( "Cell assignment " + eval.getCell() + " in test " + getName() +
+ " appears to point to an empy cell. Please check the " +
+ " reference in the ant script.", Project.MSG_ERR );
+ }
+ }
+ }
+
+ if(!passed) {
+ log( "Test named " + name + " failed because " + failureCount +
+ " of " + testCount + " evaluations failed to " +
+ "evaluate correctly.",
+ Project.MSG_ERR );
+ if(showFailureDetail && failureMessages.size() > 0 ) {
+ for (String failureMessage : failureMessages) {
+ log(failureMessage, Project.MSG_ERR);
+ }
+ }
+ }
+ }
+
+ public boolean didTestPass() {
+
+ return passed;
+ }
+ }
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java
new file mode 100644
index 000000000..5c19912b3
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.tools.ant.taskdefs.Typedef;
+
+/**
+ * This class encapsulates the Strings necessary to create the User Defined
+ * Function instances that will be passed to POI's Evaluator instance.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class ExcelAntUserDefinedFunction extends Typedef {
+
+
+ public String functionAlias ;
+
+ public String className ;
+
+
+ public ExcelAntUserDefinedFunction() {}
+
+ protected String getFunctionAlias() {
+ return functionAlias;
+ }
+
+ public void setFunctionAlias(String functionAlias) {
+ this.functionAlias = functionAlias;
+ }
+
+ protected String getClassName() {
+ // workaround for IBM JDK assigning the classname to the lowercase instance provided by Definer!?!
+ // I could not find out why that happens, the wrong assignment seems to be done somewhere deep inside Ant itself
+ // or even in IBM JDK as Oracle JDK does not have this problem.
+ if(className == null) {
+ return getClassname();
+ }
+
+ return className;
+ }
+
+ public void setClassName(String className) {
+ this.className = className;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java
new file mode 100644
index 000000000..139e34c0d
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java
@@ -0,0 +1,42 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant;
+
+import org.apache.poi.ss.usermodel.Workbook;
+
+
+/**
+ * In Excel there are many ways to handle manipulating a workbook based
+ * on some arbitrary user action (onChange, etc). You use this interface
+ * to create classes that will handle the workbook in whatever manner is needed
+ * that cannot be handled by POI.
+ *
+ * For example, suppose that in Excel when you update a cell the workbook
+ * does some calculations and updates other cells based on that change. In
+ * ExcelAnt you would set the value of the cell then write your own handler
+ * then call that from your Ant task after the set task.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public interface IExcelAntWorkbookHandler {
+ public void setWorkbook( Workbook workbook ) ;
+
+ public void execute() ;
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java
new file mode 100644
index 000000000..4eecd4fae
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java
@@ -0,0 +1,112 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant.util;
+
+/**
+ * A simple class that encapsulates information about a cell evaluation
+ * from POI.
+ *
+ * @author Jon Svede (jon [at] loquatic [dot] com)
+ * @author Brian Bush (brian [dot] bush [at] nrel [dot] gov)
+ *
+ */
+public class ExcelAntEvaluationResult {
+
+ /**
+ * This boolean flag is used to determine if the evaluation completed
+ * without error. This alone doesn't ensure that the evaluation was
+ * successful.
+ */
+ private boolean evaluationCompletedWithError ;
+
+ /**
+ * This boolean flag is used to determine if the result was within
+ * the specified precision.
+ */
+ private boolean didPass ;
+
+ /**
+ * This is the actual value returned from the evaluation.
+ */
+ private double returnValue ;
+
+ /**
+ * Any error message String values that need to be returned.
+ */
+ private String errorMessage ;
+
+ /**
+ * Stores the absolute value of the delta for this evaluation.
+ */
+ private double actualDelta ;
+
+ /**
+ * This stores the fully qualified cell name (sheetName!cellId).
+ */
+ private String cellName ;
+
+
+
+ public ExcelAntEvaluationResult(boolean completedWithError,
+ boolean passed,
+ double retValue,
+ String errMessage,
+ double delta,
+ String cellId) {
+
+ evaluationCompletedWithError = completedWithError;
+ didPass = passed;
+ returnValue = retValue;
+ errorMessage = errMessage;
+ actualDelta = delta ;
+ cellName = cellId ;
+ }
+
+ public double getReturnValue() {
+ return returnValue;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public boolean didTestPass() {
+ return didPass ;
+ }
+
+ public boolean evaluationCompleteWithError() {
+ return evaluationCompletedWithError ;
+ }
+
+ public double getDelta() {
+ return actualDelta ;
+ }
+
+ public String getCellName() {
+ return cellName ;
+ }
+
+ @Override
+ public String toString() {
+ return "ExcelAntEvaluationResult [evaluationCompletedWithError="
+ + evaluationCompletedWithError + ", didPass=" + didPass
+ + ", returnValue=" + returnValue + ", errorMessage="
+ + errorMessage + ", actualDelta=" + actualDelta + ", cellName="
+ + cellName + "]";
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java
new file mode 100644
index 000000000..627493b29
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java
@@ -0,0 +1,384 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
+import org.apache.poi.ss.formula.udf.DefaultUDFFinder;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellValue;
+import org.apache.poi.ss.usermodel.FormulaError;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Typedef;
+
+/**
+ * A general utility class that abstracts the POI details of loading the
+ * workbook, accessing and updating cells.
+ *
+ * @author Jon Svede (jon [at] loquatic [dot] com)
+ * @author Brian Bush (brian [dot] bush [at] nrel [dot] gov)
+ *
+ */
+public class ExcelAntWorkbookUtil extends Typedef {
+
+ private String excelFileName;
+
+ private Workbook workbook;
+
+ private final Map xlsMacroList = new HashMap();
+
+ /**
+ * Constructs an instance using a String that contains the fully qualified
+ * path of the Excel file. This constructor initializes a Workbook instance
+ * based on that file name.
+ *
+ * @param fName The fully qualified path of the Excel file.
+ * @throws BuildException If the workbook cannot be loaded.
+ */
+ protected ExcelAntWorkbookUtil(String fName) {
+ excelFileName = fName;
+ loadWorkbook();
+
+ }
+
+ /**
+ * Constructs an instance based on a Workbook instance.
+ *
+ * @param wb The Workbook to use for this instance.
+ */
+ protected ExcelAntWorkbookUtil(Workbook wb) {
+ workbook = wb;
+ }
+
+ /**
+ * Loads the member variable workbook based on the fileName variable.
+ * @return The opened Workbook-instance
+ * @throws BuildException If the workbook cannot be loaded.
+ */
+ private Workbook loadWorkbook() {
+
+ File workbookFile = new File(excelFileName);
+ try {
+ FileInputStream fis = new FileInputStream(workbookFile);
+ try {
+ workbook = WorkbookFactory.create(fis);
+ } finally {
+ fis.close();
+ }
+ } catch(Exception e) {
+ throw new BuildException("Cannot load file " + excelFileName
+ + ". Make sure the path and file permissions are correct.", e);
+ }
+
+ return workbook;
+ }
+
+ /**
+ * Used to add a UDF to the evaluator.
+ * @param name
+ * @param clazzName
+ * @throws ClassNotFoundException
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ */
+ public void addFunction(String name, String clazzName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ Class> clazzInst = Class.forName(clazzName);
+ Object newInst = clazzInst.newInstance();
+ if(newInst instanceof FreeRefFunction) {
+ addFunction(name, (FreeRefFunction)newInst);
+ }
+
+ }
+
+ /**
+ * Updates the internal HashMap of functions with instance and alias passed
+ * in.
+ *
+ * @param name
+ * @param func
+ */
+ protected void addFunction(String name, FreeRefFunction func) {
+ xlsMacroList.put(name, func);
+ }
+
+ /**
+ * returns a UDFFinder that contains all of the functions added.
+ *
+ * @return
+ */
+ protected UDFFinder getFunctions() {
+
+ String[] names = new String[xlsMacroList.size()];
+ FreeRefFunction[] functions = new FreeRefFunction[xlsMacroList.size()];
+
+ int x = 0;
+ for(Map.Entry entry : xlsMacroList.entrySet()) {
+ names[x] = entry.getKey();
+ functions[x] = entry.getValue();
+ }
+
+ UDFFinder udff1 = new DefaultUDFFinder(names, functions);
+ UDFFinder udff = new AggregatingUDFFinder(udff1);
+
+ return udff;
+
+ }
+
+ /**
+ * Returns a formula evaluator that is loaded with the functions that
+ * have been supplied.
+ *
+ * @param fileName
+ * @return
+ */
+ protected FormulaEvaluator getEvaluator(String fileName) {
+ FormulaEvaluator evaluator;
+ if (fileName.endsWith(".xlsx")) {
+ if(xlsMacroList.size() > 0) {
+ evaluator = XSSFFormulaEvaluator.create((XSSFWorkbook) workbook,
+ null,
+ getFunctions());
+ }
+ evaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);
+ } else {
+ if(xlsMacroList.size() > 0) {
+ evaluator = HSSFFormulaEvaluator.create((HSSFWorkbook)workbook,
+ null,
+ getFunctions());
+ }
+
+ evaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
+ }
+
+ return evaluator;
+
+ }
+
+ /**
+ * Returns the Workbook instance associated with this WorkbookUtil.
+ *
+ * @return
+ */
+ public Workbook getWorkbook() {
+ return workbook;
+ }
+
+ /**
+ * Returns the fileName that was used to initialize this instance. May
+ * return null if the instance was constructed from a Workbook object.
+ *
+ * @return
+ */
+ public String getFileName() {
+ return excelFileName;
+ }
+
+ /**
+ * Returns the list of sheet names.
+ *
+ * @return
+ */
+ public List getSheets() {
+ ArrayList sheets = new ArrayList();
+
+ int sheetCount = workbook.getNumberOfSheets();
+
+ for(int x=0; x precision) {
+ evalResults = new ExcelAntEvaluationResult(false, false,
+ resultOfEval.getNumberValue(),
+ "Results was out of range based on precision " + " of "
+ + precision + ". Delta was actually " + delta, delta, cellName);
+ } else {
+ evalResults = new ExcelAntEvaluationResult(false, true,
+ resultOfEval.getNumberValue(),
+ "Evaluation passed without error within in range.", delta, cellName);
+ }
+ } else {
+ String errorMeaning = null;
+ try {
+ errorMeaning = FormulaError.forInt(resultOfEval.getErrorValue()).getString();
+ } catch(IllegalArgumentException iae) {
+ errorMeaning = "unknown error code: " +
+ Byte.toString(resultOfEval.getErrorValue());
+ }
+
+ evalResults = new ExcelAntEvaluationResult(true, false,
+ resultOfEval.getNumberValue(),
+ "Evaluation failed due to an evaluation error of "
+ + resultOfEval.getErrorValue()
+ + " which is "
+ + errorMeaning, 0, cellName);
+ }
+
+ return evalResults;
+ }
+
+ /**
+ * Returns a Cell as a String value.
+ *
+ * @param cellName
+ * @return
+ */
+ public String getCellAsString(String cellName) {
+ Cell cell = getCell(cellName);
+ return cell.getStringCellValue();
+ }
+
+
+ /**
+ * Returns the value of the Cell as a double.
+ *
+ * @param cellName
+ * @return
+ */
+ public double getCellAsDouble(String cellName) {
+ Cell cell = getCell(cellName);
+ return cell.getNumericCellValue();
+ }
+ /**
+ * Returns a cell reference based on a String in standard Excel format
+ * (SheetName!CellId). This method will create a new cell if the
+ * requested cell isn't initialized yet.
+ *
+ * @param cellName
+ * @return
+ */
+ private Cell getCell(String cellName) {
+ CellReference cellRef = new CellReference(cellName);
+ String sheetName = cellRef.getSheetName();
+ Sheet sheet = workbook.getSheet(sheetName);
+ if(sheet == null) {
+ throw new BuildException("Sheet not found: " + sheetName);
+ }
+
+ int rowIdx = cellRef.getRow();
+ int colIdx = cellRef.getCol();
+ Row row = sheet.getRow(rowIdx);
+
+ if(row == null) {
+ row = sheet.createRow(rowIdx);
+ }
+
+ Cell cell = row.getCell(colIdx);
+
+ if(cell == null) {
+ cell = row.createCell(colIdx);
+ }
+
+ return cell;
+ }
+}
diff --git a/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java b/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java
new file mode 100644
index 000000000..08e7fb3d9
--- /dev/null
+++ b/trunk/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java
@@ -0,0 +1,60 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.excelant.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * This is a factory class maps file names to WorkbookUtil instances. This
+ * helps ExcelAnt be more efficient when being run many times in an Ant build.
+ *
+ * @author Jon Svede (jon [at] loquatic [dot] com)
+ * @author Brian Bush (brian [dot] bush [at] nrel [dot] gov)
+ *
+ */
+public final class ExcelAntWorkbookUtilFactory {
+
+ private static Map workbookUtilMap;
+
+ private ExcelAntWorkbookUtilFactory() {
+ }
+
+ /**
+ * Using the fileName, check the internal map to see if an instance
+ * of the WorkbookUtil exists. If not, then add an instance to the map.
+ *
+ * @param fileName The filename to use as key to look for the ExcelAntWorkbookUtil.
+ * @return An instance of ExcelAntWorkbookUtil associated with the filename or
+ * a freshly instantiated one if none did exist before.
+ */
+ public static ExcelAntWorkbookUtil getInstance(String fileName) {
+ if(workbookUtilMap == null) {
+ workbookUtilMap = new HashMap();
+ }
+
+ if(workbookUtilMap.containsKey(fileName)) {
+ return workbookUtilMap.get(fileName);
+ }
+
+ ExcelAntWorkbookUtil wbu = new ExcelAntWorkbookUtil(fileName);
+ workbookUtilMap.put(fileName, wbu);
+ return wbu;
+ }
+}
diff --git a/trunk/src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml b/trunk/src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml
new file mode 100644
index 000000000..af13cc649
--- /dev/null
+++ b/trunk/src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgageFunction.java b/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgageFunction.java
new file mode 100644
index 000000000..83ac61e4c
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgageFunction.java
@@ -0,0 +1,95 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.formula;
+
+import org.apache.poi.ss.formula.OperationEvaluationContext ;
+import org.apache.poi.ss.formula.eval.ErrorEval ;
+import org.apache.poi.ss.formula.eval.EvaluationException ;
+import org.apache.poi.ss.formula.eval.NumberEval ;
+import org.apache.poi.ss.formula.eval.OperandResolver ;
+import org.apache.poi.ss.formula.eval.ValueEval ;
+import org.apache.poi.ss.formula.functions.FreeRefFunction ;
+
+/**
+ * A simple user-defined function to calculate principal and interest.
+ *
+ * Used by {@link org.apache.poi.ss.excelant.util.TestExcelAntWorkbookUtil}.
+ *
+ * @author Jon Svede ( jon [at] loquatic [dot] com )
+ * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov )
+ *
+ */
+public class CalculateMortgageFunction implements FreeRefFunction {
+
+ public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) {
+
+ // verify that we have enough data
+ if (args.length != 3) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ // declare doubles for values
+ double principal, rate, years, result;
+ try {
+ // extract values as ValueEval
+ ValueEval v1 = OperandResolver.getSingleValue( args[0],
+ ec.getRowIndex(),
+ ec.getColumnIndex() ) ;
+ ValueEval v2 = OperandResolver.getSingleValue( args[1],
+ ec.getRowIndex(),
+ ec.getColumnIndex() ) ;
+ ValueEval v3 = OperandResolver.getSingleValue( args[2],
+ ec.getRowIndex(),
+ ec.getColumnIndex() ) ;
+
+ // get data as doubles
+ principal = OperandResolver.coerceValueToDouble( v1 ) ;
+ rate = OperandResolver.coerceValueToDouble( v2 ) ;
+ years = OperandResolver.coerceValueToDouble( v3 ) ;
+
+ result = calculateMortgagePayment( principal, rate, years ) ;
+ System.out.println( "Result = " + result ) ;
+
+ checkValue(result);
+
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ return new NumberEval( result ) ;
+ }
+
+ public double calculateMortgagePayment( double p, double r, double y ) {
+ double i = r / 12 ;
+ double n = y * 12 ;
+
+ double principalAndInterest =
+ p * (( i * Math.pow((1 + i),n ) ) / ( Math.pow((1 + i),n) - 1)) ;
+
+ return principalAndInterest ;
+ }
+ /**
+ * Excel does not support infinities and NaNs, rather, it gives a #NUM! error in these cases
+ *
+ * @throws EvaluationException (#NUM!) if result is NaN> or Infinity
+ */
+ private void checkValue(double result) throws EvaluationException {
+ if (Double.isNaN(result) || Double.isInfinite(result)) {
+ throw new EvaluationException(ErrorEval.NUM_ERROR);
+ }
+ }
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java b/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java
new file mode 100644
index 000000000..68e4b6957
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java
@@ -0,0 +1,36 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.formula;
+
+import org.apache.poi.ss.excelant.ExcelAntUserDefinedFunction;
+
+public class ExcelAntUserDefinedFunctionTestHelper extends
+ ExcelAntUserDefinedFunction {
+
+ @Override
+ protected String getFunctionAlias() {
+ // TODO Auto-generated method stub
+ return super.getFunctionAlias();
+ }
+
+ @Override
+ protected String getClassName() {
+ // TODO Auto-generated method stub
+ return super.getClassName();
+ }
+
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java b/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java
new file mode 100644
index 000000000..ad9851ae8
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java
@@ -0,0 +1,51 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.examples.formula;
+
+import junit.framework.TestCase;
+
+public class TestExcelAntUserDefinedFunction extends TestCase {
+
+ private ExcelAntUserDefinedFunctionTestHelper fixture ;
+
+ @Override
+ public void setUp() {
+ fixture = new ExcelAntUserDefinedFunctionTestHelper() ;
+ }
+
+ public void testSetClassName() {
+ String className = "simple.class.name" ;
+
+ fixture.setClassName( className ) ;
+ String value = fixture.getClassName() ;
+
+ assertNotNull( value ) ;
+ assertEquals( className, value ) ;
+ }
+
+ public void testSetFunction() {
+ String functionAlias = "alias" ;
+
+ fixture.setFunctionAlias( functionAlias ) ;
+
+ String alias = fixture.getFunctionAlias() ;
+
+ assertNotNull( alias ) ;
+ assertEquals( functionAlias, alias ) ;
+ }
+
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java
new file mode 100644
index 000000000..41b3b04fd
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java
@@ -0,0 +1,596 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.poi.ss.excelant;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.net.URL;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.BuildListener;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectHelper;
+
+/**
+ * A BuildFileTest is a TestCase which executes targets from an Ant buildfile
+ * for testing.
+ *
+ * This class provides a number of utility methods for particular build file
+ * tests which extend this class.
+ *
+ * @see
+ * http://svn.apache.org/repos/asf/ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java
+ */
+public abstract class BuildFileTest extends TestCase {
+
+ protected Project project;
+
+ private StringBuffer logBuffer;
+ private StringBuffer fullLogBuffer;
+ private StringBuffer outBuffer;
+ private StringBuffer errBuffer;
+ private BuildException buildException;
+
+ /**
+ * Default constructor for the BuildFileTest object.
+ */
+ public BuildFileTest() {
+ super();
+ }
+
+ /**
+ * Constructor for the BuildFileTest object.
+ *
+ * @param name string to pass up to TestCase constructor
+ */
+ public BuildFileTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Automatically calls the target called "tearDown"
+ * from the build file tested if it exits.
+ *
+ * This allows to use Ant tasks directly in the build file
+ * to clean up after each test. Note that no "setUp" target
+ * is automatically called, since it's trivial to have a
+ * test target depend on it.
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ if (project == null) {
+ /*
+ * Maybe the BuildFileTest was subclassed and there is
+ * no initialized project. So we could avoid getting a
+ * NPE.
+ * If there is an initialized project getTargets() does
+ * not return null as it is initialized by an empty
+ * HashSet.
+ */
+ return;
+ }
+ final String tearDown = "tearDown";
+ if (project.getTargets().containsKey(tearDown)) {
+ project.executeTarget(tearDown);
+ }
+ }
+
+ /**
+ * run a target, expect for any build exception
+ *
+ * @param target target to run
+ * @param cause information string to reader of report
+ */
+ public void expectBuildException(String target, String cause) {
+ expectSpecificBuildException(target, cause, null);
+ }
+
+ /**
+ * Assert that only the given message has been logged with a
+ * priority <= INFO when running the given target.
+ */
+ public void expectLog(String target, String log) {
+ executeTarget(target);
+ String realLog = getLog();
+ assertEquals(log, realLog);
+ }
+
+ /**
+ * Assert that the given substring is in the log messages.
+ */
+ public void assertLogContaining(String substring) {
+ String realLog = getLog();
+ assertTrue("expecting log to contain \"" + substring + "\" log was \""
+ + realLog + "\"",
+ realLog.indexOf(substring) >= 0);
+ }
+
+ /**
+ * Assert that the given substring is not in the log messages.
+ */
+ public void assertLogNotContaining(String substring) {
+ String realLog = getLog();
+ assertFalse("didn't expect log to contain \"" + substring + "\" log was \""
+ + realLog + "\"",
+ realLog.indexOf(substring) >= 0);
+ }
+
+ /**
+ * Assert that the given substring is in the output messages.
+ *
+ * @since Ant1.7
+ */
+ public void assertOutputContaining(String substring) {
+ assertOutputContaining(null, substring);
+ }
+
+ /**
+ * Assert that the given substring is in the output messages.
+ *
+ * @param message Print this message if the test fails. Defaults to
+ * a meaningful text if null is passed.
+ * @since Ant1.7
+ */
+ public void assertOutputContaining(String message, String substring) {
+ String realOutput = getOutput();
+ String realMessage = (message != null)
+ ? message
+ : "expecting output to contain \"" + substring + "\" output was \"" + realOutput + "\"";
+ assertTrue(realMessage, realOutput.indexOf(substring) >= 0);
+ }
+
+ /**
+ * Assert that the given substring is not in the output messages.
+ *
+ * @param message Print this message if the test fails. Defaults to
+ * a meaningful text if null is passed.
+ * @since Ant1.7
+ */
+ public void assertOutputNotContaining(String message, String substring) {
+ String realOutput = getOutput();
+ String realMessage = (message != null)
+ ? message
+ : "expecting output to not contain \"" + substring + "\" output was \"" + realOutput + "\"";
+ assertFalse(realMessage, realOutput.indexOf(substring) >= 0);
+ }
+
+ /**
+ * Assert that the given message has been logged with a priority <= INFO when running the
+ * given target.
+ */
+ public void expectLogContaining(String target, String log) {
+ executeTarget(target);
+ assertLogContaining(log);
+ }
+
+ /**
+ * Assert that the given message has not been logged with a
+ * priority <= INFO when running the given target.
+ */
+ public void expectLogNotContaining(String target, String log) {
+ executeTarget(target);
+ assertLogNotContaining(log);
+ }
+
+ /**
+ * Gets the log the BuildFileTest object.
+ * Only valid if configureProject() has been called.
+ *
+ * @return The log value
+ * @pre logBuffer!=null
+ */
+ public String getLog() {
+ return logBuffer.toString();
+ }
+
+ /**
+ * Assert that the given message has been logged with a priority
+ * >= VERBOSE when running the given target.
+ */
+ public void expectDebuglog(String target, String log) {
+ executeTarget(target);
+ String realLog = getFullLog();
+ assertEquals(log, realLog);
+ }
+
+ /**
+ * Assert that the given substring is in the log messages.
+ */
+ public void assertDebuglogContaining(String substring) {
+ String realLog = getFullLog();
+ assertTrue("expecting debug log to contain \"" + substring
+ + "\" log was \""
+ + realLog + "\"",
+ realLog.indexOf(substring) >= 0);
+ }
+
+ /**
+ * Gets the log the BuildFileTest object.
+ *
+ * Only valid if configureProject() has been called.
+ *
+ * @return The log value
+ * @pre fullLogBuffer!=null
+ */
+ public String getFullLog() {
+ return fullLogBuffer.toString();
+ }
+
+ /**
+ * execute the target, verify output matches expectations
+ *
+ * @param target target to execute
+ * @param output output to look for
+ */
+ public void expectOutput(String target, String output) {
+ executeTarget(target);
+ String realOutput = getOutput();
+ assertEquals(output, realOutput.trim());
+ }
+
+ /**
+ * Executes the target, verify output matches expectations
+ * and that we got the named error at the end
+ *
+ * @param target target to execute
+ * @param output output to look for
+ * @param error Description of Parameter
+ */
+ public void expectOutputAndError(String target, String output, String error) {
+ executeTarget(target);
+ String realOutput = getOutput();
+ assertEquals(output, realOutput);
+ String realError = getError();
+ assertEquals(error, realError);
+ }
+
+ public String getOutput() {
+ return cleanBuffer(outBuffer);
+ }
+
+ public String getError() {
+ return cleanBuffer(errBuffer);
+ }
+
+ public BuildException getBuildException() {
+ return buildException;
+ }
+
+ private String cleanBuffer(StringBuffer buffer) {
+ StringBuffer cleanedBuffer = new StringBuffer();
+ for (int i = 0; i < buffer.length(); i++) {
+ char ch = buffer.charAt(i);
+ if (ch != '\r') {
+ cleanedBuffer.append(ch);
+ }
+ }
+ return cleanedBuffer.toString();
+ }
+
+ /**
+ * Sets up to run the named project
+ *
+ * @param filename name of project file to run
+ */
+ public void configureProject(String filename) throws BuildException {
+ configureProject(filename, Project.MSG_DEBUG);
+ }
+
+ /**
+ * Sets up to run the named project
+ *
+ * @param filename name of project file to run
+ */
+ public void configureProject(String filename, int logLevel)
+ throws BuildException {
+ logBuffer = new StringBuffer();
+ fullLogBuffer = new StringBuffer();
+ project = new Project();
+ project.init();
+ project.setNewProperty("data.dir.name", getDataDir());
+ File antFile = new File(System.getProperty("root"), filename);
+ project.setUserProperty("ant.file", antFile.getAbsolutePath());
+ project.addBuildListener(new AntTestListener(logLevel));
+ ProjectHelper.configureProject(project, antFile);
+ }
+
+ /**
+ * Executes a target we have set up
+ *
+ * @param targetName target to run
+ * @pre configureProject has been called
+ */
+ public void executeTarget(String targetName) {
+ PrintStream sysOut = System.out;
+ PrintStream sysErr = System.err;
+ try {
+ sysOut.flush();
+ sysErr.flush();
+ outBuffer = new StringBuffer();
+ PrintStream out = new PrintStream(new AntOutputStream(outBuffer));
+ System.setOut(out);
+ errBuffer = new StringBuffer();
+ PrintStream err = new PrintStream(new AntOutputStream(errBuffer));
+ System.setErr(err);
+ logBuffer = new StringBuffer();
+ fullLogBuffer = new StringBuffer();
+ buildException = null;
+ project.executeTarget(targetName);
+ } finally {
+ System.setOut(sysOut);
+ System.setErr(sysErr);
+ }
+
+ }
+
+ /**
+ * Get the project which has been configured for a test.
+ *
+ * @return the Project instance for this test.
+ */
+ public Project getProject() {
+ return project;
+ }
+
+ /**
+ * Gets the directory of the project.
+ *
+ * @return the base dir of the project
+ */
+ public File getProjectDir() {
+ return project.getBaseDir();
+ }
+
+ /**
+ * Runs a target, wait for a build exception.
+ *
+ * @param target target to run
+ * @param cause information string to reader of report
+ * @param msg the message value of the build exception we are waiting
+ * for set to null for any build exception to be valid
+ */
+ public void expectSpecificBuildException(String target, String cause, String msg) {
+ try {
+ executeTarget(target);
+ } catch (org.apache.tools.ant.BuildException ex) {
+ buildException = ex;
+ if ((null != msg) && (!ex.getMessage().equals(msg))) {
+ fail("Should throw BuildException because '" + cause
+ + "' with message '" + msg
+ + "' (actual message '" + ex.getMessage() + "' instead)");
+ }
+ return;
+ }
+ fail("Should throw BuildException because: " + cause);
+ }
+
+ /**
+ * run a target, expect an exception string
+ * containing the substring we look for (case sensitive match)
+ *
+ * @param target target to run
+ * @param cause information string to reader of report
+ * @param contains substring of the build exception to look for
+ */
+ public void expectBuildExceptionContaining(String target, String cause, String contains) {
+ try {
+ executeTarget(target);
+ } catch (org.apache.tools.ant.BuildException ex) {
+ buildException = ex;
+ if ((null != contains) && (ex.getMessage().indexOf(contains) == -1)) {
+ fail("Should throw BuildException because '" + cause + "' with message containing '" + contains + "' (actual message '" + ex.getMessage() + "' instead)");
+ }
+ return;
+ }
+ fail("Should throw BuildException because: " + cause);
+ }
+
+ /**
+ * call a target, verify property is as expected
+ *
+ * @param target build file target
+ * @param property property name
+ * @param value expected value
+ */
+ public void expectPropertySet(String target, String property, String value) {
+ executeTarget(target);
+ assertPropertyEquals(property, value);
+ }
+
+ /**
+ * assert that a property equals a value; comparison is case sensitive.
+ *
+ * @param property property name
+ * @param value expected value
+ */
+ public void assertPropertyEquals(String property, String value) {
+ String result = project.getProperty(property);
+ assertEquals("property " + property, value, result);
+ }
+
+ /**
+ * assert that a property equals "true".
+ *
+ * @param property property name
+ */
+ public void assertPropertySet(String property) {
+ assertPropertyEquals(property, "true");
+ }
+
+ /**
+ * assert that a property is null.
+ *
+ * @param property property name
+ */
+ public void assertPropertyUnset(String property) {
+ String result = project.getProperty(property);
+ if (result != null) {
+ fail("Expected property " + property
+ + " to be unset, but it is set to the value: " + result);
+ }
+ }
+
+ /**
+ * call a target, verify named property is "true".
+ *
+ * @param target build file target
+ * @param property property name
+ */
+ public void expectPropertySet(String target, String property) {
+ expectPropertySet(target, property, "true");
+ }
+
+ /**
+ * Call a target, verify property is null.
+ *
+ * @param target build file target
+ * @param property property name
+ */
+ public void expectPropertyUnset(String target, String property) {
+ expectPropertySet(target, property, null);
+ }
+
+ /**
+ * Retrieve a resource from the caller classloader to avoid
+ * assuming a vm working directory. The resource path must be
+ * relative to the package name or absolute from the root path.
+ *
+ * @param resource the resource to retrieve its url.
+ * @throws junit.framework.AssertionFailedError
+ * if the resource is not found.
+ */
+ public URL getResource(String resource) {
+ URL url = getClass().getResource(resource);
+ assertNotNull("Could not find resource :" + resource, url);
+ return url;
+ }
+
+ public static String getDataDir() {
+ String dataDirName = System.getProperty(POIDataSamples.TEST_PROPERTY);
+ return dataDirName == null ? "test-data" : dataDirName;
+ }
+
+ /**
+ * an output stream which saves stuff to our buffer.
+ */
+ protected static class AntOutputStream extends java.io.OutputStream {
+ private StringBuffer buffer;
+
+ public AntOutputStream(StringBuffer buffer) {
+ this.buffer = buffer;
+ }
+
+ public void write(int b) {
+ buffer.append((char) b);
+ }
+ }
+
+ /**
+ * Our own personal build listener.
+ */
+ private class AntTestListener implements BuildListener {
+ private int logLevel;
+
+ /**
+ * Constructs a test listener which will ignore log events
+ * above the given level.
+ */
+ public AntTestListener(int logLevel) {
+ this.logLevel = logLevel;
+ }
+
+ /**
+ * Fired before any targets are started.
+ */
+ public void buildStarted(BuildEvent event) {
+ }
+
+ /**
+ * Fired after the last target has finished. This event
+ * will still be thrown if an error occurred during the build.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void buildFinished(BuildEvent event) {
+ }
+
+ /**
+ * Fired when a target is started.
+ *
+ * @see BuildEvent#getTarget()
+ */
+ public void targetStarted(BuildEvent event) {
+ //System.out.println("targetStarted " + event.getTarget().getName());
+ }
+
+ /**
+ * Fired when a target has finished. This event will
+ * still be thrown if an error occurred during the build.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void targetFinished(BuildEvent event) {
+ //System.out.println("targetFinished " + event.getTarget().getName());
+ }
+
+ /**
+ * Fired when a task is started.
+ *
+ * @see BuildEvent#getTask()
+ */
+ public void taskStarted(BuildEvent event) {
+ //System.out.println("taskStarted " + event.getTask().getTaskName());
+ }
+
+ /**
+ * Fired when a task has finished. This event will still
+ * be throw if an error occurred during the build.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void taskFinished(BuildEvent event) {
+ //System.out.println("taskFinished " + event.getTask().getTaskName());
+ }
+
+ /**
+ * Fired whenever a message is logged.
+ *
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public void messageLogged(BuildEvent event) {
+ if (event.getPriority() > logLevel) {
+ // ignore event
+ return;
+ }
+
+ if (event.getPriority() == Project.MSG_INFO ||
+ event.getPriority() == Project.MSG_WARN ||
+ event.getPriority() == Project.MSG_ERR) {
+ logBuffer.append(event.getMessage());
+ }
+ fullLogBuffer.append(event.getMessage());
+ }
+ }
+
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/MockExcelAntWorkbookHandler.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/MockExcelAntWorkbookHandler.java
new file mode 100644
index 000000000..ffdb2f2fa
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/MockExcelAntWorkbookHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.poi.ss.excelant;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.poi.ss.usermodel.Workbook;
+
+public class MockExcelAntWorkbookHandler implements IExcelAntWorkbookHandler {
+ public static boolean executed = false;
+ public static Workbook workbook = null;
+
+
+ public void setWorkbook(Workbook workbook) {
+ MockExcelAntWorkbookHandler.workbook = workbook;
+ }
+
+ public void execute() {
+ executed = true;
+ assertNotNull(workbook);
+ }
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java
new file mode 100644
index 000000000..df1993be0
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.poi.ss.excelant;
+
+
+/**
+ * JUnit test for the ExcelAnt tasks.
+ * Leverages Ant's test framework.
+ *
+ * @see
+ * http://svn.apache.org/repos/asf/ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java
+ */
+public class TestBuildFile extends BuildFileTest {
+
+ @Override
+ public void setUp() {
+ configureProject(BuildFileTest.getDataDir() + "/../src/excelant/testcases/org/apache/poi/ss/excelant/tests.xml");
+ }
+
+ public void testMissingFilename() {
+ expectSpecificBuildException("test-nofile", "required argument not specified",
+ "fileName attribute must be set!");
+ }
+
+ public void testFileNotFound() {
+ expectSpecificBuildException("test-filenotfound", "required argument not specified",
+ "Cannot load file invalid.xls. Make sure the path and file permissions are correct.");
+ }
+
+ public void testEvaluate() {
+ executeTarget("test-evaluate");
+ assertLogContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4.");
+ }
+
+ public void testEvaluateNoDetails() {
+ executeTarget("test-evaluate-nodetails");
+ assertLogContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogNotContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4.");
+ }
+
+ public void testPrecision() {
+ executeTarget("test-precision");
+
+ assertLogContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4. " +
+ "It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-4");
+ assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4. " +
+ "It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-5");
+ assertLogContaining("Failed to evaluate cell 'MortgageCalculator'!$B$4. " +
+ "It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-10 was expected.");
+ assertLogContaining("2/3 tests passed");
+ }
+
+ public void testPrecisionFail() {
+ expectSpecificBuildException("test-precision-fails", "precision not matched",
+ "\tFailed to evaluate cell 'MortgageCalculator'!$B$4. It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-10 was expected.");
+ }
+
+ public void testPassOnError() {
+ executeTarget("test-passonerror");
+ }
+
+ public void testFailOnError() {
+ expectBuildException("test-failonerror", "fail on error");
+ assertLogContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogNotContaining("failed because 1 of 0 evaluations failed to evaluate correctly. Failed to evaluate cell 'MortageCalculatorFunction'!$D$3");
+ }
+
+ public void testFailOnErrorNoDetails() {
+ expectBuildException("test-failonerror-nodetails", "fail on error");
+ assertLogNotContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogNotContaining("failed because 1 of 0 evaluations failed to evaluate correctly. Failed to evaluate cell 'MortageCalculatorFunction'!$D$3");
+ }
+
+ public void testUdf() {
+ executeTarget("test-udf");
+ assertLogContaining("1/1 tests passed");
+ }
+
+ public void testSetText() {
+ executeTarget("test-settext");
+ assertLogContaining("1/1 tests passed");
+ }
+
+ public void testAddHandler() {
+ executeTarget("test-addhandler");
+ assertLogContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4.");
+
+ assertNotNull("The workbook should have been passed to the handler", MockExcelAntWorkbookHandler.workbook);
+ assertTrue("The handler should have been executed", MockExcelAntWorkbookHandler.executed);
+ }
+
+ public void testAddHandlerWrongClass() {
+ executeTarget("test-addhandler-wrongclass");
+ assertLogContaining("Using input file: " + BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls");
+ assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4.");
+ }
+
+ public void testAddHandlerFails() {
+ expectSpecificBuildException("test-addhandler-fails", "NullPointException", null);
+ }
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java
new file mode 100644
index 000000000..0c6f9cbf8
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java
@@ -0,0 +1,48 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant;
+
+import junit.framework.TestCase;
+
+public class TestExcelAntPrecision extends TestCase {
+
+ private ExcelAntPrecision fixture ;
+
+ @Override
+ public void setUp() {
+ fixture = new ExcelAntPrecision() ;
+ }
+
+ @Override
+ public void tearDown() {
+ fixture = null ;
+ }
+
+ public void testVerifyPrecision() {
+
+ double value = 1.0E-1 ;
+
+ fixture.setValue( value ) ;
+
+ double result = fixture.getValue() ;
+
+ assertTrue( result > 0 ) ;
+
+ assertEquals( value, result, 0.0 ) ;
+ }
+
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java
new file mode 100644
index 000000000..b59c886ae
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java
@@ -0,0 +1,63 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtilFactory;
+
+public class TestExcelAntSet extends TestCase {
+
+
+ // This is abstract in nature, so we'll use a
+ // concrete instance to test the set methods.
+ private ExcelAntSet fixture ;
+
+ private static final String mortgageCalculatorFileName =
+ BuildFileTest.getDataDir() + "/spreadsheet/mortgage-calculation.xls" ;
+
+ @Override
+ public void setUp() {
+ fixture = new ExcelAntSetDoubleCell() ;
+ }
+
+ @Override
+ public void tearDown() {
+ fixture = null ;
+ }
+
+ public void testSetter() {
+ String cell = "simpleCellRef!$F$1" ;
+
+ fixture.setCell( cell ) ;
+
+ String cellStr = fixture.getCell() ;
+
+ assertNotNull( cellStr ) ;
+ assertEquals( cell, cellStr ) ;
+ }
+
+ public void testSetWorkbookUtil() {
+ ExcelAntWorkbookUtil util = ExcelAntWorkbookUtilFactory.getInstance(
+ mortgageCalculatorFileName ) ;
+
+ assertNotNull( util ) ;
+
+ fixture.setWorkbookUtil( util ) ;
+ }
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java
new file mode 100644
index 000000000..3d5b11ef4
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java
@@ -0,0 +1,66 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil;
+import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtilFactory;
+
+public class TestExcelAntSetDoubleCell extends TestCase {
+
+ private ExcelAntSetDoubleCell fixture ;
+
+ private ExcelAntWorkbookUtil util ;
+
+ private static final String mortgageCalculatorFileName =
+ BuildFileTest.getDataDir() + "/spreadsheet/mortgage-calculation.xls" ;
+
+ @Override
+ public void setUp() {
+ fixture = new ExcelAntSetDoubleCell() ;
+ util = ExcelAntWorkbookUtilFactory.getInstance(
+ mortgageCalculatorFileName ) ;
+ fixture.setWorkbookUtil( util ) ;
+ }
+
+ @Override
+ public void tearDown() {
+ fixture = null ;
+ }
+
+ public void testSetDouble() {
+ String cellId = "'Sheet3'!$A$1" ;
+ double testValue = 1.1 ;
+
+ fixture.setCell( cellId ) ;
+ fixture.setValue( testValue ) ;
+
+ double value = fixture.getCellValue() ;
+
+ assertTrue( value > 0 ) ;
+ assertEquals( testValue, value, 0.0 ) ;
+
+ fixture.execute() ;
+
+ double setValue = util.getCellAsDouble( cellId ) ;
+
+ assertEquals( setValue, testValue, 0.0 ) ;
+ }
+
+
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/tests.xml b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/tests.xml
new file mode 100644
index 000000000..d387aab36
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/tests.xml
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java
new file mode 100644
index 000000000..16189f1d5
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java
@@ -0,0 +1,50 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant.util;
+
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Workbook;
+
+/**
+ * A helper class to allow testing of protected methods and constructors.
+ *
+ * @author jsvede
+ *
+ */
+public class ExcelAntWorkbookUtilTestHelper extends ExcelAntWorkbookUtil {
+
+ public ExcelAntWorkbookUtilTestHelper(String fName) {
+ super(fName);
+ }
+
+ public ExcelAntWorkbookUtilTestHelper(Workbook wb) {
+ super(wb);
+ }
+
+ @Override
+ public UDFFinder getFunctions() {
+ return super.getFunctions();
+ }
+
+ @Override
+ public FormulaEvaluator getEvaluator(String excelFileName) {
+ return super.getEvaluator(excelFileName);
+ }
+
+
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java
new file mode 100644
index 000000000..9c9213e52
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java
@@ -0,0 +1,76 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestExcelAntEvaluationResult {
+ private ExcelAntEvaluationResult fixture;
+
+ private boolean completedWithError = false;
+ private boolean passed = false;
+ private double retValue = 1.1;
+ private String errMessage = "error message";
+ private double delta = 2.2;
+ private String cellId = "testCell!$F$1";
+
+ @Before
+ public void setUp() {
+ fixture = new ExcelAntEvaluationResult(completedWithError,
+ passed,
+ retValue,
+ errMessage,
+ delta,
+ cellId);
+ }
+
+ @After
+ public void tearDown() {
+ fixture = null;
+ }
+
+ @Test
+ public void testCompletedWithErrorMessage() {
+ String errMsg = fixture.getErrorMessage();
+ assertNotNull(errMsg);
+ assertEquals(errMsg, errMessage);
+ }
+
+ @Test
+ public void testPassed() {
+ boolean passedValue = fixture.didTestPass();
+ assertEquals(passedValue, passed);
+ }
+
+ @Test
+ public void testDelta() {
+ double deltaValue = fixture.getDelta();
+ assertEquals(deltaValue, delta, 0.0);
+ }
+
+ @Test
+ public void testCellId() {
+ String cellIdValue = fixture.getCellName();
+ assertNotNull(cellIdValue);
+ assertEquals(cellIdValue, cellId);
+ }
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java
new file mode 100644
index 000000000..bf68c2481
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java
@@ -0,0 +1,358 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.examples.formula.CalculateMortgageFunction;
+import org.apache.poi.ss.excelant.BuildFileTest;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.tools.ant.BuildException;
+
+import junit.framework.TestCase;
+
+public class TestExcelAntWorkbookUtil extends TestCase {
+
+ private static final String mortgageCalculatorFileName =
+ BuildFileTest.getDataDir() + "/spreadsheet/excelant.xls" ;
+
+ private ExcelAntWorkbookUtilTestHelper fixture ;
+
+
+ @Override
+ public void tearDown() {
+ fixture = null ;
+ }
+
+ public void testStringConstructor() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ assertNotNull(fixture);
+ }
+
+ public void testLoadNotExistingFile() {
+ try {
+ assertNotNull(new ExcelAntWorkbookUtilTestHelper(
+ "notexistingFile" ));
+ fail("Should catch exception here");
+ } catch (BuildException e) {
+ assertTrue(e.getMessage().contains("notexistingFile"));
+ }
+ }
+
+ public void testWorkbookConstructor() throws InvalidFormatException, IOException {
+ File workbookFile = new File(mortgageCalculatorFileName);
+ FileInputStream fis = new FileInputStream(workbookFile);
+ Workbook workbook = WorkbookFactory.create(fis);
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(workbook);
+
+ assertNotNull(fixture);
+ }
+
+ public void testAddFunction() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ assertNotNull(fixture);
+
+ fixture.addFunction("h2_ZFactor", new CalculateMortgageFunction());
+
+ UDFFinder functions = fixture.getFunctions();
+
+ assertNotNull(functions);
+ assertNotNull(functions.findFunction("h2_ZFactor"));
+ }
+
+ public void testAddFunctionClassName() throws Exception {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ assertNotNull(fixture);
+
+ fixture.addFunction("h2_ZFactor", CalculateMortgageFunction.class.getName());
+
+ UDFFinder functions = fixture.getFunctions();
+
+ assertNotNull(functions);
+ assertNotNull(functions.findFunction("h2_ZFactor"));
+ }
+
+ public void testAddFunctionInvalidClassName() throws Exception {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ assertNotNull(fixture);
+
+ fixture.addFunction("h2_ZFactor", String.class.getName());
+
+ UDFFinder functions = fixture.getFunctions();
+
+ assertNotNull(functions);
+ assertNull(functions.findFunction("h2_ZFactor"));
+ }
+
+ public void testGetWorkbook() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ assertNotNull(fixture);
+
+ Workbook workbook = fixture.getWorkbook();
+
+ assertNotNull(workbook);
+ }
+
+ public void testFileName() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ assertNotNull(fixture);
+
+ String fileName = fixture.getFileName();
+
+ assertNotNull(fileName);
+
+ assertEquals(mortgageCalculatorFileName, fileName);
+
+ }
+
+ public void testGetEvaluator() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ FormulaEvaluator evaluator = fixture.getEvaluator(
+ mortgageCalculatorFileName);
+
+ assertNotNull(evaluator);
+ }
+
+ public void testGetEvaluatorWithUDF() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ fixture.addFunction("h2_ZFactor", new CalculateMortgageFunction());
+
+ FormulaEvaluator evaluator = fixture.getEvaluator(
+ mortgageCalculatorFileName);
+
+ assertNotNull(evaluator);
+ }
+
+ public void testGetEvaluatorXLSX() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ BuildFileTest.getDataDir() + "/spreadsheet/sample.xlsx");
+
+ FormulaEvaluator evaluator = fixture.getEvaluator(
+ BuildFileTest.getDataDir() + "/spreadsheet/sample.xlsx");
+
+ assertNotNull(evaluator);
+ }
+
+ public void testGetEvaluatorXLSXWithFunction() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ BuildFileTest.getDataDir() + "/spreadsheet/sample.xlsx");
+
+ fixture.addFunction("h2_ZFactor", new CalculateMortgageFunction());
+
+ FormulaEvaluator evaluator = fixture.getEvaluator(
+ BuildFileTest.getDataDir() + "/spreadsheet/sample.xlsx");
+
+ assertNotNull(evaluator);
+ }
+
+ public void testEvaluateCell() {
+ String cell = "'MortgageCalculator'!B4" ;
+ double expectedValue = 790.79 ;
+ double precision = 0.1 ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ ExcelAntEvaluationResult result = fixture.evaluateCell(cell,
+ expectedValue,
+ precision);
+
+ //System.out.println(result);
+ assertTrue("Had:" + result.toString(), result.toString().contains("evaluationCompletedWithError=false"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("returnValue=790.79"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("cellName='MortgageCalculator'!B4"));
+ assertFalse(result.toString().contains("#N/A"));
+
+ assertFalse(result.evaluationCompleteWithError());
+ assertTrue(result.didTestPass());
+ }
+
+ public void testEvaluateCellFailedPrecision() {
+ String cell = "'MortgageCalculator'!B4" ;
+ double expectedValue = 790.79 ;
+ double precision = 0.0000000000001 ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ ExcelAntEvaluationResult result = fixture.evaluateCell(cell,
+ expectedValue,
+ precision);
+
+ //System.out.println(result);
+ assertTrue("Had:" + result.toString(), result.toString().contains("evaluationCompletedWithError=false"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("returnValue=790.79"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("cellName='MortgageCalculator'!B4"));
+ assertFalse("Should not see an error, but had:" + result.toString(), result.toString().contains("#"));
+
+ assertFalse(result.evaluationCompleteWithError());
+ assertFalse(result.didTestPass());
+ }
+
+ public void testEvaluateCellWithError() {
+ String cell = "'ErrorCell'!A1" ;
+ double expectedValue = 790.79 ;
+ double precision = 0.1 ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ ExcelAntEvaluationResult result = fixture.evaluateCell(cell,
+ expectedValue,
+ precision);
+
+ System.out.println(result);
+ assertTrue("Had:" + result.toString(), result.toString().contains("evaluationCompletedWithError=true"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("returnValue=0.0"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("cellName='ErrorCell'!A1"));
+ assertTrue("Had:" + result.toString(), result.toString().contains("#N/A"));
+
+ assertTrue(result.evaluationCompleteWithError());
+ assertFalse(result.didTestPass());
+ }
+
+ public void testGetSheets() {
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ List sheets = fixture.getSheets();
+
+ assertNotNull(sheets);
+ assertEquals(sheets.size(), 3);
+ }
+
+ public void testSetString() {
+ String cell = "'MortgageCalculator'!C14" ;
+ String cellValue = "testString" ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ fixture.setStringValue(cell, cellValue);
+
+ String value = fixture.getCellAsString(cell);
+
+ assertNotNull(value);
+ assertEquals(cellValue, value);
+ }
+
+ public void testSetNotExistingSheet() {
+ String cell = "'NotexistingSheet'!C14" ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+ try {
+ fixture.setStringValue(cell, "some");
+ fail("Should catch exception here");
+ } catch (BuildException e) {
+ assertTrue(e.getMessage().contains("NotexistingSheet"));
+ }
+ }
+
+ public void testSetFormula() {
+ String cell = "'MortgageCalculator'!C14" ;
+ String cellValue = "SUM(B14:B18)" ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ fixture.setFormulaValue(cell, cellValue);
+
+ double value = fixture.getCellAsDouble(cell);
+
+ assertNotNull(value);
+ assertEquals(0.0, value);
+ }
+
+ public void testSetDoubleValue() {
+ String cell = "'MortgageCalculator'!C14" ;
+ double cellValue = 1.2;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ fixture.setDoubleValue(cell, cellValue);
+
+ Double value = fixture.getCellAsDouble(cell);
+
+ assertNotNull(value);
+ assertEquals(cellValue, value);
+ }
+
+ public void testSetDate() {
+ String cell = "'MortgageCalculator'!C14" ;
+ Date cellValue = new Date();
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ fixture.setDateValue(cell, cellValue);
+
+ double value = fixture.getCellAsDouble(cell);
+
+ assertNotNull(value);
+ assertEquals(DateUtil.getExcelDate(cellValue, false), value);
+ }
+
+ public void testGetNonexistingString() {
+ String cell = "'MortgageCalculator'!C33" ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ String value = fixture.getCellAsString(cell);
+
+ assertEquals("", value);
+ }
+
+ public void testGetNonexistingDouble() {
+ String cell = "'MortgageCalculator'!C33" ;
+
+ fixture = new ExcelAntWorkbookUtilTestHelper(
+ mortgageCalculatorFileName);
+
+ double value = fixture.getCellAsDouble(cell);
+
+ assertEquals(0.0, value);
+ }
+}
diff --git a/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java
new file mode 100644
index 000000000..b69449a5f
--- /dev/null
+++ b/trunk/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ss.excelant.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.poi.ss.excelant.BuildFileTest;
+import org.junit.Test;
+
+
+/**
+ * Tests for the ExcelAntWorbookUtilFactory.
+ *
+ * @author Jon Svede (jon [at] loquatic [dot] com)
+ * @author Brian Bush (brian [dot] bush [at] nrel [dot] gov)
+ *
+ */
+public class TestExcelAntWorkbookUtilFactory {
+
+ private static final String mortgageCalculatorWorkbookFile =
+ BuildFileTest.getDataDir() + "/spreadsheet/mortgage-calculation.xls" ;
+
+
+ /**
+ * Simple test to determine if the factory properly returns an non-null
+ * instance of the ExcelAntWorkbookUtil class.
+ */
+ @Test
+ public void testGetNewWorkbookUtilInstance() {
+ ExcelAntWorkbookUtil util = ExcelAntWorkbookUtilFactory.getInstance(
+ mortgageCalculatorWorkbookFile) ;
+
+ assertNotNull(util) ;
+ }
+
+
+ /**
+ * Test whether or not the factory will properly return the same reference
+ * to an ExcelAnt WorkbookUtil when two different Strings, that point to
+ * the same resource, are passed in.
+ */
+ @Test
+ public void testVerifyEquivalence() {
+ String sameFileName = BuildFileTest.getDataDir() + "/spreadsheet/mortgage-calculation.xls" ;
+
+ ExcelAntWorkbookUtil util = ExcelAntWorkbookUtilFactory.getInstance(
+ mortgageCalculatorWorkbookFile) ;
+
+ ExcelAntWorkbookUtil util2 = ExcelAntWorkbookUtilFactory.getInstance(
+ sameFileName) ;
+
+ assertNotNull(util) ;
+ assertNotNull(util2) ;
+
+ assertEquals(util, util2) ;
+ }
+}
diff --git a/trunk/src/integrationtest/build.xml b/trunk/src/integrationtest/build.xml
new file mode 100644
index 000000000..8409e5354
--- /dev/null
+++ b/trunk/src/integrationtest/build.xml
@@ -0,0 +1,157 @@
+
+
+
+
+ Test-Ant file which verifies that the Apache POI distribution build sources can be compiled successfully.
+
+Before running this, you should execute the "assemble" target in the main build.xml to have the packaged files created correctly.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trunk/src/integrationtest/commons-logging.properties b/trunk/src/integrationtest/commons-logging.properties
new file mode 100644
index 000000000..45db0a7a8
--- /dev/null
+++ b/trunk/src/integrationtest/commons-logging.properties
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+log4j.configuration=log4j.properties
\ No newline at end of file
diff --git a/trunk/src/integrationtest/log4j.properties b/trunk/src/integrationtest/log4j.properties
new file mode 100644
index 000000000..f2aea6851
--- /dev/null
+++ b/trunk/src/integrationtest/log4j.properties
@@ -0,0 +1,40 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+log4j.rootLogger=info, R
+
+log4j.appender.R=org.apache.log4j.RollingFileAppender
+log4j.appender.R.File=build/test-integration.log
+
+log4j.appender.R.MaxFileSize=1000KB
+# Keep one backup file
+log4j.appender.R.MaxBackupIndex=5
+
+log4j.appender.R.layout=org.apache.log4j.PatternLayout
+log4j.appender.R.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
+
+log4j.logger.org.apache.poi.poifs.nio.FileBackedDataSource=ERROR
+log4j.logger.org.apache.poi.hdgf.chunks.Chunk=FATAL
+log4j.logger.org.apache.poi.hpsf.CodePageString=ERROR
+log4j.logger.org.apache.poi.hdgf.chunks.ChunkFactory=ERROR
+log4j.logger.org.apache.poi.hslf.model.textproperties.BitMaskTextProp=ERROR
+log4j.logger.org.apache.poi.util.JvmBugs=ERROR
+log4j.logger.org.apache.poi.hslf.usermodel.HSLFTextParagraph=ERROR
+log4j.logger.org.apache.poi.openxml4j.opc.ZipPackage=ERROR
+log4j.logger.org.apache.poi.POIDocument=WARN
+log4j.logger.org.apache.poi.openxml4j.opc.OPCPackage=ERROR
+log4j.logger.org.apache.poi.xssf.usermodel.XSSFWorkbook=ERROR
+log4j.logger.org.apache.poi.hslf.usermodel.HSLFGroupShape=WARN
+log4j.logger.org.apache.poi.hslf.record.Record=ERROR
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/TestAllFiles.java b/trunk/src/integrationtest/org/apache/poi/TestAllFiles.java
new file mode 100644
index 000000000..6183c185c
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/TestAllFiles.java
@@ -0,0 +1,394 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+
+import org.apache.poi.hwpf.OldWordFileFormatException;
+import org.apache.poi.stress.*;
+import org.apache.tools.ant.DirectoryScanner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.*;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * This is an integration test which performs various actions on all stored test-files and tries
+ * to reveal problems which are introduced, but not covered (yet) by unit tests.
+ *
+ * This test looks for any file under the test-data directory and tries to do some useful
+ * processing with it based on it's type.
+ *
+ * The test is implemented as a junit {@link Parameterized} test, which leads
+ * to one test-method call for each file (currently around 950 files are handled).
+ *
+ * There is a a mapping of extension to implementations of the interface
+ * {@link FileHandler} which defines how the file is loaded and which actions are
+ * tried with the file.
+ *
+ * The test can be expanded by adding more actions to the FileHandlers, this automatically
+ * applies the action to any such file in our test-data repository.
+ *
+ * There is also a list of files that should actually fail.
+ *
+ * Note: It is also a test-failure if a file that is expected to fail now actually works,
+ * i.e. if a bug was fixed in POI itself, the file should be removed from the expected-failures
+ * here as well! This is to ensure that files that should not work really do not work, e.g.
+ * that we do not remove expected sanity checks.
+ */
+@RunWith(Parameterized.class)
+public class TestAllFiles {
+ private static final File ROOT_DIR = new File("test-data");
+
+ static final String[] SCAN_EXCLUDES = new String[] { "**/.svn/**", "lost+found" };
+
+ // map file extensions to the actual mappers
+ static final Map HANDLERS = new HashMap();
+ static {
+ // Excel
+ HANDLERS.put(".xls", new HSSFFileHandler());
+ HANDLERS.put(".xlsx", new XSSFFileHandler());
+ HANDLERS.put(".xlsm", new XSSFFileHandler());
+ HANDLERS.put(".xltx", new XSSFFileHandler());
+ HANDLERS.put(".xlsb", new XSSFFileHandler());
+
+ // Word
+ HANDLERS.put(".doc", new HWPFFileHandler());
+ HANDLERS.put(".docx", new XWPFFileHandler());
+ HANDLERS.put(".dotx", new XWPFFileHandler());
+ HANDLERS.put(".docm", new XWPFFileHandler());
+
+ // OpenXML4J files
+ HANDLERS.put(".ooxml", new OPCFileHandler()); // OPCPackage
+ HANDLERS.put(".zip", new OPCFileHandler()); // OPCPackage
+
+ // Powerpoint
+ HANDLERS.put(".ppt", new HSLFFileHandler());
+ HANDLERS.put(".pptx", new XSLFFileHandler());
+ HANDLERS.put(".pptm", new XSLFFileHandler());
+ HANDLERS.put(".ppsm", new XSLFFileHandler());
+ HANDLERS.put(".ppsx", new XSLFFileHandler());
+ HANDLERS.put(".thmx", new XSLFFileHandler());
+
+ // Outlook
+ HANDLERS.put(".msg", new HSMFFileHandler());
+
+ // Publisher
+ HANDLERS.put(".pub", new HPBFFileHandler());
+
+ // Visio - binary
+ HANDLERS.put(".vsd", new HDGFFileHandler());
+
+ // Visio - ooxml
+ HANDLERS.put(".vsdm", new XDGFFileHandler());
+ HANDLERS.put(".vsdx", new XDGFFileHandler());
+ HANDLERS.put(".vssm", new XDGFFileHandler());
+ HANDLERS.put(".vssx", new XDGFFileHandler());
+ HANDLERS.put(".vstm", new XDGFFileHandler());
+ HANDLERS.put(".vstx", new XDGFFileHandler());
+
+ // Visio - not handled yet
+ HANDLERS.put(".vst", new NullFileHandler());
+ HANDLERS.put(".vss", new NullFileHandler());
+
+ // POIFS
+ HANDLERS.put(".ole2", new POIFSFileHandler());
+
+ // Microsoft Admin Template?
+ HANDLERS.put(".adm", new HPSFFileHandler());
+
+ // Microsoft TNEF
+ HANDLERS.put(".dat", new HMEFFileHandler());
+
+ // TODO: are these readable by some of the formats?
+ HANDLERS.put(".shw", new NullFileHandler());
+ HANDLERS.put(".zvi", new NullFileHandler());
+ HANDLERS.put(".mpp", new NullFileHandler());
+ HANDLERS.put(".qwp", new NullFileHandler());
+ HANDLERS.put(".wps", new NullFileHandler());
+ HANDLERS.put(".bin", new NullFileHandler());
+ HANDLERS.put(".xps", new NullFileHandler());
+ HANDLERS.put(".sldprt", new NullFileHandler());
+ HANDLERS.put(".mdb", new NullFileHandler());
+ HANDLERS.put(".vml", new NullFileHandler());
+
+ // ignore some file types, images, other formats, ...
+ HANDLERS.put(".txt", new NullFileHandler());
+ HANDLERS.put(".pdf", new NullFileHandler());
+ HANDLERS.put(".rtf", new NullFileHandler());
+ HANDLERS.put(".gif", new NullFileHandler());
+ HANDLERS.put(".html", new NullFileHandler());
+ HANDLERS.put(".png", new NullFileHandler());
+ HANDLERS.put(".wmf", new NullFileHandler());
+ HANDLERS.put(".emf", new NullFileHandler());
+ HANDLERS.put(".dib", new NullFileHandler());
+ HANDLERS.put(".svg", new NullFileHandler());
+ HANDLERS.put(".pict", new NullFileHandler());
+ HANDLERS.put(".jpg", new NullFileHandler());
+ HANDLERS.put(".jpeg", new NullFileHandler());
+ HANDLERS.put(".tif", new NullFileHandler());
+ HANDLERS.put(".tiff", new NullFileHandler());
+ HANDLERS.put(".wav", new NullFileHandler());
+ HANDLERS.put(".pfx", new NullFileHandler());
+ HANDLERS.put(".xml", new NullFileHandler());
+ HANDLERS.put(".csv", new NullFileHandler());
+ HANDLERS.put(".ods", new NullFileHandler());
+ // VBA source files
+ HANDLERS.put(".vba", new NullFileHandler());
+ HANDLERS.put(".bas", new NullFileHandler());
+ HANDLERS.put(".frm", new NullFileHandler());
+ HANDLERS.put(".frx", new NullFileHandler()); //binary
+ HANDLERS.put(".cls", new NullFileHandler());
+
+ // map some files without extension
+ HANDLERS.put("spreadsheet/BigSSTRecord", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR1", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR2", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR3", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR4", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR5", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR6", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecord2CR7", new NullFileHandler());
+ HANDLERS.put("spreadsheet/BigSSTRecordCR", new NullFileHandler());
+ HANDLERS.put("spreadsheet/test_properties1", new NullFileHandler());
+ }
+
+ // Old Word Documents where we can at least extract some text
+ private static final Set OLD_FILES = new HashSet();
+ static {
+ OLD_FILES.add("document/Bug49933.doc");
+ OLD_FILES.add("document/Bug51944.doc");
+ OLD_FILES.add("document/Word6.doc");
+ OLD_FILES.add("document/Word6_sections.doc");
+ OLD_FILES.add("document/Word6_sections2.doc");
+ OLD_FILES.add("document/Word95.doc");
+ OLD_FILES.add("document/word95err.doc");
+ OLD_FILES.add("hpsf/TestMickey.doc");
+ OLD_FILES.add("document/52117.doc");
+ }
+
+ private static final Set EXPECTED_FAILURES = new HashSet();
+ static {
+ // password protected files
+ EXPECTED_FAILURES.add("spreadsheet/password.xls");
+ EXPECTED_FAILURES.add("spreadsheet/protected_passtika.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/51832.xls");
+ EXPECTED_FAILURES.add("document/PasswordProtected.doc");
+ EXPECTED_FAILURES.add("slideshow/Password_Protected-hello.ppt");
+ EXPECTED_FAILURES.add("slideshow/Password_Protected-56-hello.ppt");
+ EXPECTED_FAILURES.add("slideshow/Password_Protected-np-hello.ppt");
+ EXPECTED_FAILURES.add("slideshow/cryptoapi-proc2356.ppt");
+ //EXPECTED_FAILURES.add("document/bug53475-password-is-pass.docx");
+ //EXPECTED_FAILURES.add("document/bug53475-password-is-solrcell.docx");
+ EXPECTED_FAILURES.add("spreadsheet/xor-encryption-abc.xls");
+ EXPECTED_FAILURES.add("spreadsheet/35897-type4.xls");
+ //EXPECTED_FAILURES.add("poifs/protect.xlsx");
+ //EXPECTED_FAILURES.add("poifs/protected_sha512.xlsx");
+ //EXPECTED_FAILURES.add("poifs/extenxls_pwd123.xlsx");
+ //EXPECTED_FAILURES.add("poifs/protected_agile.docx");
+ EXPECTED_FAILURES.add("spreadsheet/58616.xlsx");
+
+ // TODO: fails XMLExportTest, is this ok?
+ EXPECTED_FAILURES.add("spreadsheet/CustomXMLMapping-singleattributenamespace.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/55864.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/57890.xlsx");
+
+ // TODO: these fail now with some NPE/file read error because we now try to compute every value via Cell.toString()!
+ EXPECTED_FAILURES.add("spreadsheet/44958.xls");
+ EXPECTED_FAILURES.add("spreadsheet/44958_1.xls");
+ EXPECTED_FAILURES.add("spreadsheet/testArraysAndTables.xls");
+
+ // TODO: good to ignore?
+ EXPECTED_FAILURES.add("spreadsheet/sample-beta.xlsx");
+
+ // This is actually a spreadsheet!
+ EXPECTED_FAILURES.add("hpsf/TestRobert_Flaherty.doc");
+
+ // some files that are broken, eg Word 95, ...
+ EXPECTED_FAILURES.add("spreadsheet/43493.xls");
+ EXPECTED_FAILURES.add("spreadsheet/46904.xls");
+ EXPECTED_FAILURES.add("document/Bug50955.doc");
+ EXPECTED_FAILURES.add("slideshow/PPT95.ppt");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_CoreProperties_DCTermsNamespaceLimitedUseFAIL.docx");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_CoreProperties_DoNotUseCompatibilityMarkupFAIL.docx");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_CoreProperties_LimitedXSITypeAttribute_NotPresentFAIL.docx");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_CoreProperties_LimitedXSITypeAttribute_PresentWithUnauthorizedValueFAIL.docx");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_CoreProperties_OnlyOneCorePropertiesPartFAIL.docx");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_CoreProperties_UnauthorizedXMLLangAttributeFAIL.docx");
+ EXPECTED_FAILURES.add("openxml4j/OPCCompliance_DerivedPartNameFAIL.docx");
+ EXPECTED_FAILURES.add("spreadsheet/54764-2.xlsx"); // see TestXSSFBugs.bug54764()
+ EXPECTED_FAILURES.add("spreadsheet/54764.xlsx"); // see TestXSSFBugs.bug54764()
+ EXPECTED_FAILURES.add("spreadsheet/Simple.xlsb");
+ EXPECTED_FAILURES.add("poifs/unknown_properties.msg"); // POIFS properties corrupted
+ EXPECTED_FAILURES.add("poifs/only-zero-byte-streams.ole2"); // No actual contents
+ EXPECTED_FAILURES.add("spreadsheet/poc-xmlbomb.xlsx"); // contains xml-entity-expansion
+ EXPECTED_FAILURES.add("spreadsheet/poc-shared-strings.xlsx"); // contains shared-string-entity-expansion
+
+ // old Excel files, which we only support simple text extraction of
+ EXPECTED_FAILURES.add("spreadsheet/testEXCEL_2.xls");
+ EXPECTED_FAILURES.add("spreadsheet/testEXCEL_3.xls");
+ EXPECTED_FAILURES.add("spreadsheet/testEXCEL_4.xls");
+ EXPECTED_FAILURES.add("spreadsheet/testEXCEL_5.xls");
+ EXPECTED_FAILURES.add("spreadsheet/testEXCEL_95.xls");
+ EXPECTED_FAILURES.add("spreadsheet/59074.xls");
+
+ // OOXML Strict is not yet supported, see bug #57699
+ EXPECTED_FAILURES.add("spreadsheet/SampleSS.strict.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/SimpleStrict.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/sample.strict.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/57914.xlsx");
+
+ // non-TNEF files
+ EXPECTED_FAILURES.add("ddf/Container.dat");
+ EXPECTED_FAILURES.add("ddf/47143.dat");
+
+ // sheet cloning errors
+ EXPECTED_FAILURES.add("spreadsheet/47813.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/56450.xls");
+ EXPECTED_FAILURES.add("spreadsheet/57231_MixedGasReport.xls");
+ EXPECTED_FAILURES.add("spreadsheet/OddStyleRecord.xls");
+ EXPECTED_FAILURES.add("spreadsheet/WithChartSheet.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/chart_sheet.xlsx");
+ EXPECTED_FAILURES.add("spreadsheet/SimpleScatterChart.xlsx");
+ }
+
+ private static final Set IGNORED = new HashSet();
+ static {
+ // need JDK8+ - https://bugs.openjdk.java.net/browse/JDK-8038081
+ IGNORED.add("slideshow/42474-2.ppt");
+ // OPC handler works / XSSF handler fails
+ IGNORED.add("spreadsheet/57181.xlsm");
+ }
+
+ @Parameters(name="{index}: {0} using {1}")
+ public static Iterable files() {
+ DirectoryScanner scanner = new DirectoryScanner();
+ scanner.setBasedir(ROOT_DIR);
+ scanner.setExcludes(SCAN_EXCLUDES);
+
+ scanner.scan();
+
+ System.out.println("Handling " + scanner.getIncludedFiles().length + " files");
+
+ List files = new ArrayList();
+ for(String file : scanner.getIncludedFiles()) {
+ file = file.replace('\\', '/'); // ... failures/handlers lookup doesn't work on windows otherwise
+ if (IGNORED.contains(file)) continue;
+ FileHandler handler = HANDLERS.get(getExtension(file));
+ files.add(new Object[] { file, handler });
+
+ // for some file-types also run OPCFileHandler
+ if(handler instanceof XSSFFileHandler ||
+ handler instanceof XWPFFileHandler ||
+ handler instanceof XSLFFileHandler ||
+ handler instanceof XDGFFileHandler) {
+ files.add(new Object[] { file, HANDLERS.get(".ooxml") });
+ }
+ }
+
+ return files;
+ }
+
+ @SuppressWarnings("DefaultAnnotationParam")
+ @Parameter(value=0)
+ public String file;
+
+ @Parameter(value=1)
+ public FileHandler handler;
+
+ @Test
+ public void testAllFiles() throws Exception {
+ System.out.println("Reading " + file + " with " + handler.getClass());
+ assertNotNull("Unknown file extension for file: " + file + ": " + getExtension(file), handler);
+ File inputFile = new File(ROOT_DIR, file);
+
+ try {
+ InputStream stream = new BufferedInputStream(new FileInputStream(inputFile), 64*1024);
+ try {
+ handler.handleFile(stream);
+
+ assertFalse("Expected to fail for file " + file + " and handler " + handler + ", but did not fail!",
+ OLD_FILES.contains(file));
+ } finally {
+ stream.close();
+ }
+
+ handler.handleExtracting(inputFile);
+
+ // special cases where docx-handling breaks, but OPCPackage handling works
+ boolean ignoredOPC = (file.endsWith(".docx") || file.endsWith(".xlsx") ||
+ file.endsWith(".xlsb") || file.endsWith(".pptx")) &&
+ handler instanceof OPCFileHandler;
+
+ assertFalse("Expected to fail for file " + file + " and handler " + handler + ", but did not fail!",
+ EXPECTED_FAILURES.contains(file) && !ignoredOPC);
+ } catch (OldWordFileFormatException e) {
+ // for old word files we should still support extracting text
+ if(OLD_FILES.contains(file)) {
+ handler.handleExtracting(inputFile);
+ } else {
+ // check if we expect failure for this file
+ if(!EXPECTED_FAILURES.contains(file) && !AbstractFileHandler.EXPECTED_EXTRACTOR_FAILURES.contains(file)) {
+ System.out.println("Failed: " + file);
+ throw new Exception("While handling " + file, e);
+ }
+ }
+ } catch (Exception e) {
+ // check if we expect failure for this file
+ if(!EXPECTED_FAILURES.contains(file) && !AbstractFileHandler.EXPECTED_EXTRACTOR_FAILURES.contains(file)) {
+ System.out.println("Failed: " + file);
+ throw new Exception("While handling " + file, e);
+ }
+ }
+
+ // let some file handlers do additional stuff
+ handler.handleAdditional(inputFile);
+ }
+
+ static String getExtension(String file) {
+ int pos = file.lastIndexOf('.');
+ if(pos == -1 || pos == file.length()-1) {
+ return file;
+ }
+
+ return file.substring(pos).toLowerCase(Locale.ROOT);
+ }
+
+ private static class NullFileHandler implements FileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ }
+
+ @Override
+ public void handleExtracting(File file) throws Exception {
+ }
+
+ @Override
+ public void handleAdditional(File file) throws Exception {
+ }
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/hssf/usermodel/RecordsStresser.java b/trunk/src/integrationtest/org/apache/poi/hssf/usermodel/RecordsStresser.java
new file mode 100644
index 000000000..1b712f788
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/hssf/usermodel/RecordsStresser.java
@@ -0,0 +1,80 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.usermodel;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.poi.hssf.record.Record;
+import org.junit.Test;
+
+/**
+ * Needs to be implemented in this package to have access to
+ * HSSFWorkbook.getWorkbook()
+ */
+public class RecordsStresser {
+ public static void handleWorkbook(HSSFWorkbook wb) {
+ List records = wb.getWorkbook().getRecords();
+ for(Record record : records) {
+ // some Records do not implement clone ?!
+ // equals instead of instanceof is on purpose here to only skip exactly this class and not any derived ones
+// if(record.getClass().equals(InterfaceHdrRecord.class) ||
+// record.getClass().equals(MMSRecord.class) ||
+// record.getClass().equals(InterfaceEndRecord.class) ||
+// record.getClass().equals(WriteAccessRecord.class) ||
+// record.getClass().equals(CodepageRecord.class) ||
+// record.getClass().equals(DSFRecord.class)) {
+// continue;
+// }
+ try {
+ Record newRecord = (Record) record.clone();
+
+ assertEquals("Expecting the same class back from clone(), but had Record of type " + record.getClass() + " and got back a " + newRecord.getClass() + " from clone()",
+ record.getClass(), newRecord.getClass());
+
+ byte[] origBytes = record.serialize();
+ byte[] newBytes = newRecord.serialize();
+
+ assertArrayEquals("Record of type " + record.getClass() + " should return the same byte array via the clone() method, but did return a different array",
+ origBytes, newBytes);
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ } catch (RuntimeException e) {
+ // some Records do not implement clone, ignore those for now
+ assertTrue(e.getMessage().contains("needs to define a clone method"));
+ }
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/spreadsheet/15556.xls");
+ try {
+ HSSFWorkbook wb = new HSSFWorkbook(stream);
+ handleWorkbook(wb);
+ wb.close();
+ } finally {
+ stream.close();
+ }
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java
new file mode 100644
index 000000000..65edbaae5
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/AbstractFileHandler.java
@@ -0,0 +1,141 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.poi.POIOLE2TextExtractor;
+import org.apache.poi.POITextExtractor;
+import org.apache.poi.extractor.ExtractorFactory;
+import org.apache.poi.hpsf.extractor.HPSFPropertiesExtractor;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.xmlbeans.XmlException;
+
+public abstract class AbstractFileHandler implements FileHandler {
+ public static final Set EXPECTED_EXTRACTOR_FAILURES = new HashSet();
+ static {
+ // password protected files
+ EXPECTED_EXTRACTOR_FAILURES.add("document/bug53475-password-is-pass.docx");
+ EXPECTED_EXTRACTOR_FAILURES.add("poifs/extenxls_pwd123.xlsx");
+ EXPECTED_EXTRACTOR_FAILURES.add("poifs/protect.xlsx");
+ EXPECTED_EXTRACTOR_FAILURES.add("poifs/protected_agile.docx");
+ EXPECTED_EXTRACTOR_FAILURES.add("poifs/protected_sha512.xlsx");
+
+ // unsupported file-types, no supported OLE2 parts
+ EXPECTED_EXTRACTOR_FAILURES.add("hmef/quick-winmail.dat");
+ EXPECTED_EXTRACTOR_FAILURES.add("hmef/winmail-sample1.dat");
+ EXPECTED_EXTRACTOR_FAILURES.add("hmef/bug52400-winmail-simple.dat");
+ EXPECTED_EXTRACTOR_FAILURES.add("hmef/bug52400-winmail-with-attachments.dat");
+ EXPECTED_EXTRACTOR_FAILURES.add("hpsf/Test0313rur.adm");
+ EXPECTED_EXTRACTOR_FAILURES.add("poifs/Notes.ole2");
+ }
+
+ public void handleExtracting(File file) throws Exception {
+ boolean before = ExtractorFactory.getThreadPrefersEventExtractors();
+ try {
+ ExtractorFactory.setThreadPrefersEventExtractors(true);
+ handleExtractingInternal(file);
+
+ ExtractorFactory.setThreadPrefersEventExtractors(false);
+ handleExtractingInternal(file);
+ } finally {
+ ExtractorFactory.setThreadPrefersEventExtractors(before);
+ }
+
+ /* Did fail for some documents with special XML contents...
+ try {
+ OOXMLPrettyPrint.main(new String[] { file.getAbsolutePath(),
+ "/tmp/pretty-" + file.getName() });
+ } catch (ZipException e) {
+ // ignore, not a Zip/OOXML file
+ }*/
+ }
+
+ private void handleExtractingInternal(File file) throws Exception {
+ long length = file.length();
+ long modified = file.lastModified();
+
+ POITextExtractor extractor = ExtractorFactory.createExtractor(file);
+ try {
+ assertNotNull(extractor);
+
+ assertNotNull(extractor.getText());
+
+ // also try metadata
+ @SuppressWarnings("resource")
+ POITextExtractor metadataExtractor = extractor.getMetadataTextExtractor();
+ assertNotNull(metadataExtractor.getText());
+
+ assertFalse("Expected Extraction to fail for file " + file + " and handler " + this + ", but did not fail!",
+ EXPECTED_EXTRACTOR_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName()));
+
+ assertEquals("File should not be modified by extractor", length, file.length());
+ assertEquals("File should not be modified by extractor", modified, file.lastModified());
+
+ handleExtractingAsStream(file);
+
+ if(extractor instanceof POIOLE2TextExtractor) {
+ HPSFPropertiesExtractor hpsfExtractor = new HPSFPropertiesExtractor((POIOLE2TextExtractor)extractor);
+ try {
+ assertNotNull(hpsfExtractor.getDocumentSummaryInformationText());
+ assertNotNull(hpsfExtractor.getSummaryInformationText());
+ String text = hpsfExtractor.getText();
+ //System.out.println(text);
+ assertNotNull(text);
+ } finally {
+ hpsfExtractor.close();
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ if(!EXPECTED_EXTRACTOR_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } finally {
+ extractor.close();
+ }
+ }
+
+ private void handleExtractingAsStream(File file) throws IOException, OpenXML4JException, XmlException {
+ InputStream stream = new FileInputStream(file);
+ try {
+ POITextExtractor streamExtractor = ExtractorFactory.createExtractor(stream);
+ try {
+ assertNotNull(streamExtractor);
+
+ assertNotNull(streamExtractor.getText());
+ } finally {
+ streamExtractor.close();
+ }
+ } finally {
+ stream.close();
+ }
+ }
+
+ @Override
+ public void handleAdditional(File file) throws Exception {
+ // by default we do nothing here
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/FileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/FileHandler.java
new file mode 100644
index 000000000..8b65cfa47
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/FileHandler.java
@@ -0,0 +1,50 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * Base interface for the various file types that are
+ * used in the stress testing.
+ */
+public interface FileHandler {
+ /**
+ * The FileHandler receives a stream ready for reading the
+ * file and should handle the content that is provided and
+ * try to read and interpret the data.
+ *
+ * Closing is handled by the framework outside this call.
+ *
+ * @param stream
+ * @throws Exception
+ */
+ void handleFile(InputStream stream) throws Exception;
+
+ /**
+ * Ensures that extracting text from the given file
+ * is returning some text.
+ */
+ void handleExtracting(File file) throws Exception;
+
+ /**
+ * Allows to perform some additional work, e.g. run
+ * some of the example applications
+ */
+ void handleAdditional(File file) throws Exception;
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HDGFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HDGFFileHandler.java
new file mode 100644
index 000000000..12313b78e
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HDGFFileHandler.java
@@ -0,0 +1,79 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hdgf.HDGFDiagram;
+import org.apache.poi.hdgf.extractor.VisioTextExtractor;
+import org.apache.poi.hdgf.streams.Stream;
+import org.apache.poi.hdgf.streams.TrailerStream;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.junit.Test;
+
+public class HDGFFileHandler extends POIFSFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ POIFSFileSystem poifs = new POIFSFileSystem(stream);
+ HDGFDiagram diagram = new HDGFDiagram(poifs);
+ Stream[] topLevelStreams = diagram.getTopLevelStreams();
+ assertNotNull(topLevelStreams);
+ for(Stream str : topLevelStreams) {
+ assertTrue(str.getPointer().getLength() >= 0);
+ }
+
+ TrailerStream trailerStream = diagram.getTrailerStream();
+ assertNotNull(trailerStream);
+ assertTrue(trailerStream.getPointer().getLength() >= 0);
+
+ poifs.close();
+
+ // writing is not yet implemented... handlePOIDocument(diagram);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ File file = new File("test-data/diagram/44501.vsd");
+
+ InputStream stream = new FileInputStream(file);
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+
+ handleExtracting(file);
+
+ stream = new FileInputStream(file);
+ try {
+ VisioTextExtractor extractor = new VisioTextExtractor(stream);
+ try {
+ assertNotNull(extractor.getText());
+ } finally {
+ extractor.close();
+ }
+ } finally {
+ stream.close();
+ }
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HMEFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HMEFFileHandler.java
new file mode 100644
index 000000000..9f492bf0e
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HMEFFileHandler.java
@@ -0,0 +1,60 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hmef.HMEFMessage;
+import org.apache.poi.hmef.attribute.MAPIAttribute;
+import org.apache.poi.hmef.attribute.MAPIStringAttribute;
+import org.junit.Test;
+
+public class HMEFFileHandler extends AbstractFileHandler {
+
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ HMEFMessage msg = new HMEFMessage(stream);
+
+ // list all properties
+ StringBuilder props = new StringBuilder();
+ for(MAPIAttribute att : msg.getMessageMAPIAttributes()) {
+ props.append(att.getType()).append(": ").append(MAPIStringAttribute.getAsString( att)).append("\n");
+ }
+
+ // there are two test-files that have no body...
+ if(!msg.getSubject().equals("Testing TNEF Message") && !msg.getSubject().equals("TNEF test message with attachments")) {
+ assertNotNull("Had: " + msg.getBody() + ", " + msg.getSubject() + ", " + msg.getAttachments() + ": " + props.toString(),
+ msg.getBody());
+ }
+ assertNotNull("Had: " + msg.getBody() + ", " + msg.getSubject() + ", " + msg.getAttachments() + ": " + props.toString(),
+ msg.getSubject());
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/hmef/quick-winmail.dat");
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HPBFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HPBFFileHandler.java
new file mode 100644
index 000000000..a41b6ebad
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HPBFFileHandler.java
@@ -0,0 +1,68 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hpbf.HPBFDocument;
+import org.apache.poi.hpbf.extractor.PublisherTextExtractor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.junit.Test;
+
+public class HPBFFileHandler extends POIFSFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ HPBFDocument pub = new HPBFDocument(new POIFSFileSystem(stream));
+ assertNotNull(pub.getEscherDelayStm());
+ assertNotNull(pub.getMainContents());
+ assertNotNull(pub.getQuillContents());
+
+ // writing is not yet implemented... handlePOIDocument(pub);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ File file = new File("test-data/publisher/SampleBrochure.pub");
+
+ InputStream stream = new FileInputStream(file);
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+
+ handleExtracting(file);
+
+ stream = new FileInputStream(file);
+ try {
+ PublisherTextExtractor extractor = new PublisherTextExtractor(stream);
+ try {
+ assertNotNull(extractor.getText());
+ } finally {
+ extractor.close();
+ }
+ } finally {
+ stream.close();
+ }
+ }
+
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java
new file mode 100644
index 000000000..6a84201b4
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HPSFFileHandler.java
@@ -0,0 +1,55 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hpsf.HPSFPropertiesOnlyDocument;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.junit.Test;
+
+public class HPSFFileHandler extends POIFSFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ HPSFPropertiesOnlyDocument hpsf = new HPSFPropertiesOnlyDocument(new POIFSFileSystem(stream));
+ assertNotNull(hpsf.getDocumentSummaryInformation());
+ assertNotNull(hpsf.getSummaryInformation());
+
+ handlePOIDocument(hpsf);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/hpsf/Test0313rur.adm");
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void testExtractor() throws Exception {
+ handleExtracting(new File("test-data/hpsf/TestBug44375.xls"));
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HSLFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HSLFFileHandler.java
new file mode 100644
index 000000000..c0454e0d3
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HSLFFileHandler.java
@@ -0,0 +1,67 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hslf.record.Record;
+import org.apache.poi.hslf.usermodel.HSLFSlideShow;
+import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl;
+import org.junit.Test;
+
+public class HSLFFileHandler extends SlideShowHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ HSLFSlideShowImpl slide = new HSLFSlideShowImpl(stream);
+ assertNotNull(slide.getCurrentUserAtom());
+ assertNotNull(slide.getEmbeddedObjects());
+ assertNotNull(slide.getUnderlyingBytes());
+ assertNotNull(slide.getPictureData());
+ Record[] records = slide.getRecords();
+ assertNotNull(records);
+ for(Record record : records) {
+ assertTrue(record.getRecordType() >= 0);
+ }
+
+ handlePOIDocument(slide);
+
+ HSLFSlideShow ss = new HSLFSlideShow(slide);
+ handleSlideShow(ss);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/hpsf/Test_Humor-Generation.ppt");
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void testExtractor() throws Exception {
+ handleExtracting(new File("test-data/slideshow/ae.ac.uaeu.faculty_nafaachbili_GeomLec1.pptx"));
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HSMFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HSMFFileHandler.java
new file mode 100644
index 000000000..d68504a04
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HSMFFileHandler.java
@@ -0,0 +1,84 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hsmf.MAPIMessage;
+import org.apache.poi.hsmf.datatypes.AttachmentChunks;
+import org.apache.poi.hsmf.datatypes.DirectoryChunk;
+import org.junit.Test;
+
+public class HSMFFileHandler extends POIFSFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ MAPIMessage mapi = new MAPIMessage(stream);
+ assertNotNull(mapi.getAttachmentFiles());
+ assertNotNull(mapi.getDisplayBCC());
+ assertNotNull(mapi.getMessageDate());
+
+ AttachmentChunks[] attachments = mapi.getAttachmentFiles();
+
+ for(AttachmentChunks attachment : attachments) {
+
+ DirectoryChunk chunkDirectory = attachment.attachmentDirectory;
+ if(chunkDirectory != null) {
+ MAPIMessage attachmentMSG = chunkDirectory.getAsEmbededMessage();
+ assertNotNull(attachmentMSG);
+ String body = attachmentMSG.getTextBody();
+ assertNotNull(body);
+ }
+ }
+
+ /* => Writing isn't yet supported...
+ // write out the file
+ File file = TempFile.createTempFile("StressTest", ".msg");
+ writeToFile(mapi, file);
+
+ MAPIMessage read = new MAPIMessage(file.getAbsolutePath());
+ assertNotNull(read.getAttachmentFiles());
+ assertNotNull(read.getDisplayBCC());
+ assertNotNull(read.getMessageDate());
+ */
+
+ // writing is not yet supported... handlePOIDocument(mapi);
+ }
+
+// private void writeToFile(MAPIMessage mapi, File file)
+// throws FileNotFoundException, IOException {
+// OutputStream stream = new FileOutputStream(file);
+// try {
+// mapi.write(stream);
+// } finally {
+// stream.close();
+// }
+// }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/hsmf/example_received_regular.msg");
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HSSFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HSSFFileHandler.java
new file mode 100644
index 000000000..19a06ab63
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HSSFFileHandler.java
@@ -0,0 +1,113 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.hssf.OldExcelFormatException;
+import org.apache.poi.hssf.dev.BiffViewer;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.util.RecordFormatException;
+import org.junit.Test;
+
+import java.io.*;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertFalse;
+
+public class HSSFFileHandler extends SpreadsheetHandler {
+ private POIFSFileHandler delegate = new POIFSFileHandler();
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ HSSFWorkbook wb = new HSSFWorkbook(stream);
+ handleWorkbook(wb);
+
+ // TODO: some documents fail currently...
+ //HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb);
+ //evaluator.evaluateAll();
+
+ delegate.handlePOIDocument(wb);
+
+ // also try to see if some of the Records behave incorrectly
+ // TODO: still fails on some records... RecordsStresser.handleWorkbook(wb);
+ }
+
+ private static final Set EXPECTED_ADDITIONAL_FAILURES = new HashSet();
+ static {
+ // encrypted
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/35897-type4.xls");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/xor-encryption-abc.xls");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/password.xls");
+ // broken files
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/43493.xls");
+ // TODO: ok to ignore?
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/50833.xls");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/51832.xls");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/XRefCalc.xls");
+ }
+
+ @Override
+ public void handleAdditional(File file) throws Exception {
+ // redirect stdout as the examples often write lots of text
+ PrintStream oldOut = System.out;
+ try {
+ System.setOut(new PrintStream(new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ }
+ }));
+
+ BiffViewer.main(new String[]{file.getAbsolutePath()});
+
+ assertFalse("Expected Extraction to fail for file " + file + " and handler " + this + ", but did not fail!",
+ EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName()));
+ } catch (OldExcelFormatException e) {
+ // old excel formats are not supported here
+ } catch (EncryptedDocumentException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } catch (RecordFormatException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } catch (RuntimeException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } finally {
+ System.setOut(oldOut);
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/spreadsheet/49219.xls");
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void testExtractor() throws Exception {
+ handleExtracting(new File("test-data/spreadsheet/BOOK_in_capitals.xls"));
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/HWPFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/HWPFFileHandler.java
new file mode 100644
index 000000000..ead35e47b
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/HWPFFileHandler.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.hwpf.HWPFDocument;
+import org.apache.poi.hwpf.extractor.WordExtractor;
+import org.junit.Test;
+
+public class HWPFFileHandler extends POIFSFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ HWPFDocument doc = new HWPFDocument(stream);
+ assertNotNull(doc.getBookmarks());
+ assertNotNull(doc.getCharacterTable());
+ assertNotNull(doc.getEndnotes());
+
+ handlePOIDocument(doc);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ File file = new File("test-data/document/52117.doc");
+
+ InputStream stream = new FileInputStream(file);
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+
+ handleExtracting(file);
+
+ stream = new FileInputStream(file);
+ try {
+ WordExtractor extractor = new WordExtractor(stream);
+ try {
+ assertNotNull(extractor.getText());
+ } finally {
+ extractor.close();
+ }
+ } finally {
+ stream.close();
+ }
+ }
+
+ @Test
+ public void testExtractingOld() throws Exception {
+ File file = new File("test-data/document/52117.doc");
+ handleExtracting(file);
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/OPCFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/OPCFileHandler.java
new file mode 100644
index 000000000..50a9577c0
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/OPCFileHandler.java
@@ -0,0 +1,76 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+import org.apache.poi.openxml4j.opc.ContentTypes;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.xwpf.usermodel.XWPFRelation;
+import org.junit.Test;
+
+public class OPCFileHandler extends AbstractFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ // ignore password protected files
+ if (POIXMLDocumentHandler.isEncrypted(stream)) return;
+
+ OPCPackage p = OPCPackage.open(stream);
+
+ for (PackagePart part : p.getParts()) {
+ if (part.getPartName().toString().equals("/docProps/core.xml")) {
+ assertEquals(ContentTypes.CORE_PROPERTIES_PART, part.getContentType());
+ }
+ if (part.getPartName().toString().equals("/word/document.xml")) {
+ assertTrue("Expected one of " + XWPFRelation.MACRO_DOCUMENT + ", " + XWPFRelation.DOCUMENT + ", " + XWPFRelation.TEMPLATE +
+ ", but had " + part.getContentType(),
+ XWPFRelation.DOCUMENT.getContentType().equals(part.getContentType()) ||
+ XWPFRelation.MACRO_DOCUMENT.getContentType().equals(part.getContentType()) ||
+ XWPFRelation.TEMPLATE.getContentType().equals(part.getContentType()));
+ }
+ if (part.getPartName().toString().equals("/word/theme/theme1.xml")) {
+ assertEquals(XWPFRelation.THEME.getContentType(), part.getContentType());
+ }
+ }
+ }
+
+ public void handleExtracting(File file) throws Exception {
+ // text-extraction is not possible currenlty for these types of files
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ File file = new File("test-data/diagram/test.vsdx");
+
+ InputStream stream = new PushbackInputStream(new FileInputStream(file), 100000);
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+
+ handleExtracting(file);
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/POIFSFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/POIFSFileHandler.java
new file mode 100644
index 000000000..56be0d9ee
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/POIFSFileHandler.java
@@ -0,0 +1,90 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.POIDocument;
+import org.apache.poi.hpsf.extractor.HPSFPropertiesExtractor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.junit.Test;
+
+public class POIFSFileHandler extends AbstractFileHandler {
+
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ POIFSFileSystem fs = new POIFSFileSystem(stream);
+ try {
+ handlePOIFSFileSystem(fs);
+ handleHPSFProperties(fs);
+ } finally {
+ fs.close();
+ }
+ }
+
+ private void handleHPSFProperties(POIFSFileSystem fs) throws IOException {
+ HPSFPropertiesExtractor ext = new HPSFPropertiesExtractor(fs);
+ try {
+ // can be null
+ ext.getDocSummaryInformation();
+ ext.getSummaryInformation();
+
+ assertNotNull(ext.getDocumentSummaryInformationText());
+ assertNotNull(ext.getSummaryInformationText());
+ assertNotNull(ext.getText());
+ } finally {
+ ext.close();
+ }
+ }
+
+ private void handlePOIFSFileSystem(POIFSFileSystem fs) {
+ assertNotNull(fs);
+ assertNotNull(fs.getRoot());
+ }
+
+ protected void handlePOIDocument(POIDocument doc) throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ doc.write(out);
+
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ POIFSFileSystem fs = new POIFSFileSystem(in);
+ handlePOIFSFileSystem(fs);
+ fs.close();
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ File file = new File("test-data/poifs/Notes.ole2");
+
+ InputStream stream = new FileInputStream(file);
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+
+ //handleExtracting(file);
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/POIXMLDocumentHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/POIXMLDocumentHandler.java
new file mode 100644
index 000000000..1a8cbf6a0
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/POIXMLDocumentHandler.java
@@ -0,0 +1,51 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.POIXMLDocument;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+public final class POIXMLDocumentHandler {
+ protected void handlePOIXMLDocument(POIXMLDocument doc) throws Exception {
+ assertNotNull(doc.getAllEmbedds());
+ assertNotNull(doc.getPackage());
+ assertNotNull(doc.getPackagePart());
+ assertNotNull(doc.getProperties());
+ assertNotNull(doc.getRelations());
+ }
+
+ protected static boolean isEncrypted(InputStream stream) throws IOException {
+ if (POIFSFileSystem.hasPOIFSHeader(stream)) {
+ POIFSFileSystem poifs = new POIFSFileSystem(stream);
+ try {
+ if (poifs.getRoot().hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) {
+ return true;
+ }
+ } finally {
+ poifs.close();
+ }
+ throw new IOException("wrong file format or file extension for OO XML file");
+ }
+ return false;
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
new file mode 100644
index 000000000..e798b3533
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
@@ -0,0 +1,126 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.sl.draw.DrawFactory;
+import org.apache.poi.sl.usermodel.PictureData;
+import org.apache.poi.sl.usermodel.Shape;
+import org.apache.poi.sl.usermodel.ShapeContainer;
+import org.apache.poi.sl.usermodel.Slide;
+import org.apache.poi.sl.usermodel.SlideShow;
+import org.apache.poi.sl.usermodel.SlideShowFactory;
+import org.apache.poi.sl.usermodel.TextParagraph;
+import org.apache.poi.sl.usermodel.TextRun;
+import org.apache.poi.sl.usermodel.TextShape;
+
+public abstract class SlideShowHandler extends POIFSFileHandler {
+ public void handleSlideShow(SlideShow,?> ss) throws IOException {
+ renderSlides(ss);
+
+ readContent(ss);
+ readPictures(ss);
+
+ // write out the file
+ ByteArrayOutputStream out = writeToArray(ss);
+
+ readContent(ss);
+
+ // read in the writen file
+ SlideShow,?> read = SlideShowFactory.create(new ByteArrayInputStream(out.toByteArray()));
+ try {
+ assertNotNull(read);
+ readContent(read);
+ } finally {
+ read.close();
+ }
+ }
+
+ private ByteArrayOutputStream writeToArray(SlideShow,?> ss) throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ ss.write(stream);
+ } finally {
+ stream.close();
+ }
+
+ return stream;
+ }
+
+
+ private void readContent(SlideShow,?> ss) {
+ for (Slide,?> s : ss.getSlides()) {
+ s.getTitle();
+ readText(s);
+ readText(s.getNotes());
+ readText(s.getMasterSheet());
+ }
+ }
+
+ private void readText(ShapeContainer,?> sc) {
+ if (sc == null) return;
+ for (Shape,?> s : sc) {
+ if (s instanceof TextShape) {
+ for (TextParagraph,?,?> tp : (TextShape,?>)s) {
+ for (TextRun tr : tp) {
+ tr.getRawText();
+ }
+ }
+ }
+ }
+ }
+
+ private void readPictures(SlideShow,?> ss) {
+ for (PictureData pd : ss.getPictureData()) {
+ Dimension dim = pd.getImageDimension();
+ assertTrue(dim.getHeight() >= 0);
+ assertTrue(dim.getWidth() >= 0);
+ }
+ }
+
+ private void renderSlides(SlideShow,?> ss) {
+ Dimension pgsize = ss.getPageSize();
+
+ for (Slide,?> s : ss.getSlides()) {
+ BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = img.createGraphics();
+ DrawFactory.getInstance(graphics).fixFonts(graphics);
+
+ // default rendering options
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+ // draw stuff
+ s.draw(graphics);
+
+ graphics.dispose();
+ img.flush();
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/SpreadsheetHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/SpreadsheetHandler.java
new file mode 100644
index 000000000..123bfa745
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/SpreadsheetHandler.java
@@ -0,0 +1,131 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.apache.poi.util.RecordFormatException;
+
+public abstract class SpreadsheetHandler extends AbstractFileHandler {
+ public void handleWorkbook(Workbook wb) throws IOException {
+ // try to access some of the content
+ readContent(wb);
+
+ // write out the file
+ writeToArray(wb);
+
+ // access some more content (we had cases where writing corrupts the data in memory)
+ readContent(wb);
+
+ // write once more
+ ByteArrayOutputStream out = writeToArray(wb);
+
+ // read in the written file
+ Workbook read;
+ try {
+ read = WorkbookFactory.create(new ByteArrayInputStream(out.toByteArray()));
+ } catch (InvalidFormatException e) {
+ throw new IllegalStateException(e);
+ }
+ assertNotNull(read);
+
+ readContent(read);
+
+ modifyContent(read);
+
+ read.close();
+ }
+
+ private ByteArrayOutputStream writeToArray(Workbook wb) throws IOException {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ wb.write(stream);
+ } finally {
+ stream.close();
+ }
+
+ return stream;
+ }
+
+ private void readContent(Workbook wb) {
+ for(int i = 0;i < wb.getNumberOfSheets();i++) {
+ Sheet sheet = wb.getSheetAt(i);
+ assertNotNull(wb.getSheet(sheet.getSheetName()));
+ sheet.groupColumn((short) 4, (short) 5);
+ sheet.setColumnGroupCollapsed(4, true);
+ sheet.setColumnGroupCollapsed(4, false);
+
+ // don't do this for very large sheets as it will take a long time
+ if(sheet.getPhysicalNumberOfRows() > 1000) {
+ continue;
+ }
+
+ for(Row row : sheet) {
+ for(Cell cell : row) {
+ assertNotNull(cell.toString());
+ }
+ }
+ }
+ }
+
+ private void modifyContent(Workbook wb) {
+ /* a number of file fail because of various things: udf, unimplemented functions, ...
+ we would need quite a list of excludes and the large regression tests would probably
+ take a lot longer to run...
+ try {
+ // try to re-compute all formulas to find cases where parsing fails
+ wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
+ } catch (RuntimeException e) {
+ // only allow a specific exception which indicates that an external
+ // reference was not found
+ if(!e.getMessage().contains("Could not resolve external workbook name")) {
+ throw e;
+ }
+
+ }*/
+
+ for (int i=wb.getNumberOfSheets()-1; i>=0; i--) {
+ try {
+ wb.cloneSheet(i);
+ } catch (RecordFormatException e) {
+ if (e.getCause() instanceof CloneNotSupportedException) {
+ // ignore me
+ continue;
+ }
+ throw e;
+ } catch (RuntimeException e) {
+ if ("Could not find 'internal references' EXTERNALBOOK".equals(e.getMessage()) ||
+ "CountryRecord not found".equals(e.getMessage()) ||
+ "Cannot add more than 65535 shapes".equals(e.getMessage()) ) {
+ // ignore these here for now
+ continue;
+ }
+ throw e;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/XDGFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/XDGFFileHandler.java
new file mode 100644
index 000000000..9b7d03f8a
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/XDGFFileHandler.java
@@ -0,0 +1,47 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import java.io.InputStream;
+
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.xdgf.usermodel.XmlVisioDocument;
+import org.junit.Test;
+
+public class XDGFFileHandler extends AbstractFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ // ignore password protected files
+ if (POIXMLDocumentHandler.isEncrypted(stream)) return;
+
+ XmlVisioDocument doc = new XmlVisioDocument(stream);
+ new POIXMLDocumentHandler().handlePOIXMLDocument(doc);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ OPCPackage pkg = OPCPackage.open("test-data/diagram/test.vsdx", PackageAccess.READ);
+ try {
+ XmlVisioDocument doc = new XmlVisioDocument(pkg);
+ new POIXMLDocumentHandler().handlePOIXMLDocument(doc);
+ } finally {
+ pkg.close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java
new file mode 100644
index 000000000..bbfdc661c
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/XSLFFileHandler.java
@@ -0,0 +1,83 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import org.apache.poi.extractor.ExtractorFactory;
+import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor;
+import org.apache.poi.xslf.usermodel.XMLSlideShow;
+import org.apache.poi.xslf.usermodel.XSLFSlideShow;
+import org.junit.Test;
+
+public class XSLFFileHandler extends SlideShowHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ XMLSlideShow slide = new XMLSlideShow(stream);
+ XSLFSlideShow slideInner = new XSLFSlideShow(slide.getPackage());
+ assertNotNull(slideInner.getPresentation());
+ assertNotNull(slideInner.getSlideMasterReferences());
+ assertNotNull(slideInner.getSlideReferences());
+
+ new POIXMLDocumentHandler().handlePOIXMLDocument(slide);
+
+ handleSlideShow(slide);
+
+ slideInner.close();
+ slide.close();
+ }
+
+ public void handleExtracting(File file) throws Exception {
+ super.handleExtracting(file);
+
+
+ // additionally try the other getText() methods
+
+ XSLFPowerPointExtractor extractor = (XSLFPowerPointExtractor) ExtractorFactory.createExtractor(file);
+ try {
+ assertNotNull(extractor);
+
+ assertNotNull(extractor.getText(true, true, true));
+ assertEquals("With all options disabled we should not get text",
+ "", extractor.getText(false, false, false));
+ } finally {
+ extractor.close();
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new FileInputStream("test-data/slideshow/ae.ac.uaeu.faculty_nafaachbili_GeomLec1.pptx");
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void testExtractor() throws Exception {
+ handleExtracting(new File("test-data/slideshow/ae.ac.uaeu.faculty_nafaachbili_GeomLec1.pptx"));
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java
new file mode 100644
index 000000000..be6039707
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/XSSFFileHandler.java
@@ -0,0 +1,237 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.*;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+
+import org.apache.poi.POIXMLException;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException;
+import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.eventusermodel.XLSX2CSV;
+import org.apache.poi.xssf.eventusermodel.XSSFReader;
+import org.apache.poi.xssf.eventusermodel.examples.FromHowTo;
+import org.apache.poi.xssf.extractor.XSSFExportToXml;
+import org.apache.poi.xssf.usermodel.XSSFMap;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+public class XSSFFileHandler extends SpreadsheetHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ // ignore password protected files
+ if (POIXMLDocumentHandler.isEncrypted(stream)) return;
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ IOUtils.copy(stream, out);
+
+ final byte[] bytes = out.toByteArray();
+ final XSSFWorkbook wb;
+ wb = new XSSFWorkbook(new ByteArrayInputStream(bytes));
+
+ // use the combined handler for HSSF/XSSF
+ handleWorkbook(wb);
+
+ // TODO: some documents fail currently...
+ //XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(wb);
+ //evaluator.evaluateAll();
+
+ // also verify general POIFS-stuff
+ new POIXMLDocumentHandler().handlePOIXMLDocument(wb);
+
+ // and finally ensure that exporting to XML works
+ exportToXML(wb);
+
+ checkXSSFReader(OPCPackage.open(new ByteArrayInputStream(bytes)));
+
+ wb.close();
+ }
+
+
+ private void checkXSSFReader(OPCPackage p) throws IOException, OpenXML4JException {
+ XSSFReader reader = new XSSFReader(p);
+
+ // these can be null...
+ InputStream sharedStringsData = reader.getSharedStringsData();
+ if(sharedStringsData != null) {
+ sharedStringsData.close();
+ }
+ reader.getSharedStringsTable();
+
+ InputStream stylesData = reader.getStylesData();
+ if(stylesData != null) {
+ stylesData.close();
+ }
+ reader.getStylesTable();
+
+ InputStream themesData = reader.getThemesData();
+ if(themesData != null) {
+ themesData.close();
+ }
+
+ assertNotNull(reader.getWorkbookData());
+
+ Iterator sheetsData = reader.getSheetsData();
+ while(sheetsData.hasNext()) {
+ InputStream str = sheetsData.next();
+ str.close();
+ }
+ }
+
+ private void exportToXML(XSSFWorkbook wb) throws SAXException,
+ ParserConfigurationException, TransformerException {
+ for (XSSFMap map : wb.getCustomXMLMappings()) {
+ XSSFExportToXml exporter = new XSSFExportToXml(map);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ exporter.exportToXML(os, true);
+ }
+ }
+
+ private static final Set EXPECTED_ADDITIONAL_FAILURES = new HashSet();
+ static {
+ // expected sheet-id not found
+ // EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/52348.xlsx");
+ // EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/59021.xlsx");
+ // zip-bomb
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/54764.xlsx");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/54764-2.xlsx");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/54764.xlsx");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/poc-xmlbomb.xlsx");
+ // strict OOXML
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/57914.xlsx");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/SampleSS.strict.xlsx");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/SimpleStrict.xlsx");
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/sample.strict.xlsx");
+ // binary format
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/Simple.xlsb");
+ // TODO: good to ignore?
+ EXPECTED_ADDITIONAL_FAILURES.add("spreadsheet/sample-beta.xlsx");
+ }
+
+ @SuppressWarnings("resource")
+ @Override
+ public void handleAdditional(File file) throws Exception {
+ // redirect stdout as the examples often write lots of text
+ PrintStream oldOut = System.out;
+ try {
+ System.setOut(new NullPrintStream());
+ FromHowTo.main(new String[]{file.getAbsolutePath()});
+ XLSX2CSV.main(new String[]{file.getAbsolutePath()});
+
+ assertFalse("Expected Extraction to fail for file " + file + " and handler " + this + ", but did not fail!",
+ EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName()));
+
+ } catch (OLE2NotOfficeXmlFileException e) {
+ // we have some files that are not actually OOXML and thus cannot be tested here
+ } catch (IllegalArgumentException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } catch (InvalidFormatException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } catch (IOException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } catch (POIXMLException e) {
+ if(!EXPECTED_ADDITIONAL_FAILURES.contains(file.getParentFile().getName() + "/" + file.getName())) {
+ throw e;
+ }
+ } finally {
+ System.setOut(oldOut);
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ InputStream stream = new BufferedInputStream(new FileInputStream("test-data/spreadsheet/ref-56737.xlsx"));
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void testExtractor() throws Exception {
+ handleExtracting(new File("test-data/spreadsheet/ref-56737.xlsx"));
+ }
+
+ @Test
+ public void testAdditional() throws Exception {
+ handleAdditional(new File("test-data/spreadsheet/poc-xmlbomb.xlsx"));
+ }
+
+ // need to override all methods to omit calls to UTF-handling methods
+ static class NullPrintStream extends PrintStream {
+ @SuppressWarnings("resource")
+ NullPrintStream() {
+ super(new OutputStream() {
+ public void write(int b) {}
+ public void write(byte[] b) {}
+ public void write(byte[] b, int off, int len) {}
+ });
+ }
+ public void write(int b) {}
+ public void write(byte[] buf, int off, int len) {}
+ public void print(boolean b) {}
+ public void print(char c) {}
+ public void print(int i) {}
+ public void print(long l) {}
+ public void print(float f) {}
+ public void print(double d) {}
+ public void print(char[] s) {}
+ public void print(String s) {}
+ public void print(Object obj) {}
+ public void println() {}
+ public void println(boolean x) {}
+ public void println(char x) {}
+ public void println(int x) {}
+ public void println(long x) {}
+ public void println(float x) {}
+ public void println(double x) {}
+ public void println(char[] x) {}
+ public void println(String x) {}
+ public void println(Object x) {}
+ public PrintStream printf(String format, Object... args) { return this; }
+ public PrintStream printf(Locale l, String format, Object... args) { return this; }
+ public PrintStream format(String format, Object... args) { return this; }
+ public PrintStream format(Locale l, String format, Object... args) { return this; }
+ public PrintStream append(CharSequence csq) { return this; }
+ public PrintStream append(CharSequence csq, int start, int end) { return this; }
+ public PrintStream append(char c) { return this; }
+ public void write(byte[] b) {}
+ }
+}
diff --git a/trunk/src/integrationtest/org/apache/poi/stress/XWPFFileHandler.java b/trunk/src/integrationtest/org/apache/poi/stress/XWPFFileHandler.java
new file mode 100644
index 000000000..c097dc9f7
--- /dev/null
+++ b/trunk/src/integrationtest/org/apache/poi/stress/XWPFFileHandler.java
@@ -0,0 +1,53 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.stress;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.junit.Test;
+
+public class XWPFFileHandler extends AbstractFileHandler {
+ @Override
+ public void handleFile(InputStream stream) throws Exception {
+ // ignore password protected files
+ if (POIXMLDocumentHandler.isEncrypted(stream)) return;
+
+ XWPFDocument doc = new XWPFDocument(stream);
+
+ new POIXMLDocumentHandler().handlePOIXMLDocument(doc);
+ }
+
+ // a test-case to test this locally without executing the full TestAllFiles
+ @Test
+ public void test() throws Exception {
+ File file = new File("test-data/document/51921-Word-Crash067.docx");
+
+ InputStream stream = new PushbackInputStream(new FileInputStream(file), 100000);
+ try {
+ handleFile(stream);
+ } finally {
+ stream.close();
+ }
+
+ handleExtracting(file);
+ }
+
+}
\ No newline at end of file
diff --git a/trunk/src/java/org/apache/poi/EmptyFileException.java b/trunk/src/java/org/apache/poi/EmptyFileException.java
new file mode 100644
index 000000000..ddbd14280
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/EmptyFileException.java
@@ -0,0 +1,28 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+/**
+ * Exception thrown if an Empty (zero byte) file is supplied
+ */
+public class EmptyFileException extends IllegalArgumentException {
+ private static final long serialVersionUID = 1536449292174360166L;
+
+ public EmptyFileException() {
+ super("The supplied file was empty (zero bytes long)");
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/java/org/apache/poi/EncryptedDocumentException.java b/trunk/src/java/org/apache/poi/EncryptedDocumentException.java
new file mode 100644
index 000000000..4ac6123e4
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/EncryptedDocumentException.java
@@ -0,0 +1,34 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+public class EncryptedDocumentException extends IllegalStateException
+{
+ private static final long serialVersionUID = 7276950444540469193L;
+
+ public EncryptedDocumentException(String s) {
+ super(s);
+ }
+
+ public EncryptedDocumentException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public EncryptedDocumentException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/OldFileFormatException.java b/trunk/src/java/org/apache/poi/OldFileFormatException.java
new file mode 100644
index 000000000..3c01f5459
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/OldFileFormatException.java
@@ -0,0 +1,29 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+/**
+ * Base class of all the exceptions that POI throws in the event
+ * that it's given a file that's older than currently supported.
+ */
+public abstract class OldFileFormatException extends UnsupportedFileFormatException {
+ private static final long serialVersionUID = 7849681804154571175L;
+
+ public OldFileFormatException(String s) {
+ super(s);
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/java/org/apache/poi/POIDocument.java b/trunk/src/java/org/apache/poi/POIDocument.java
new file mode 100644
index 000000000..87c6a14f6
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/POIDocument.java
@@ -0,0 +1,400 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.MutablePropertySet;
+import org.apache.poi.hpsf.PropertySet;
+import org.apache.poi.hpsf.PropertySetFactory;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * This holds the common functionality for all POI
+ * Document classes.
+ * Currently, this relates to Document Information Properties
+ */
+public abstract class POIDocument implements Closeable {
+ /** Holds metadata on our document */
+ private SummaryInformation sInf;
+ /** Holds further metadata on our document */
+ private DocumentSummaryInformation dsInf;
+ /** The directory that our document lives in */
+ protected DirectoryNode directory;
+
+ /** For our own logging use */
+ private static final POILogger logger = POILogFactory.getLogger(POIDocument.class);
+
+ /* Have the property streams been read yet? (Only done on-demand) */
+ private boolean initialized = false;
+
+ /**
+ * Constructs a POIDocument with the given directory node.
+ *
+ * @param dir The {@link DirectoryNode} where information is read from.
+ */
+ protected POIDocument(DirectoryNode dir) {
+ this.directory = dir;
+ }
+
+ /**
+ * Constructs from an old-style OPOIFS
+ *
+ * @param fs the filesystem the document is read from
+ */
+ protected POIDocument(OPOIFSFileSystem fs) {
+ this(fs.getRoot());
+ }
+ /**
+ * Constructs from an old-style OPOIFS
+ *
+ * @param fs the filesystem the document is read from
+ */
+ protected POIDocument(NPOIFSFileSystem fs) {
+ this(fs.getRoot());
+ }
+ /**
+ * Constructs from the default POIFS
+ *
+ * @param fs the filesystem the document is read from
+ */
+ protected POIDocument(POIFSFileSystem fs) {
+ this(fs.getRoot());
+ }
+
+ /**
+ * Fetch the Document Summary Information of the document
+ *
+ * @return The Document Summary Information or null
+ * if it could not be read for this document.
+ */
+ public DocumentSummaryInformation getDocumentSummaryInformation() {
+ if(!initialized) readProperties();
+ return dsInf;
+ }
+
+ /**
+ * Fetch the Summary Information of the document
+ *
+ * @return The Summary information for the document or null
+ * if it could not be read for this document.
+ */
+ public SummaryInformation getSummaryInformation() {
+ if(!initialized) readProperties();
+ return sInf;
+ }
+
+ /**
+ * Will create whichever of SummaryInformation
+ * and DocumentSummaryInformation (HPSF) properties
+ * are not already part of your document.
+ * This is normally useful when creating a new
+ * document from scratch.
+ * If the information properties are already there,
+ * then nothing will happen.
+ */
+ public void createInformationProperties() {
+ if (!initialized) readProperties();
+ if (sInf == null) {
+ sInf = PropertySetFactory.newSummaryInformation();
+ }
+ if (dsInf == null) {
+ dsInf = PropertySetFactory.newDocumentSummaryInformation();
+ }
+ }
+
+ /**
+ * Find, and create objects for, the standard
+ * Document Information Properties (HPSF).
+ * If a given property set is missing or corrupt,
+ * it will remain null;
+ */
+ protected void readProperties() {
+ PropertySet ps;
+
+ // DocumentSummaryInformation
+ ps = getPropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+ if (ps instanceof DocumentSummaryInformation) {
+ dsInf = (DocumentSummaryInformation)ps;
+ } else if (ps != null) {
+ logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back with wrong class - ", ps.getClass());
+ } else {
+ logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back as null");
+ }
+
+ // SummaryInformation
+ ps = getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME);
+ if (ps instanceof SummaryInformation) {
+ sInf = (SummaryInformation)ps;
+ } else if (ps != null) {
+ logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass());
+ } else {
+ logger.log(POILogger.WARN, "SummaryInformation property set came back as null");
+ }
+
+ // Mark the fact that we've now loaded up the properties
+ initialized = true;
+ }
+
+ /**
+ * For a given named property entry, either return it or null if
+ * if it wasn't found
+ *
+ * @param setName The property to read
+ * @return The value of the given property or null if it wasn't found.
+ */
+ protected PropertySet getPropertySet(String setName) {
+ return getPropertySet(setName, null);
+ }
+
+ /**
+ * For a given named property entry, either return it or null if
+ * if it wasn't found
+ *
+ * @param setName The property to read
+ * @param encryptionInfo the encryption descriptor in case of cryptoAPI encryption
+ * @return The value of the given property or null if it wasn't found.
+ */
+ protected PropertySet getPropertySet(String setName, EncryptionInfo encryptionInfo) {
+ DirectoryNode dirNode = directory;
+
+ NPOIFSFileSystem encPoifs = null;
+ String step = "getting";
+ try {
+ if (encryptionInfo != null) {
+ step = "getting encrypted";
+ InputStream is = encryptionInfo.getDecryptor().getDataStream(directory);
+ try {
+ encPoifs = new NPOIFSFileSystem(is);
+ dirNode = encPoifs.getRoot();
+ } finally {
+ is.close();
+ }
+ }
+
+ //directory can be null when creating new documents
+ if (dirNode == null || !dirNode.hasEntry(setName)) {
+ return null;
+ }
+
+ // Find the entry, and get an input stream for it
+ step = "getting";
+ DocumentInputStream dis = dirNode.createDocumentInputStream( dirNode.getEntry(setName) );
+ try {
+ // Create the Property Set
+ step = "creating";
+ return PropertySetFactory.create(dis);
+ } finally {
+ dis.close();
+ }
+ } catch (Exception e) {
+ logger.log(POILogger.WARN, "Error "+step+" property set with name " + setName, e);
+ return null;
+ } finally {
+ if (encPoifs != null) {
+ try {
+ encPoifs.close();
+ } catch(IOException e) {
+ logger.log(POILogger.WARN, "Error closing encrypted property poifs", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Writes out the updated standard Document Information Properties (HPSF)
+ * into the currently open NPOIFSFileSystem
+ *
+ * @throws IOException if an error when writing to the open
+ * {@link NPOIFSFileSystem} occurs
+ */
+ protected void writeProperties() throws IOException {
+ validateInPlaceWritePossible();
+ writeProperties(directory.getFileSystem(), null);
+ }
+
+ /**
+ * Writes out the standard Document Information Properties (HPSF)
+ * @param outFS the POIFSFileSystem to write the properties into
+ *
+ * @throws IOException if an error when writing to the
+ * {@link NPOIFSFileSystem} occurs
+ */
+ protected void writeProperties(NPOIFSFileSystem outFS) throws IOException {
+ writeProperties(outFS, null);
+ }
+ /**
+ * Writes out the standard Document Information Properties (HPSF)
+ * @param outFS the NPOIFSFileSystem to write the properties into
+ * @param writtenEntries a list of POIFS entries to add the property names too
+ *
+ * @throws IOException if an error when writing to the
+ * {@link NPOIFSFileSystem} occurs
+ */
+ protected void writeProperties(NPOIFSFileSystem outFS, List writtenEntries) throws IOException {
+ SummaryInformation si = getSummaryInformation();
+ if (si != null) {
+ writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, outFS);
+ if(writtenEntries != null) {
+ writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME);
+ }
+ }
+ DocumentSummaryInformation dsi = getDocumentSummaryInformation();
+ if (dsi != null) {
+ writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, outFS);
+ if(writtenEntries != null) {
+ writtenEntries.add(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+ }
+ }
+ }
+
+ /**
+ * Writes out a given ProperySet
+ * @param name the (POIFS Level) name of the property to write
+ * @param set the PropertySet to write out
+ * @param outFS the NPOIFSFileSystem to write the property into
+ *
+ * @throws IOException if an error when writing to the
+ * {@link NPOIFSFileSystem} occurs
+ */
+ protected void writePropertySet(String name, PropertySet set, NPOIFSFileSystem outFS) throws IOException {
+ try {
+ MutablePropertySet mSet = new MutablePropertySet(set);
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ mSet.write(bOut);
+ byte[] data = bOut.toByteArray();
+ ByteArrayInputStream bIn = new ByteArrayInputStream(data);
+
+ // Create or Update the Property Set stream in the POIFS
+ outFS.createOrUpdateDocument(bIn, name);
+
+ logger.log(POILogger.INFO, "Wrote property set " + name + " of size " + data.length);
+ } catch(org.apache.poi.hpsf.WritingNotSupportedException wnse) {
+ logger.log( POILogger.ERROR, "Couldn't write property set with name " + name + " as not supported by HPSF yet");
+ }
+ }
+
+ /**
+ * Called during a {@link #write()} to ensure that the Document (and
+ * associated {@link POIFSFileSystem}) was opened in a way compatible
+ * with an in-place write.
+ *
+ * @throws IllegalStateException if the document was opened suitably
+ */
+ protected void validateInPlaceWritePossible() throws IllegalStateException {
+ if (directory == null) {
+ throw new IllegalStateException("Newly created Document, cannot save in-place");
+ }
+ if (directory.getParent() != null) {
+ throw new IllegalStateException("This is not the root Document, cannot save embedded resource in-place");
+ }
+ if (directory.getFileSystem() == null ||
+ !directory.getFileSystem().isInPlaceWriteable()) {
+ throw new IllegalStateException("Opened read-only or via an InputStream, a Writeable File is required");
+ }
+ }
+
+ /**
+ * Writes the document out to the currently open {@link File}, via the
+ * writeable {@link POIFSFileSystem} it was opened from.
+ *
+ *
This will fail (with an {@link IllegalStateException} if the
+ * document was opened read-only, opened from an {@link InputStream}
+ * instead of a File, or if this is not the root document. For those cases,
+ * you must use {@link #write(OutputStream)} or {@link #write(File)} to
+ * write to a brand new document.
+ *
+ * @since POI 3.15 beta 3
+ *
+ * @throws IOException thrown on errors writing to the file
+ * @throws IllegalStateException if this isn't from a writable File
+ */
+ public abstract void write() throws IOException;
+
+ /**
+ * Writes the document out to the specified new {@link File}. If the file
+ * exists, it will be replaced, otherwise a new one will be created
+ *
+ * @since POI 3.15 beta 3
+ *
+ * @param newFile The new File to write to.
+ *
+ * @throws IOException thrown on errors writing to the file
+ */
+ public abstract void write(File newFile) throws IOException;
+
+ /**
+ * Writes the document out to the specified output stream. The
+ * stream is not closed as part of this operation.
+ *
+ * Note - if the Document was opened from a {@link File} rather
+ * than an {@link InputStream}, you must write out using
+ * {@link #write()} or to a different File. Overwriting the currently
+ * open file via an OutputStream isn't possible.
+ *
+ * If {@code stream} is a {@link java.io.FileOutputStream} on a networked drive
+ * or has a high cost/latency associated with each written byte,
+ * consider wrapping the OutputStream in a {@link java.io.BufferedOutputStream}
+ * to improve write performance, or use {@link #write()} / {@link #write(File)}
+ * if possible.
+ *
+ * @param out The stream to write to.
+ *
+ * @throws IOException thrown on errors writing to the stream
+ */
+ public abstract void write(OutputStream out) throws IOException;
+
+ /**
+ * Closes the underlying {@link NPOIFSFileSystem} from which
+ * the document was read, if any. Has no effect on documents
+ * opened from an InputStream, or newly created ones.
+ *
Once {@link #close()} has been called, no further operations
+ * should be called on the document.
+ */
+ public void close() throws IOException {
+ if (directory != null) {
+ if (directory.getNFileSystem() != null) {
+ directory.getNFileSystem().close();
+ directory = null;
+ }
+ }
+ }
+
+ @Internal
+ public DirectoryNode getDirectory() {
+ return directory;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/POIOLE2TextExtractor.java b/trunk/src/java/org/apache/poi/POIOLE2TextExtractor.java
new file mode 100644
index 000000000..24bbb9a65
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/POIOLE2TextExtractor.java
@@ -0,0 +1,103 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.extractor.HPSFPropertiesExtractor;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+
+/**
+ * Common Parent for OLE2 based Text Extractors
+ * of POI Documents, such as .doc, .xls
+ * You will typically find the implementation of
+ * a given format's text extractor under
+ * org.apache.poi.[format].extractor .
+ *
+ * @see org.apache.poi.hssf.extractor.ExcelExtractor
+ * @see org.apache.poi.hslf.extractor.PowerPointExtractor
+ * @see org.apache.poi.hdgf.extractor.VisioTextExtractor
+ * @see org.apache.poi.hwpf.extractor.WordExtractor
+ */
+public abstract class POIOLE2TextExtractor extends POITextExtractor {
+ /** The POIDocument that's open */
+ protected POIDocument document;
+
+ /**
+ * Creates a new text extractor for the given document
+ *
+ * @param document The POIDocument to use in this extractor.
+ */
+ public POIOLE2TextExtractor(POIDocument document) {
+ this.document = document;
+
+ // Ensure any underlying resources, such as open files,
+ // will get cleaned up if the user calls #close()
+ setFilesystem(document);
+ }
+
+ /**
+ * Creates a new text extractor, using the same
+ * document as another text extractor. Normally
+ * only used by properties extractors.
+ *
+ * @param otherExtractor the extractor which document to be used
+ */
+ protected POIOLE2TextExtractor(POIOLE2TextExtractor otherExtractor) {
+ this.document = otherExtractor.document;
+ }
+
+ /**
+ * Returns the document information metadata for the document
+ *
+ * @return The Document Summary Information or null
+ * if it could not be read for this document.
+ */
+ public DocumentSummaryInformation getDocSummaryInformation() {
+ return document.getDocumentSummaryInformation();
+ }
+ /**
+ * Returns the summary information metadata for the document.
+ *
+ * @return The Summary information for the document or null
+ * if it could not be read for this document.
+ */
+ public SummaryInformation getSummaryInformation() {
+ return document.getSummaryInformation();
+ }
+
+ /**
+ * Returns an HPSF powered text extractor for the
+ * document properties metadata, such as title and author.
+ *
+ * @return an instance of POIExtractor that can extract meta-data.
+ */
+ @Override
+ public POITextExtractor getMetadataTextExtractor() {
+ return new HPSFPropertiesExtractor(this);
+ }
+
+ /**
+ * Return the underlying DirectoryEntry of this document.
+ *
+ * @return the DirectoryEntry that is associated with the POIDocument of this extractor.
+ */
+ public DirectoryEntry getRoot()
+ {
+ return document.directory;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/POITextExtractor.java b/trunk/src/java/org/apache/poi/POITextExtractor.java
new file mode 100644
index 000000000..83e26be94
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/POITextExtractor.java
@@ -0,0 +1,77 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Common Parent for Text Extractors
+ * of POI Documents.
+ * You will typically find the implementation of
+ * a given format's text extractor under
+ * org.apache.poi.[format].extractor .
+ *
+ * @see org.apache.poi.hssf.extractor.ExcelExtractor
+ * @see org.apache.poi.hslf.extractor.PowerPointExtractor
+ * @see org.apache.poi.hdgf.extractor.VisioTextExtractor
+ * @see org.apache.poi.hwpf.extractor.WordExtractor
+ */
+public abstract class POITextExtractor implements Closeable {
+ private Closeable fsToClose = null;
+
+ /**
+ * Retrieves all the text from the document.
+ * How cells, paragraphs etc are separated in the text
+ * is implementation specific - see the javadocs for
+ * a specific project for details.
+ * @return All the text from the document
+ */
+ public abstract String getText();
+
+ /**
+ * Returns another text extractor, which is able to
+ * output the textual content of the document
+ * metadata / properties, such as author and title.
+ *
+ * @return the metadata and text extractor
+ */
+ public abstract POITextExtractor getMetadataTextExtractor();
+
+ /**
+ * Used to ensure file handle cleanup.
+ *
+ * @param fs filesystem to close
+ */
+ public void setFilesystem(Closeable fs) {
+ fsToClose = fs;
+ }
+
+ /**
+ * Allows to free resources of the Extractor as soon as
+ * it is not needed any more. This may include closing
+ * open file handles and freeing memory.
+ *
+ * The Extractor cannot be used after close has been called.
+ */
+ @Override
+ public void close() throws IOException {
+ if(fsToClose != null) {
+ fsToClose.close();
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/UnsupportedFileFormatException.java b/trunk/src/java/org/apache/poi/UnsupportedFileFormatException.java
new file mode 100644
index 000000000..a8caebb4b
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/UnsupportedFileFormatException.java
@@ -0,0 +1,29 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi;
+
+/**
+ * Base class of all the exceptions that POI throws in the event
+ * that it's given a file that isn't supported
+ */
+public abstract class UnsupportedFileFormatException extends IllegalArgumentException {
+ private static final long serialVersionUID = -8281969197282030046L;
+
+ public UnsupportedFileFormatException(String s) {
+ super(s);
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/java/org/apache/poi/common/usermodel/Hyperlink.java b/trunk/src/java/org/apache/poi/common/usermodel/Hyperlink.java
new file mode 100644
index 000000000..132a3eb4c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/common/usermodel/Hyperlink.java
@@ -0,0 +1,96 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.common.usermodel;
+
+/**
+ * Represents a hyperlink.
+ */
+public interface Hyperlink {
+ /**
+ * Link to an existing file or web page
+ *
+ * @deprecated POI 3.15 beta 3. Use {@link HyperlinkType#URL} instead.
+ */
+ public static final int LINK_URL = 1; // HyperlinkType.URL.getCode()
+
+ /**
+ * Link to a place in this document
+ *
+ * @deprecated POI 3.15 beta 3. Use {@link HyperlinkType#DOCUMENT} instead.
+ */
+ public static final int LINK_DOCUMENT = 2; // HyperlinkType.DOCUMENT.getCode()
+
+ /**
+ * Link to an E-mail address
+ *
+ * @deprecated POI 3.15 beta 3. Use {@link HyperlinkType#EMAIL} instead.
+ */
+ public static final int LINK_EMAIL = 3; // HyperlinkType.EMAIL.getCode()
+
+ /**
+ * Link to an file
+ *
+ * @deprecated POI 3.15 beta 3. Use {@link HyperlinkType#FILE} instead.
+ */
+ public static final int LINK_FILE = 4; // HyperlinkType.FILE.getCode()
+
+
+ /**
+ * Hyperlink address. Depending on the hyperlink type it can be URL, e-mail, path to a file, etc.
+ *
+ * @return the address of this hyperlink
+ */
+ public String getAddress();
+
+ /**
+ * Hyperlink address. Depending on the hyperlink type it can be URL, e-mail, path to a file, etc.
+ *
+ * @param address the address of this hyperlink
+ */
+ public void setAddress(String address);
+
+ /**
+ * Return text label for this hyperlink
+ *
+ * @return text to display
+ */
+ public String getLabel();
+
+ /**
+ * Sets text label for this hyperlink
+ *
+ * @param label text label for this hyperlink
+ */
+ public void setLabel(String label);
+
+ /**
+ * Return the type of this hyperlink
+ *
+ * @return the type of this hyperlink
+ * @see HyperlinkType#forInt(int)
+ * @deprecated POI 3.15 beta 3. Use {@link #getTypeEnum()}
+ */
+ public int getType();
+
+ /**
+ * Return the type of this hyperlink
+ *
+ * @return the type of this hyperlink
+ * @since POI 3.15 beta 3
+ */
+ public HyperlinkType getTypeEnum();
+}
diff --git a/trunk/src/java/org/apache/poi/common/usermodel/HyperlinkType.java b/trunk/src/java/org/apache/poi/common/usermodel/HyperlinkType.java
new file mode 100644
index 000000000..dc88dc560
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/common/usermodel/HyperlinkType.java
@@ -0,0 +1,107 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.common.usermodel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.util.Internal;
+
+/**
+ * @since POI 3.15 beta 3
+ */
+public enum HyperlinkType {
+ /** Not a hyperlink */
+ @Internal
+ NONE(-1),
+
+ /**
+ * Link to an existing file or web page
+ */
+ URL(1),
+
+ /**
+ * Link to a place in this document
+ */
+ DOCUMENT(2),
+
+ /**
+ * Link to an E-mail address
+ */
+ EMAIL(3),
+
+ /**
+ * Link to a file
+ */
+ FILE(4);
+
+
+ /** @deprecated POI 3.15 beta 3 */
+ @Internal(since="3.15 beta 3")
+ @Deprecated
+ private final int code;
+
+ /**
+ * The codes don't have any real meaning.
+ * There bytes that are read in and written out from HSSF, HSLF, XSSF, and XSLF are different
+ * that the codes here.
+ * These codes only exist to assist in transitioning from using ints to enums.
+ *
+ * @param code
+ * @deprecated POI 3.15 beta 3
+ */
+ @Internal(since="3.15 beta 3")
+ @Deprecated
+ private HyperlinkType(int code) {
+ this.code = code;
+ }
+
+ private static final Map map = new HashMap();
+ static {
+ for (HyperlinkType type : values()) {
+ map.put(type.getCode(), type);
+ }
+ }
+
+ /**
+ * @deprecated POI 3.15 beta 3
+ *
+ * @return the old integer code for a HyperlinkType enum
+ */
+ @Internal(since="3.15 beta 3")
+ @Deprecated
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * @deprecated POI 3.15 beta 3
+ *
+ * @param code the old integer code
+ * @return the corresponding HyperlinkEnum, if it exists
+ * @throws IllegalArgumentException if {@code code} is not a valid HyperlinkType.
+ */
+ @Internal(since="3.15 beta 3")
+ @Deprecated
+ public static HyperlinkType forInt(int code) {
+ HyperlinkType type = map.get(code);
+ if (type == null) {
+ throw new IllegalArgumentException("Invalid type: " + code);
+ }
+ return type;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java b/trunk/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java
new file mode 100644
index 000000000..c8d2c95c8
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java
@@ -0,0 +1,227 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ddf;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Common abstract class for {@link EscherOptRecord} and
+ * {@link EscherTertiaryOptRecord}
+ */
+public abstract class AbstractEscherOptRecord extends EscherRecord
+{
+ protected List properties = new ArrayList();
+
+ /**
+ * Add a property to this record.
+ *
+ * @param prop the escher property to add
+ */
+ public void addEscherProperty( EscherProperty prop )
+ {
+ properties.add( prop );
+ }
+
+ @Override
+ public int fillFields( byte[] data, int offset,
+ EscherRecordFactory recordFactory )
+ {
+ int bytesRemaining = readHeader( data, offset );
+ short propertiesCount = readInstance( data, offset );
+ int pos = offset + 8;
+
+ EscherPropertyFactory f = new EscherPropertyFactory();
+ properties = f.createProperties( data, pos, propertiesCount );
+ return bytesRemaining + 8;
+ }
+
+ /**
+ * The list of properties stored by this record.
+ *
+ * @return the list of properties
+ */
+ public List getEscherProperties()
+ {
+ return properties;
+ }
+
+ /**
+ * The list of properties stored by this record.
+ *
+ * @param index the ordinal index of the property
+ * @return the escher property
+ */
+ public EscherProperty getEscherProperty( int index )
+ {
+ return properties.get( index );
+ }
+
+
+ private int getPropertiesSize()
+ {
+ int totalSize = 0;
+ for ( EscherProperty property : properties )
+ {
+ totalSize += property.getPropertySize();
+ }
+
+ return totalSize;
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + getPropertiesSize();
+ }
+
+ public T lookup( int propId )
+ {
+ for ( EscherProperty prop : properties )
+ {
+ if ( prop.getPropertyNumber() == propId )
+ {
+ @SuppressWarnings( "unchecked" )
+ final T result = (T) prop;
+ return result;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data,
+ EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, getPropertiesSize() );
+ int pos = offset + 8;
+ for ( EscherProperty property : properties )
+ {
+ pos += property.serializeSimplePart( data, pos );
+ }
+ for ( EscherProperty property : properties )
+ {
+ pos += property.serializeComplexPart( data, pos );
+ }
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ /**
+ * Records should be sorted by property number before being stored.
+ */
+ public void sortProperties()
+ {
+ Collections.sort( properties, new Comparator()
+ {
+ @Override
+ public int compare( EscherProperty p1, EscherProperty p2 )
+ {
+ short s1 = p1.getPropertyNumber();
+ short s2 = p2.getPropertyNumber();
+ return s1 < s2 ? -1 : s1 == s2 ? 0 : 1;
+ }
+ } );
+ }
+
+ /**
+ * Set an escher property. If a property with given propId already
+ exists it is replaced.
+ *
+ * @param value the property to set.
+ */
+ public void setEscherProperty(EscherProperty value){
+ for ( Iterator iterator =
+ properties.iterator(); iterator.hasNext(); ) {
+ EscherProperty prop = iterator.next();
+ if (prop.getId() == value.getId()){
+ iterator.remove();
+ }
+ }
+ properties.add( value );
+ sortProperties();
+ }
+
+ public void removeEscherProperty(int num){
+ for ( Iterator iterator = getEscherProperties().iterator(); iterator.hasNext(); ) {
+ EscherProperty prop = iterator.next();
+ if (prop.getPropertyNumber() == num){
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Retrieve the string representation of this record.
+ */
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append( getClass().getName() );
+ stringBuilder.append( ":" );
+ stringBuilder.append( nl );
+ stringBuilder.append( " isContainer: " );
+ stringBuilder.append( isContainerRecord() );
+ stringBuilder.append( nl );
+ stringBuilder.append( " version: 0x" );
+ stringBuilder.append( HexDump.toHex( getVersion() ) );
+ stringBuilder.append( nl );
+ stringBuilder.append( " instance: 0x" );
+ stringBuilder.append( HexDump.toHex( getInstance() ) );
+ stringBuilder.append( nl );
+ stringBuilder.append( " recordId: 0x" );
+ stringBuilder.append( HexDump.toHex( getRecordId() ) );
+ stringBuilder.append( nl );
+ stringBuilder.append( " numchildren: " );
+ stringBuilder.append( getChildRecords().size() );
+ stringBuilder.append( nl );
+ stringBuilder.append( " properties:" );
+ stringBuilder.append( nl );
+
+ for ( EscherProperty property : properties )
+ {
+ stringBuilder.append(" ").append(property.toString()).append(nl);
+ }
+
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(),
+ HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())));
+ for (EscherProperty property: getEscherProperties()){
+ builder.append(property.toXml(tab+"\t"));
+ }
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java b/trunk/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java
new file mode 100644
index 000000000..4342708bf
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java
@@ -0,0 +1,150 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Generates escher records when provided the byte array containing those records.
+ *
+ * @see EscherRecordFactory
+ */
+public class DefaultEscherRecordFactory implements EscherRecordFactory {
+ private static Class>[] escherRecordClasses = { EscherBSERecord.class,
+ EscherOptRecord.class, EscherTertiaryOptRecord.class,
+ EscherClientAnchorRecord.class, EscherDgRecord.class,
+ EscherSpgrRecord.class, EscherSpRecord.class,
+ EscherClientDataRecord.class, EscherDggRecord.class,
+ EscherSplitMenuColorsRecord.class, EscherChildAnchorRecord.class,
+ EscherTextboxRecord.class };
+ private static Map> recordsMap = recordsToMap( escherRecordClasses );
+
+ /**
+ * Creates an instance of the escher record factory
+ */
+ public DefaultEscherRecordFactory() {
+ // no instance initialisation
+ }
+
+ @Override
+ public EscherRecord createRecord(byte[] data, int offset) {
+ short options = LittleEndian.getShort( data, offset );
+ short recordId = LittleEndian.getShort( data, offset + 2 );
+ // int remainingBytes = LittleEndian.getInt( data, offset + 4 );
+
+ // Options of 0x000F means container record
+ // However, EscherTextboxRecord are containers of records for the
+ // host application, not of other Escher records, so treat them
+ // differently
+ if (isContainer(options, recordId)) {
+ EscherContainerRecord r = new EscherContainerRecord();
+ r.setRecordId( recordId );
+ r.setOptions( options );
+ return r;
+ }
+
+ if (recordId >= EscherBlipRecord.RECORD_ID_START
+ && recordId <= EscherBlipRecord.RECORD_ID_END) {
+ EscherBlipRecord r;
+ if (recordId == EscherBitmapBlip.RECORD_ID_DIB ||
+ recordId == EscherBitmapBlip.RECORD_ID_JPEG ||
+ recordId == EscherBitmapBlip.RECORD_ID_PNG)
+ {
+ r = new EscherBitmapBlip();
+ }
+ else if (recordId == EscherMetafileBlip.RECORD_ID_EMF ||
+ recordId == EscherMetafileBlip.RECORD_ID_WMF ||
+ recordId == EscherMetafileBlip.RECORD_ID_PICT)
+ {
+ r = new EscherMetafileBlip();
+ } else {
+ r = new EscherBlipRecord();
+ }
+ r.setRecordId( recordId );
+ r.setOptions( options );
+ return r;
+ }
+
+ Constructor extends EscherRecord> recordConstructor = recordsMap.get(Short.valueOf(recordId));
+ final EscherRecord escherRecord;
+ if (recordConstructor == null) {
+ return new UnknownEscherRecord();
+ }
+ try {
+ escherRecord = recordConstructor.newInstance();
+ } catch (Exception e) {
+ return new UnknownEscherRecord();
+ }
+ escherRecord.setRecordId(recordId);
+ escherRecord.setOptions(options);
+ return escherRecord;
+ }
+
+ /**
+ * Converts from a list of classes into a map that contains the record id as the key and
+ * the Constructor in the value part of the map. It does this by using reflection to look up
+ * the RECORD_ID field then using reflection again to find a reference to the constructor.
+ *
+ * @param recClasses The records to convert
+ * @return The map containing the id/constructor pairs.
+ */
+ protected static Map> recordsToMap(Class>[] recClasses) {
+ Map> result = new HashMap>();
+ final Class>[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ for (Class> recClass : recClasses) {
+ @SuppressWarnings("unchecked")
+ Class extends EscherRecord> recCls = (Class extends EscherRecord>) recClass;
+ short sid;
+ try {
+ sid = recCls.getField("RECORD_ID").getShort(null);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchFieldException e) {
+ throw new RuntimeException(e);
+ }
+ Constructor extends EscherRecord> constructor;
+ try {
+ constructor = recCls.getConstructor(EMPTY_CLASS_ARRAY);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ result.put(Short.valueOf(sid), constructor);
+ }
+ return result;
+ }
+
+ public static boolean isContainer(short options, short recordId){
+ if(recordId >= EscherContainerRecord.DGG_CONTAINER && recordId
+ <= EscherContainerRecord.SOLVER_CONTAINER){
+ return true;
+ } else {
+ if (recordId == EscherTextboxRecord.RECORD_ID) {
+ return false;
+ } else {
+ return ( options & (short) 0x000F ) == (short) 0x000F;
+ }
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherArrayProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherArrayProperty.java
new file mode 100644
index 000000000..bf7211d3c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherArrayProperty.java
@@ -0,0 +1,233 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Escher array properties are the most wierd construction ever invented
+ * with all sorts of special cases. I'm hopeful I've got them all.
+ */
+public final class EscherArrayProperty extends EscherComplexProperty implements Iterable {
+ /**
+ * The size of the header that goes at the
+ * start of the array, before the data
+ */
+ private static final int FIXED_SIZE = 3 * 2;
+ /**
+ * Normally, the size recorded in the simple data (for the complex
+ * data) includes the size of the header.
+ * There are a few cases when it doesn't though...
+ */
+ private boolean sizeIncludesHeaderSize = true;
+
+ /**
+ * When reading a property from data stream remember if the complex part is empty and set this flag.
+ */
+ private boolean emptyComplexPart = false;
+
+ public EscherArrayProperty(short id, byte[] complexData) {
+ super(id, checkComplexData(complexData));
+ emptyComplexPart = complexData.length == 0;
+ }
+
+ public EscherArrayProperty(short propertyNumber, boolean isBlipId, byte[] complexData) {
+ super(propertyNumber, isBlipId, checkComplexData(complexData));
+ }
+
+ private static byte[] checkComplexData(byte[] complexData) {
+ if (complexData == null || complexData.length == 0) {
+ return new byte[6];
+ }
+
+ return complexData;
+ }
+
+ public int getNumberOfElementsInArray() {
+ return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 0);
+ }
+
+ public void setNumberOfElementsInArray(int numberOfElements) {
+ int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
+ if (expectedArraySize != _complexData.length) {
+ byte[] newArray = new byte[expectedArraySize];
+ System.arraycopy(_complexData, 0, newArray, 0, _complexData.length);
+ _complexData = newArray;
+ }
+ LittleEndian.putShort(_complexData, 0, (short) numberOfElements);
+ }
+
+ public int getNumberOfElementsInMemory() {
+ return (emptyComplexPart) ? 0 : LittleEndian.getUShort(_complexData, 2);
+ }
+
+ public void setNumberOfElementsInMemory(int numberOfElements) {
+ int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
+ if (expectedArraySize != _complexData.length) {
+ byte[] newArray = new byte[expectedArraySize];
+ System.arraycopy(_complexData, 0, newArray, 0, expectedArraySize);
+ _complexData = newArray;
+ }
+ LittleEndian.putShort(_complexData, 2, (short) numberOfElements);
+ }
+
+ public short getSizeOfElements() {
+ return (emptyComplexPart) ? 0 : LittleEndian.getShort( _complexData, 4 );
+ }
+
+ public void setSizeOfElements(int sizeOfElements) {
+ LittleEndian.putShort( _complexData, 4, (short) sizeOfElements );
+
+ int expectedArraySize = getNumberOfElementsInArray() * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
+ if (expectedArraySize != _complexData.length) {
+ // Keep just the first 6 bytes. The rest is no good to us anyway.
+ byte[] newArray = new byte[expectedArraySize];
+ System.arraycopy( _complexData, 0, newArray, 0, 6 );
+ _complexData = newArray;
+ }
+ }
+
+ public byte[] getElement(int index) {
+ int actualSize = getActualSizeOfElements(getSizeOfElements());
+ byte[] result = new byte[actualSize];
+ System.arraycopy(_complexData, FIXED_SIZE + index * actualSize, result, 0, result.length );
+ return result;
+ }
+
+ public void setElement(int index, byte[] element) {
+ int actualSize = getActualSizeOfElements(getSizeOfElements());
+ System.arraycopy( element, 0, _complexData, FIXED_SIZE + index * actualSize, actualSize);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer results = new StringBuffer();
+ results.append(" {EscherArrayProperty:" + '\n');
+ results.append(" Num Elements: " + getNumberOfElementsInArray() + '\n');
+ results.append(" Num Elements In Memory: " + getNumberOfElementsInMemory() + '\n');
+ results.append(" Size of elements: " + getSizeOfElements() + '\n');
+ for (int i = 0; i < getNumberOfElementsInArray(); i++) {
+ results.append(" Element " + i + ": " + HexDump.toHex(getElement(i)) + '\n');
+ }
+ results.append("}" + '\n');
+
+ return "propNum: " + getPropertyNumber()
+ + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() )
+ + ", complex: " + isComplex()
+ + ", blipId: " + isBlipId()
+ + ", data: " + '\n' + results.toString();
+ }
+
+ @Override
+ public String toXml(String tab){
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append("<").append(getClass().getSimpleName()).append(" id=\"0x").append(HexDump.toHex(getId()))
+ .append("\" name=\"").append(getName()).append("\" blipId=\"")
+ .append(isBlipId()).append("\">\n");
+ for (int i = 0; i < getNumberOfElementsInArray(); i++) {
+ builder.append("\t").append(tab).append("").append(HexDump.toHex(getElement(i))).append("\n");
+ }
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * We have this method because the way in which arrays in escher works
+ * is screwed for seemly arbitary reasons. While most properties are
+ * fairly consistent and have a predictable array size, escher arrays
+ * have special cases.
+ *
+ * @param data The data array containing the escher array information
+ * @param offset The offset into the array to start reading from.
+ * @return the number of bytes used by this complex property.
+ */
+ public int setArrayData(byte[] data, int offset) {
+ if (emptyComplexPart){
+ _complexData = new byte[0];
+ } else {
+ short numElements = LittleEndian.getShort(data, offset);
+ // LittleEndian.getShort(data, offset + 2); // numReserved
+ short sizeOfElements = LittleEndian.getShort(data, offset + 4);
+
+ int arraySize = getActualSizeOfElements(sizeOfElements) * numElements;
+ if (arraySize == _complexData.length) {
+ // The stored data size in the simple block excludes the header size
+ _complexData = new byte[arraySize + 6];
+ sizeIncludesHeaderSize = false;
+ }
+ System.arraycopy(data, offset, _complexData, 0, _complexData.length );
+ }
+ return _complexData.length;
+ }
+
+ /**
+ * Serializes the simple part of this property. ie the first 6 bytes.
+ *
+ * Needs special code to handle the case when the size doesn't
+ * include the size of the header block
+ */
+ @Override
+ public int serializeSimplePart(byte[] data, int pos) {
+ LittleEndian.putShort(data, pos, getId());
+ int recordSize = _complexData.length;
+ if(!sizeIncludesHeaderSize) {
+ recordSize -= 6;
+ }
+ LittleEndian.putInt(data, pos + 2, recordSize);
+ return 6;
+ }
+
+ /**
+ * Sometimes the element size is stored as a negative number. We
+ * negate it and shift it to get the real value.
+ */
+ private static int getActualSizeOfElements(short sizeOfElements) {
+ if (sizeOfElements < 0) {
+ return (short) ( ( -sizeOfElements ) >> 2 );
+ }
+ return sizeOfElements;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator(){
+ int idx = 0;
+ @Override
+ public boolean hasNext() {
+ return (idx < getNumberOfElementsInArray());
+ }
+
+ @Override
+ public byte[] next() {
+ if (!hasNext()) throw new NoSuchElementException();
+ return getElement(idx++);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("not yet implemented");
+ }
+ };
+ }
+
+
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherBSERecord.java b/trunk/src/java/org/apache/poi/ddf/EscherBSERecord.java
new file mode 100644
index 000000000..776881038
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherBSERecord.java
@@ -0,0 +1,424 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * The BSE record is related closely to the EscherBlipRecord and stores
+ * extra information about the blip. A blip record is actually stored inside
+ * the BSE record even though the BSE record isn't actually a container record.
+ *
+ * @see EscherBlipRecord
+ */
+public final class EscherBSERecord extends EscherRecord {
+ public static final short RECORD_ID = (short) 0xF007;
+ public static final String RECORD_DESCRIPTION = "MsofbtBSE";
+
+ public static final byte BT_ERROR = 0;
+ public static final byte BT_UNKNOWN = 1;
+ public static final byte BT_EMF = 2;
+ public static final byte BT_WMF = 3;
+ public static final byte BT_PICT = 4;
+ public static final byte BT_JPEG = 5;
+ public static final byte BT_PNG = 6;
+ public static final byte BT_DIB = 7;
+
+ private byte field_1_blipTypeWin32;
+ private byte field_2_blipTypeMacOS;
+ private final byte[] field_3_uid = new byte[16];
+ private short field_4_tag;
+ private int field_5_size;
+ private int field_6_ref;
+ private int field_7_offset;
+ private byte field_8_usage;
+ private byte field_9_name;
+ private byte field_10_unused2;
+ private byte field_11_unused3;
+ private EscherBlipRecord field_12_blipRecord;
+
+ private byte[] _remainingData = new byte[0];
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ field_1_blipTypeWin32 = data[pos];
+ field_2_blipTypeMacOS = data[pos + 1];
+ System.arraycopy( data, pos + 2, field_3_uid, 0, 16 );
+ field_4_tag = LittleEndian.getShort( data, pos + 18 );
+ field_5_size = LittleEndian.getInt( data, pos + 20 );
+ field_6_ref = LittleEndian.getInt( data, pos + 24 );
+ field_7_offset = LittleEndian.getInt( data, pos + 28 );
+ field_8_usage = data[pos + 32];
+ field_9_name = data[pos + 33];
+ field_10_unused2 = data[pos + 34];
+ field_11_unused3 = data[pos + 35];
+ bytesRemaining -= 36;
+
+ int bytesRead = 0;
+ if (bytesRemaining > 0) {
+ // Some older escher formats skip this last record
+ field_12_blipRecord = (EscherBlipRecord) recordFactory.createRecord( data, pos + 36 );
+ bytesRead = field_12_blipRecord.fillFields( data, pos + 36, recordFactory );
+ }
+ pos += 36 + bytesRead;
+ bytesRemaining -= bytesRead;
+
+ _remainingData = new byte[bytesRemaining];
+ System.arraycopy( data, pos, _remainingData, 0, bytesRemaining );
+ return bytesRemaining + 8 + 36 + (field_12_blipRecord == null ? 0 : field_12_blipRecord.getRecordSize()) ;
+
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ if (_remainingData == null) {
+ _remainingData = new byte[0];
+ }
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int blipSize = field_12_blipRecord == null ? 0 : field_12_blipRecord.getRecordSize();
+ int remainingBytes = _remainingData.length + 36 + blipSize;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+
+ data[offset + 8] = field_1_blipTypeWin32;
+ data[offset + 9] = field_2_blipTypeMacOS;
+ for ( int i = 0; i < 16; i++ )
+ data[offset + 10 + i] = field_3_uid[i];
+ LittleEndian.putShort( data, offset + 26, field_4_tag );
+ LittleEndian.putInt( data, offset + 28, field_5_size );
+ LittleEndian.putInt( data, offset + 32, field_6_ref );
+ LittleEndian.putInt( data, offset + 36, field_7_offset );
+ data[offset + 40] = field_8_usage;
+ data[offset + 41] = field_9_name;
+ data[offset + 42] = field_10_unused2;
+ data[offset + 43] = field_11_unused3;
+ int bytesWritten = 0;
+ if (field_12_blipRecord != null)
+ {
+ bytesWritten = field_12_blipRecord.serialize( offset + 44, data, new NullEscherSerializationListener() );
+ }
+ System.arraycopy( _remainingData, 0, data, offset + 44 + bytesWritten, _remainingData.length );
+ int pos = offset + 8 + 36 + _remainingData.length + bytesWritten;
+
+ listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this);
+ return pos - offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ int field_12_size = 0;
+ if(field_12_blipRecord != null) {
+ field_12_size = field_12_blipRecord.getRecordSize();
+ }
+ int remaining_size = 0;
+ if(_remainingData != null) {
+ remaining_size = _remainingData.length;
+ }
+ return 8 + 1 + 1 + 16 + 2 + 4 + 4 + 4 + 1 + 1 +
+ 1 + 1 + field_12_size + remaining_size;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "BSE";
+ }
+
+ /**
+ * The expected blip type under windows (failure to match this blip type will result in
+ * Excel converting to this format).
+ *
+ * @return win32 blip type
+ */
+ public byte getBlipTypeWin32() {
+ return field_1_blipTypeWin32;
+ }
+
+ /**
+ * Set the expected win32 blip type
+ *
+ * @param blipTypeWin32 win32 blip type
+ */
+ public void setBlipTypeWin32(byte blipTypeWin32) {
+ field_1_blipTypeWin32 = blipTypeWin32;
+ }
+
+ /**
+ * The expected blip type under MacOS (failure to match this blip type will result in
+ * Excel converting to this format).
+ *
+ * @return MacOS blip type
+ */
+ public byte getBlipTypeMacOS() {
+ return field_2_blipTypeMacOS;
+ }
+
+ /**
+ * Set the expected MacOS blip type
+ *
+ * @param blipTypeMacOS MacOS blip type
+ */
+ public void setBlipTypeMacOS(byte blipTypeMacOS) {
+ field_2_blipTypeMacOS = blipTypeMacOS;
+ }
+
+ /**
+ * 16 byte MD4 checksum.
+ *
+ * @return 16 byte MD4 checksum
+ */
+ public byte[] getUid() {
+ return field_3_uid;
+ }
+
+ /**
+ * 16 byte MD4 checksum.
+ *
+ * @param uid 16 byte MD4 checksum
+ */
+ public void setUid(byte[] uid) {
+ if (uid == null || uid.length != 16) {
+ throw new IllegalArgumentException("uid must be byte[16]");
+ }
+ System.arraycopy(uid, 0, field_3_uid, 0, field_3_uid.length);
+ }
+
+ /**
+ * unused
+ *
+ * @return an unknown tag
+ */
+ public short getTag() {
+ return field_4_tag;
+ }
+
+ /**
+ * unused
+ *
+ * @param tag unknown tag
+ */
+ public void setTag(short tag) {
+ field_4_tag = tag;
+ }
+
+ /**
+ * Blip size in stream.
+ *
+ * @return the blip size
+ */
+ public int getSize() {
+ return field_5_size;
+ }
+
+ /**
+ * Blip size in stream.
+ *
+ * @param size blip size
+ */
+ public void setSize(int size) {
+ field_5_size = size;
+ }
+
+ /**
+ * The reference count of this blip.
+ *
+ * @return the reference count
+ */
+ public int getRef() {
+ return field_6_ref;
+ }
+
+ /**
+ * The reference count of this blip.
+ *
+ * @param ref the reference count
+ */
+ public void setRef(int ref) {
+ field_6_ref = ref;
+ }
+
+ /**
+ * File offset in the delay stream.
+ *
+ * @return the file offset
+ */
+ public int getOffset() {
+ return field_7_offset;
+ }
+
+ /**
+ * File offset in the delay stream.
+ *
+ * @param offset the file offset
+ */
+ public void setOffset(int offset) {
+ field_7_offset = offset;
+ }
+
+ /**
+ * Defines the way this blip is used.
+ *
+ * @return the blip usage
+ */
+ public byte getUsage() {
+ return field_8_usage;
+ }
+
+ /**
+ * Defines the way this blip is used.
+ *
+ * @param usage the blip usae
+ */
+ public void setUsage(byte usage) {
+ field_8_usage = usage;
+ }
+
+ /**
+ * The length in characters of the blip name.
+ *
+ * @return the blip name length
+ */
+ public byte getName() {
+ return field_9_name;
+ }
+
+ /**
+ * The length in characters of the blip name.
+ *
+ * @param name the blip name length
+ */
+ public void setName(byte name) {
+ field_9_name = name;
+ }
+
+ public byte getUnused2() {
+ return field_10_unused2;
+ }
+
+ public void setUnused2(byte unused2) {
+ field_10_unused2 = unused2;
+ }
+
+ public byte getUnused3() {
+ return field_11_unused3;
+ }
+
+ public void setUnused3(byte unused3) {
+ field_11_unused3 = unused3;
+ }
+
+ public EscherBlipRecord getBlipRecord() {
+ return field_12_blipRecord;
+ }
+
+ public void setBlipRecord(EscherBlipRecord blipRecord) {
+ field_12_blipRecord = blipRecord;
+ }
+
+ /**
+ * Any remaining data in this record.
+ *
+ * @return the remaining bytes
+ */
+ public byte[] getRemainingData() {
+ return _remainingData;
+ }
+
+ /**
+ * Any remaining data in this record.
+ *
+ * @param remainingData the remaining bytes
+ */
+ public void setRemainingData(byte[] remainingData) {
+ if (remainingData == null) {
+ _remainingData = new byte[0];
+ } else {
+ _remainingData = remainingData.clone();
+ }
+ }
+
+ @Override
+ public String toString() {
+ String extraData = _remainingData == null ? null : HexDump.toHex(_remainingData, 32);
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex( RECORD_ID ) + '\n' +
+ " Version: 0x" + HexDump.toHex( getVersion() ) + '\n' +
+ " Instance: 0x" + HexDump.toHex( getInstance() ) + '\n' +
+ " BlipTypeWin32: " + field_1_blipTypeWin32 + '\n' +
+ " BlipTypeMacOS: " + field_2_blipTypeMacOS + '\n' +
+ " SUID: " + (field_3_uid == null ? "" : HexDump.toHex(field_3_uid)) + '\n' +
+ " Tag: " + field_4_tag + '\n' +
+ " Size: " + field_5_size + '\n' +
+ " Ref: " + field_6_ref + '\n' +
+ " Offset: " + field_7_offset + '\n' +
+ " Usage: " + field_8_usage + '\n' +
+ " Name: " + field_9_name + '\n' +
+ " Unused2: " + field_10_unused2 + '\n' +
+ " Unused3: " + field_11_unused3 + '\n' +
+ " blipRecord: " + field_12_blipRecord + '\n' +
+ " Extra Data:" + '\n' + extraData;
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(field_1_blipTypeWin32).append("\n")
+ .append(tab).append("\t").append("").append(field_2_blipTypeMacOS).append("\n")
+ .append(tab).append("\t").append("").append(field_3_uid == null ? "" : HexDump.toHex(field_3_uid)).append("\n")
+ .append(tab).append("\t").append("").append(field_4_tag).append("\n")
+ .append(tab).append("\t").append("").append(field_5_size).append("\n")
+ .append(tab).append("\t").append("").append(field_6_ref).append("\n")
+ .append(tab).append("\t").append("").append(field_7_offset).append("\n")
+ .append(tab).append("\t").append("").append(field_8_usage).append("\n")
+ .append(tab).append("\t").append("").append(field_9_name).append("\n")
+ .append(tab).append("\t").append("").append(field_10_unused2).append("\n")
+ .append(tab).append("\t").append("").append(field_11_unused3).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Retrieve the string representation given a blip id.
+ *
+ * @param b the blip type byte-encoded
+ *
+ * @return the blip type as string
+ */
+ public static String getBlipType(byte b) {
+ switch (b) {
+ case BT_ERROR: return " ERROR";
+ case BT_UNKNOWN: return " UNKNOWN";
+ case BT_EMF: return " EMF";
+ case BT_WMF: return " WMF";
+ case BT_PICT: return " PICT";
+ case BT_JPEG: return " JPEG";
+ case BT_PNG: return " PNG";
+ case BT_DIB: return " DIB";
+ }
+ if ( b < 32 ) {
+ return " NotKnown";
+ }
+ return " Client";
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherBitmapBlip.java b/trunk/src/java/org/apache/poi/ddf/EscherBitmapBlip.java
new file mode 100644
index 000000000..cb2eba8fc
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherBitmapBlip.java
@@ -0,0 +1,138 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+public class EscherBitmapBlip extends EscherBlipRecord {
+ public static final short RECORD_ID_JPEG = (short) 0xF018 + 5;
+ public static final short RECORD_ID_PNG = (short) 0xF018 + 6;
+ public static final short RECORD_ID_DIB = (short) 0xF018 + 7;
+
+ private static final int HEADER_SIZE = 8;
+
+ private final byte[] field_1_UID = new byte[16];
+ private byte field_2_marker = (byte) 0xFF;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesAfterHeader = readHeader( data, offset );
+ int pos = offset + HEADER_SIZE;
+
+ System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16;
+ field_2_marker = data[pos]; pos++;
+
+ field_pictureData = new byte[bytesAfterHeader - 17];
+ System.arraycopy( data, pos, field_pictureData, 0, field_pictureData.length );
+
+ return bytesAfterHeader + HEADER_SIZE;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener ) {
+ listener.beforeRecordSerialize(offset, getRecordId(), this);
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, getRecordSize() - HEADER_SIZE );
+ int pos = offset + HEADER_SIZE;
+
+ System.arraycopy( field_1_UID, 0, data, pos, 16 );
+ data[pos + 16] = field_2_marker;
+ System.arraycopy( field_pictureData, 0, data, pos + 17, field_pictureData.length );
+
+ listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this);
+ return HEADER_SIZE + 16 + 1 + field_pictureData.length;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return 8 + 16 + 1 + field_pictureData.length;
+ }
+
+ /**
+ * Gets the first MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @return the first MD4
+ */
+ public byte[] getUID() {
+ return field_1_UID;
+ }
+
+ /**
+ * Sets the first MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @param field_1_UID the first MD4
+ */
+ public void setUID( byte[] field_1_UID ) {
+ if (field_1_UID == null || field_1_UID.length != 16) {
+ throw new IllegalArgumentException("field_1_UID must be byte[16]");
+ }
+ System.arraycopy(field_1_UID, 0, this.field_1_UID , 0, 16);
+ }
+
+ /**
+ * Gets an unsigned integer that specifies an application-defined internal
+ * resource tag. This value MUST be 0xFF for external files.
+ *
+ * @return the marker
+ */
+ public byte getMarker() {
+ return field_2_marker;
+ }
+
+ /**
+ * Sets an unsigned integer that specifies an application-defined internal
+ * resource tag. This value MUST be 0xFF for external files.
+ *
+ * @param field_2_marker the marker
+ */
+ public void setMarker( byte field_2_marker ) {
+ this.field_2_marker = field_2_marker;
+ }
+
+ @Override
+ public String toString() {
+ String nl = System.getProperty( "line.separator" );
+
+ String extraData = HexDump.dump(this.field_pictureData, 0, 0);
+
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " Version: 0x" + HexDump.toHex( getVersion() ) + nl +
+ " Instance: 0x" + HexDump.toHex( getInstance() ) + nl +
+ " UID: 0x" + HexDump.toHex( field_1_UID ) + nl +
+ " Marker: 0x" + HexDump.toHex( field_2_marker ) + nl +
+ " Extra Data:" + nl + extraData;
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String extraData = HexDump.dump(this.field_pictureData, 0, 0);
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(field_1_UID)).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(field_2_marker)).append("\n")
+ .append(tab).append("\t").append("").append(extraData).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherBlipRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherBlipRecord.java
new file mode 100644
index 000000000..0ea893001
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherBlipRecord.java
@@ -0,0 +1,109 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+public class EscherBlipRecord extends EscherRecord {
+ public static final short RECORD_ID_START = (short) 0xF018;
+ public static final short RECORD_ID_END = (short) 0xF117;
+ public static final String RECORD_DESCRIPTION = "msofbtBlip";
+
+ private static final int HEADER_SIZE = 8;
+
+ protected byte[] field_pictureData;
+
+ public EscherBlipRecord() {
+ }
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesAfterHeader = readHeader( data, offset );
+ int pos = offset + HEADER_SIZE;
+
+ field_pictureData = new byte[bytesAfterHeader];
+ System.arraycopy(data, pos, field_pictureData, 0, bytesAfterHeader);
+
+ return bytesAfterHeader + 8;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize(offset, getRecordId(), this);
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+
+ System.arraycopy( field_pictureData, 0, data, offset + 4, field_pictureData.length );
+
+ listener.afterRecordSerialize(offset + 4 + field_pictureData.length, getRecordId(), field_pictureData.length + 4, this);
+ return field_pictureData.length + 4;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return field_pictureData.length + HEADER_SIZE;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "Blip";
+ }
+
+ /**
+ * Gets the picture data bytes
+ *
+ * @return the picture data
+ */
+ public byte[] getPicturedata() {
+ return field_pictureData;
+ }
+
+ /**
+ * Sets the picture data bytes
+ *
+ * @param pictureData the picture data
+ */
+ public void setPictureData(byte[] pictureData) {
+ if (pictureData == null) {
+ throw new IllegalArgumentException("picture data can't be null");
+ }
+ field_pictureData = pictureData.clone();
+ }
+
+ @Override
+ public String toString() {
+ String extraData = HexDump.toHex(field_pictureData, 32);
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex( getRecordId() ) + '\n' +
+ " Version: 0x" + HexDump.toHex( getVersion() ) + '\n' +
+ " Instance: 0x" + HexDump.toHex( getInstance() ) + '\n' +
+ " Extra Data:" + '\n' + extraData;
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String extraData = HexDump.toHex(field_pictureData, 32);
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(extraData).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherBoolProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherBoolProperty.java
new file mode 100644
index 000000000..1f44e2343
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherBoolProperty.java
@@ -0,0 +1,83 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+
+/**
+ * Represents a boolean property. The actual utility of this property is in doubt because many
+ * of the properties marked as boolean seem to actually contain special values. In other words
+ * they're not true booleans.
+ *
+ * @see EscherSimpleProperty
+ * @see EscherProperty
+ */
+public class EscherBoolProperty
+ extends EscherSimpleProperty
+{
+ /**
+ * Create an instance of an escher boolean property.
+ *
+ * @param propertyNumber The property number (or id)
+ * @param value The 32 bit value of this bool property
+ */
+ public EscherBoolProperty( short propertyNumber, int value )
+ {
+ super(propertyNumber, value);
+ }
+
+ /**
+ * Whether this boolean property is true
+ *
+ * @return the boolean property value
+ */
+ public boolean isTrue()
+ {
+ return propertyValue != 0;
+ }
+
+ /**
+ * Whether this boolean property is false
+ *
+ * @return true, if this boolean property is false
+ *
+ * @deprecated use !isTrue() instead, planed to be removed in POI 3.17
+ */
+ public boolean isFalse()
+ {
+ return propertyValue == 0;
+ }
+
+// public String toString()
+// {
+// return "propNum: " + getPropertyNumber()
+// + ", complex: " + isComplex()
+// + ", blipId: " + isBlipId()
+// + ", value: " + (getValue() != 0);
+// }
+
+ @Override
+ public String toXml(String tab){
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append("<").append(getClass().getSimpleName()).append(" id=\"0x").append(HexDump.toHex(getId()))
+ .append("\" name=\"").append(getName()).append("\" simpleValue=\"").append(getPropertyValue()).append("\" blipId=\"")
+ .append(isBlipId()).append("\" value=\"").append(isTrue()).append("\"").append("/>\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java
new file mode 100644
index 000000000..1fb16444f
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java
@@ -0,0 +1,210 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * The escher child achor record is used to specify the position of a shape under an
+ * existing group. The first level of shape records use a EscherClientAnchor record instead.
+ *
+ * @see EscherChildAnchorRecord
+ */
+public class EscherChildAnchorRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF00F;
+ public static final String RECORD_DESCRIPTION = "MsofbtChildAnchor";
+
+ private int field_1_dx1;
+ private int field_2_dy1;
+ private int field_3_dx2;
+ private int field_4_dy2;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ switch (bytesRemaining) {
+ case 16: // RectStruct
+ field_1_dx1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_2_dy1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_dx2 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_dy2 = LittleEndian.getInt( data, pos + size );size+=4;
+ break;
+ case 8: // SmallRectStruct
+ field_1_dx1 = LittleEndian.getShort( data, pos + size );size+=2;
+ field_2_dy1 = LittleEndian.getShort( data, pos + size );size+=2;
+ field_3_dx2 = LittleEndian.getShort( data, pos + size );size+=2;
+ field_4_dy2 = LittleEndian.getShort( data, pos + size );size+=2;
+ break;
+ default:
+ throw new RuntimeException("Invalid EscherChildAnchorRecord - neither 8 nor 16 bytes.");
+ }
+
+ return 8 + size;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ LittleEndian.putInt( data, pos, getRecordSize()-8 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_1_dx1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_2_dy1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_dx2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_dy2 ); pos += 4;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + 4 * 4;
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "ChildAnchor";
+ }
+
+
+ /**
+ * The string representation of this record
+ */
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Version: 0x" + HexDump.toHex(getVersion()) + nl +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + nl +
+ " X1: " + field_1_dx1 + nl +
+ " Y1: " + field_2_dy1 + nl +
+ " X2: " + field_3_dx2 + nl +
+ " Y2: " + field_4_dy2 + nl ;
+
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(field_1_dx1).append("\n")
+ .append(tab).append("\t").append("").append(field_2_dy1).append("\n")
+ .append(tab).append("\t").append("").append(field_3_dx2).append("\n")
+ .append(tab).append("\t").append("").append(field_4_dy2).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Retrieves offset within the parent coordinate space for the top left point.
+ *
+ * @return the x offset of the top left point
+ */
+ public int getDx1()
+ {
+ return field_1_dx1;
+ }
+
+ /**
+ * Sets offset within the parent coordinate space for the top left point.
+ *
+ * @param field_1_dx1 the x offset of the top left point
+ */
+ public void setDx1( int field_1_dx1 )
+ {
+ this.field_1_dx1 = field_1_dx1;
+ }
+
+ /**
+ * Gets offset within the parent coordinate space for the top left point.
+ *
+ * @return the y offset of the top left point
+ */
+ public int getDy1()
+ {
+ return field_2_dy1;
+ }
+
+ /**
+ * Sets offset within the parent coordinate space for the top left point.
+ *
+ * @param field_2_dy1 the y offset of the top left point
+ */
+ public void setDy1( int field_2_dy1 )
+ {
+ this.field_2_dy1 = field_2_dy1;
+ }
+
+ /**
+ * Retrieves offset within the parent coordinate space for the bottom right point.
+ *
+ * @return the x offset of the bottom right point
+ */
+ public int getDx2()
+ {
+ return field_3_dx2;
+ }
+
+ /**
+ * Sets offset within the parent coordinate space for the bottom right point.
+ *
+ * @param field_3_dx2 the x offset of the bottom right point
+ */
+ public void setDx2( int field_3_dx2 )
+ {
+ this.field_3_dx2 = field_3_dx2;
+ }
+
+ /**
+ * Gets the offset within the parent coordinate space for the bottom right point.
+ *
+ * @return the y offset of the bottom right point
+ */
+ public int getDy2()
+ {
+ return field_4_dy2;
+ }
+
+ /**
+ * Sets the offset within the parent coordinate space for the bottom right point.
+ *
+ * @param field_4_dy2 the y offset of the bottom right point
+ */
+ public void setDy2( int field_4_dy2 )
+ {
+ this.field_4_dy2 = field_4_dy2;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java
new file mode 100644
index 000000000..bb1cf66ee
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java
@@ -0,0 +1,387 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * The escher client anchor specifies which rows and cells the shape is bound to as well as
+ * the offsets within those cells. Each cell is 1024 units wide by 256 units long regardless
+ * of the actual size of the cell. The EscherClientAnchorRecord only applies to the top-most
+ * shapes. Shapes contained in groups are bound using the EscherChildAnchorRecords.
+ *
+ * @see EscherChildAnchorRecord
+ */
+public class EscherClientAnchorRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF010;
+ public static final String RECORD_DESCRIPTION = "MsofbtClientAnchor";
+
+ /**
+ * bit[0] - fMove (1 bit): A bit that specifies whether the shape will be kept intact when the cells are moved.
+ * bit[1] - fSize (1 bit): A bit that specifies whether the shape will be kept intact when the cells are resized. If fMove is 1, the value MUST be 1.
+ * bit[2-4] - reserved, MUST be 0 and MUST be ignored
+ * bit[5-15]- Undefined and MUST be ignored.
+ *
+ * it can take values: 0, 2, 3
+ */
+ private short field_1_flag;
+ private short field_2_col1;
+ private short field_3_dx1;
+ private short field_4_row1;
+ private short field_5_dy1;
+ private short field_6_col2;
+ private short field_7_dx2;
+ private short field_8_row2;
+ private short field_9_dy2;
+ private byte[] remainingData = new byte[0];
+ private boolean shortRecord = false;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+
+ // Always find 4 two byte entries. Sometimes find 9
+ /*if (bytesRemaining == 4) // Word format only 4 bytes
+ {
+ // Not sure exactly what the format is quite yet, likely a reference to a PLC
+ }
+ else */
+ if (bytesRemaining != 4) // Word format only 4 bytes
+ {
+ field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ if(bytesRemaining >= 18) {
+ field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2;
+ shortRecord = false;
+ } else {
+ shortRecord = true;
+ }
+ }
+ bytesRemaining -= size;
+ remainingData = new byte[bytesRemaining];
+ System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return 8 + size + bytesRemaining;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ if (remainingData == null) remainingData = new byte[0];
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = remainingData.length + (shortRecord ? 8 : 18);
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+ LittleEndian.putShort( data, offset + 8, field_1_flag );
+ LittleEndian.putShort( data, offset + 10, field_2_col1 );
+ LittleEndian.putShort( data, offset + 12, field_3_dx1 );
+ LittleEndian.putShort( data, offset + 14, field_4_row1 );
+ if(!shortRecord) {
+ LittleEndian.putShort( data, offset + 16, field_5_dy1 );
+ LittleEndian.putShort( data, offset + 18, field_6_col2 );
+ LittleEndian.putShort( data, offset + 20, field_7_dx2 );
+ LittleEndian.putShort( data, offset + 22, field_8_row2 );
+ LittleEndian.putShort( data, offset + 24, field_9_dy2 );
+ }
+ System.arraycopy( remainingData, 0, data, offset + (shortRecord ? 16 : 26), remainingData.length );
+ int pos = offset + 8 + (shortRecord ? 8 : 18) + remainingData.length;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + (shortRecord ? 8 : 18) + (remainingData == null ? 0 : remainingData.length);
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "ClientAnchor";
+ }
+
+ /**
+ * Returns the string representation for this record.
+ *
+ * @return A string
+ */
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+ String extraData = HexDump.dump(this.remainingData, 0, 0);
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Version: 0x" + HexDump.toHex(getVersion()) + nl +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + nl +
+ " Flag: " + field_1_flag + nl +
+ " Col1: " + field_2_col1 + nl +
+ " DX1: " + field_3_dx1 + nl +
+ " Row1: " + field_4_row1 + nl +
+ " DY1: " + field_5_dy1 + nl +
+ " Col2: " + field_6_col2 + nl +
+ " DX2: " + field_7_dx2 + nl +
+ " Row2: " + field_8_row2 + nl +
+ " DY2: " + field_9_dy2 + nl +
+ " Extra Data:" + nl + extraData;
+
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String extraData = HexDump.dump(this.remainingData, 0, 0).trim();
+ return tab + formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())) +
+ tab + "\t" + "" + field_1_flag + "\n" +
+ tab + "\t" + "" + field_2_col1 + "\n" +
+ tab + "\t" + "" + field_3_dx1 + "\n" +
+ tab + "\t" + "" + field_4_row1 + "\n" +
+ tab + "\t" + "" + field_5_dy1 + "\n" +
+ tab + "\t" + "" + field_6_col2 + "\n" +
+ tab + "\t" + "" + field_7_dx2 + "\n" +
+ tab + "\t" + "" + field_8_row2 + "\n" +
+ tab + "\t" + "" + field_9_dy2 + "\n" +
+ tab + "\t" + "" + extraData + "\n" +
+ tab + "" + getClass().getSimpleName() + ">\n";
+ }
+
+ /**
+ * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells.
+ *
+ * @return the move/size flag
+ */
+ public short getFlag()
+ {
+ return field_1_flag;
+ }
+
+ /**
+ * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells.
+ *
+ * @param field_1_flag the move/size flag
+ */
+ public void setFlag( short field_1_flag )
+ {
+ this.field_1_flag = field_1_flag;
+ }
+
+ /**
+ * The column number for the top-left position. 0 based.
+ *
+ * @return the column number of the top-left corner
+ */
+ public short getCol1()
+ {
+ return field_2_col1;
+ }
+
+ /**
+ * The column number for the top-left position. 0 based.
+ *
+ * @param field_2_col1 the column number of the top-left corner
+ */
+ public void setCol1( short field_2_col1 )
+ {
+ this.field_2_col1 = field_2_col1;
+ }
+
+ /**
+ * The x offset within the top-left cell. Range is from 0 to 1023.
+ *
+ * @return the x offset of the top-left corner
+ */
+ public short getDx1()
+ {
+ return field_3_dx1;
+ }
+
+ /**
+ * The x offset within the top-left cell. Range is from 0 to 1023.
+ *
+ * @param field_3_dx1 the x offset of the top-left corner
+ */
+ public void setDx1( short field_3_dx1 )
+ {
+ this.field_3_dx1 = field_3_dx1;
+ }
+
+ /**
+ * The row number for the top-left corner of the shape.
+ *
+ * @return the row number of the top-left corner
+ */
+ public short getRow1()
+ {
+ return field_4_row1;
+ }
+
+ /**
+ * The row number of the top-left corner of the shape.
+ *
+ * @param field_4_row1 the row number of the top-left corner
+ */
+ public void setRow1( short field_4_row1 )
+ {
+ this.field_4_row1 = field_4_row1;
+ }
+
+ /**
+ * The y offset within the top-left corner of the current shape.
+ *
+ * @return the y offset of the top-left corner
+ */
+ public short getDy1()
+ {
+ return field_5_dy1;
+ }
+
+ /**
+ * The y offset within the top-left corner of the current shape.
+ *
+ * @param field_5_dy1 the y offset of the top-left corner
+ */
+ public void setDy1( short field_5_dy1 )
+ {
+ shortRecord = false;
+ this.field_5_dy1 = field_5_dy1;
+ }
+
+ /**
+ * The column of the bottom right corner of this shape.
+ *
+ * @return the column of the bottom right corner
+ */
+ public short getCol2()
+ {
+ return field_6_col2;
+ }
+
+ /**
+ * The column of the bottom right corner of this shape.
+ *
+ * @param field_6_col2 the column of the bottom right corner
+ */
+ public void setCol2( short field_6_col2 )
+ {
+ shortRecord = false;
+ this.field_6_col2 = field_6_col2;
+ }
+
+ /**
+ * The x offset withing the cell for the bottom-right corner of this shape.
+ *
+ * @return the x offset of the bottom-right corner
+ */
+ public short getDx2()
+ {
+ return field_7_dx2;
+ }
+
+ /**
+ * The x offset withing the cell for the bottom-right corner of this shape.
+ *
+ * @param field_7_dx2 the x offset of the bottom-right corner
+ */
+ public void setDx2( short field_7_dx2 )
+ {
+ shortRecord = false;
+ this.field_7_dx2 = field_7_dx2;
+ }
+
+ /**
+ * The row number for the bottom-right corner of the current shape.
+ *
+ * @return the row number for the bottom-right corner
+ */
+ public short getRow2()
+ {
+ return field_8_row2;
+ }
+
+ /**
+ * The row number for the bottom-right corner of the current shape.
+ *
+ * @param field_8_row2 the row number for the bottom-right corner
+ */
+ public void setRow2( short field_8_row2 )
+ {
+ shortRecord = false;
+ this.field_8_row2 = field_8_row2;
+ }
+
+ /**
+ * The y offset withing the cell for the bottom-right corner of this shape.
+ *
+ * @return the y offset of the bottom-right corner
+ */
+ public short getDy2()
+ {
+ return field_9_dy2;
+ }
+
+ /**
+ * The y offset withing the cell for the bottom-right corner of this shape.
+ *
+ * @param field_9_dy2 the y offset of the bottom-right corner
+ */
+ public void setDy2( short field_9_dy2 )
+ {
+ shortRecord = false;
+ this.field_9_dy2 = field_9_dy2;
+ }
+
+ /**
+ * Any remaining data in the record
+ *
+ * @return the remaining bytes
+ */
+ public byte[] getRemainingData()
+ {
+ return remainingData;
+ }
+
+ /**
+ * Any remaining data in the record
+ *
+ * @param remainingData the remaining bytes
+ */
+ public void setRemainingData( byte[] remainingData ) {
+ if (remainingData == null) {
+ this.remainingData = new byte[0];
+ } else {
+ this.remainingData = remainingData.clone();
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherClientDataRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherClientDataRecord.java
new file mode 100644
index 000000000..749007cfe
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherClientDataRecord.java
@@ -0,0 +1,124 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * The EscherClientDataRecord is used to store client specific data about the position of a
+ * shape within a container.
+ */
+public class EscherClientDataRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF011;
+ public static final String RECORD_DESCRIPTION = "MsofbtClientData";
+
+ private byte[] remainingData;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ remainingData = new byte[bytesRemaining];
+ System.arraycopy( data, pos, remainingData, 0, bytesRemaining );
+ return 8 + bytesRemaining;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ if (remainingData == null) remainingData = new byte[0];
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, remainingData.length );
+ System.arraycopy( remainingData, 0, data, offset + 8, remainingData.length );
+ int pos = offset + 8 + remainingData.length;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + (remainingData == null ? 0 : remainingData.length);
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "ClientData";
+ }
+
+ /**
+ * Returns the string representation of this record.
+ */
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+ String extraData = HexDump.dump(getRemainingData(), 0, 0);
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Version: 0x" + HexDump.toHex(getVersion()) + nl +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + nl +
+ " Extra Data:" + nl +
+ extraData;
+
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String extraData = HexDump.dump(getRemainingData(), 0, 0).trim();
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()),
+ HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(extraData).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Any data recording this record.
+ *
+ * @return the remaining bytes
+ */
+ public byte[] getRemainingData()
+ {
+ return remainingData;
+ }
+
+ /**
+ * Any data recording this record.
+ *
+ * @param remainingData the remaining bytes
+ */
+ public void setRemainingData( byte[] remainingData ) {
+ this.remainingData = (remainingData == null)
+ ? new byte[0]
+ : remainingData.clone();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherColorRef.java b/trunk/src/java/org/apache/poi/ddf/EscherColorRef.java
new file mode 100644
index 000000000..84f94301d
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherColorRef.java
@@ -0,0 +1,295 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * An OfficeArtCOLORREF structure entry which also handles color extension opid data
+ */
+public class EscherColorRef {
+ int opid = -1;
+ int colorRef = 0;
+
+ public enum SysIndexSource {
+ /** Use the fill color of the shape. */
+ FILL_COLOR(0xF0),
+ /** If the shape contains a line, use the line color of the shape. Otherwise, use the fill color. */
+ LINE_OR_FILL_COLOR(0xF1),
+ /** Use the line color of the shape. */
+ LINE_COLOR(0xF2),
+ /** Use the shadow color of the shape. */
+ SHADOW_COLOR(0xF3),
+ /** Use the current, or last-used, color. */
+ CURRENT_OR_LAST_COLOR(0xF4),
+ /** Use the fill background color of the shape. */
+ FILL_BACKGROUND_COLOR(0xF5),
+ /** Use the line background color of the shape. */
+ LINE_BACKGROUND_COLOR(0xF6),
+ /** If the shape contains a fill, use the fill color of the shape. Otherwise, use the line color. */
+ FILL_OR_LINE_COLOR(0xF7)
+ ;
+ int value;
+ SysIndexSource(int value) { this.value = value; }
+ }
+
+ /**
+ * The following enum specifies values that indicate special procedural properties that
+ * are used to modify the color components of another color. These values are combined with
+ * those of the {@link SysIndexSource} enum or with a user-specified color.
+ * The first six values are mutually exclusive.
+ */
+ public enum SysIndexProcedure {
+ /**
+ * Darken the color by the value that is specified in the blue field.
+ * A blue value of 0xFF specifies that the color is to be left unchanged,
+ * whereas a blue value of 0x00 specifies that the color is to be completely darkened.
+ */
+ DARKEN_COLOR(0x01),
+ /**
+ * Lighten the color by the value that is specified in the blue field.
+ * A blue value of 0xFF specifies that the color is to be left unchanged,
+ * whereas a blue value of 0x00 specifies that the color is to be completely lightened.
+ */
+ LIGHTEN_COLOR(0x02),
+ /**
+ * Add a gray level RGB value. The blue field contains the gray level to add:
+ * NewColor = SourceColor + gray
+ */
+ ADD_GRAY_LEVEL(0x03),
+ /**
+ * Subtract a gray level RGB value. The blue field contains the gray level to subtract:
+ * NewColor = SourceColor - gray
+ */
+ SUB_GRAY_LEVEL(0x04),
+ /**
+ * Reverse-subtract a gray level RGB value. The blue field contains the gray level from
+ * which to subtract:
+ * NewColor = gray - SourceColor
+ */
+ REVERSE_GRAY_LEVEL(0x05),
+ /**
+ * If the color component being modified is less than the parameter contained in the blue
+ * field, set it to the minimum intensity. If the color component being modified is greater
+ * than or equal to the parameter, set it to the maximum intensity.
+ */
+ THRESHOLD(0x06),
+ /**
+ * After making other modifications, invert the color.
+ * This enum value is only for documentation and won't be directly returned.
+ */
+ INVERT_AFTER(0x20),
+ /**
+ * After making other modifications, invert the color by toggling just the high bit of each
+ * color channel.
+ * This enum value is only for documentation and won't be directly returned.
+ */
+ INVERT_HIGHBIT_AFTER(0x40)
+ ;
+ BitField mask;
+ SysIndexProcedure(int mask) {
+ this.mask = new BitField(mask);
+ }
+ }
+
+ /**
+ * A bit that specifies whether the system color scheme will be used to determine the color.
+ * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index
+ * into the system color table. Values less than 0x00F0 map directly to system colors.
+ */
+ private static final BitField FLAG_SYS_INDEX = new BitField(0x10000000);
+
+ /**
+ * A bit that specifies whether the current application-defined color scheme will be used
+ * to determine the color. A value of 0x1 specifies that red will be treated as an index
+ * into the current color scheme table. If this value is 0x1, green and blue MUST be 0x00.
+ */
+ private static final BitField FLAG_SCHEME_INDEX = new BitField(0x08000000);
+
+ /**
+ * A bit that specifies whether the color is a standard RGB color.
+ * 0x0 : The RGB color MAY use halftone dithering to display.
+ * 0x1 : The color MUST be a solid color.
+ */
+ private static final BitField FLAG_SYSTEM_RGB = new BitField(0x04000000);
+
+ /**
+ * A bit that specifies whether the current palette will be used to determine the color.
+ * A value of 0x1 specifies that red, green, and blue contain an RGB value that will be
+ * matched in the current color palette. This color MUST be solid.
+ */
+ private static final BitField FLAG_PALETTE_RGB = new BitField(0x02000000);
+
+ /**
+ * A bit that specifies whether the current palette will be used to determine the color.
+ * A value of 0x1 specifies that green and red will be treated as an unsigned 16-bit index into
+ * the current color palette. This color MAY be dithered. If this value is 0x1, blue MUST be 0x00.
+ */
+ private static final BitField FLAG_PALETTE_INDEX = new BitField(0x01000000);
+
+ /**
+ * An unsigned integer that specifies the intensity of the blue color channel. A value
+ * of 0x00 has the minimum blue intensity. A value of 0xFF has the maximum blue intensity.
+ */
+ private static final BitField FLAG_BLUE = new BitField(0x00FF0000);
+
+ /**
+ * An unsigned integer that specifies the intensity of the green color channel. A value
+ * of 0x00 has the minimum green intensity. A value of 0xFF has the maximum green intensity.
+ */
+ private static final BitField FLAG_GREEN = new BitField(0x0000FF00);
+
+ /**
+ * An unsigned integer that specifies the intensity of the red color channel. A value
+ * of 0x00 has the minimum red intensity. A value of 0xFF has the maximum red intensity.
+ */
+ private static final BitField FLAG_RED = new BitField(0x000000FF);
+
+ public EscherColorRef(int colorRef) {
+ this.colorRef = colorRef;
+ }
+
+ public EscherColorRef(byte[] source, int start, int len) {
+ assert(len == 4 || len == 6);
+
+ int offset = start;
+ if (len == 6) {
+ opid = LittleEndian.getUShort(source, offset);
+ offset += 2;
+ }
+ colorRef = LittleEndian.getInt(source, offset);
+ }
+
+ public boolean hasSysIndexFlag() {
+ return FLAG_SYS_INDEX.isSet(colorRef);
+ }
+
+ public void setSysIndexFlag(boolean flag) {
+ colorRef = FLAG_SYS_INDEX.setBoolean(colorRef, flag);
+ }
+
+ public boolean hasSchemeIndexFlag() {
+ return FLAG_SCHEME_INDEX.isSet(colorRef);
+ }
+
+ public void setSchemeIndexFlag(boolean flag) {
+ colorRef = FLAG_SCHEME_INDEX.setBoolean(colorRef, flag);
+ }
+
+ public boolean hasSystemRGBFlag() {
+ return FLAG_SYSTEM_RGB.isSet(colorRef);
+ }
+
+ public void setSystemRGBFlag(boolean flag) {
+ colorRef = FLAG_SYSTEM_RGB.setBoolean(colorRef, flag);
+ }
+
+ public boolean hasPaletteRGBFlag() {
+ return FLAG_PALETTE_RGB.isSet(colorRef);
+ }
+
+ public void setPaletteRGBFlag(boolean flag) {
+ colorRef = FLAG_PALETTE_RGB.setBoolean(colorRef, flag);
+ }
+
+ public boolean hasPaletteIndexFlag() {
+ return FLAG_PALETTE_INDEX.isSet(colorRef);
+ }
+
+ public void setPaletteIndexFlag(boolean flag) {
+ colorRef = FLAG_PALETTE_INDEX.setBoolean(colorRef, flag);
+ }
+
+ public int[] getRGB() {
+ int rgb[] = {
+ FLAG_RED.getValue(colorRef),
+ FLAG_GREEN.getValue(colorRef),
+ FLAG_BLUE.getValue(colorRef)
+ };
+ return rgb;
+ }
+
+ /**
+ * @return {@link SysIndexSource} if {@link #hasSysIndexFlag()} is {@code true}, otherwise null
+ */
+ public SysIndexSource getSysIndexSource() {
+ if (!hasSysIndexFlag()) return null;
+ int val = FLAG_RED.getValue(colorRef);
+ for (SysIndexSource sis : SysIndexSource.values()) {
+ if (sis.value == val) return sis;
+ }
+ return null;
+ }
+
+ /**
+ * Return the {@link SysIndexProcedure} - for invert flag use {@link #getSysIndexInvert()}
+ * @return {@link SysIndexProcedure} if {@link #hasSysIndexFlag()} is {@code true}, otherwise null
+ */
+ public SysIndexProcedure getSysIndexProcedure() {
+ if (!hasSysIndexFlag()) return null;
+ int val = FLAG_GREEN.getValue(colorRef);
+ for (SysIndexProcedure sip : SysIndexProcedure.values()) {
+ if (sip == SysIndexProcedure.INVERT_AFTER || sip == SysIndexProcedure.INVERT_HIGHBIT_AFTER) continue;
+ if (sip.mask.isSet(val)) return sip;
+ }
+ return null;
+ }
+
+ /**
+ * @return 0 for no invert flag, 1 for {@link SysIndexProcedure#INVERT_AFTER} and
+ * 2 for {@link SysIndexProcedure#INVERT_HIGHBIT_AFTER}
+ */
+ public int getSysIndexInvert() {
+ if (!hasSysIndexFlag()) return 0;
+ int val = FLAG_GREEN.getValue(colorRef);
+ if ((SysIndexProcedure.INVERT_AFTER.mask.isSet(val))) return 1;
+ if ((SysIndexProcedure.INVERT_HIGHBIT_AFTER.mask.isSet(val))) return 2;
+ return 0;
+ }
+
+ /**
+ * @return index of the scheme color or -1 if {@link #hasSchemeIndexFlag()} is {@code false}
+ *
+ * @see org.apache.poi.hslf.record.ColorSchemeAtom#getColor(int)
+ */
+ public int getSchemeIndex() {
+ if (!hasSchemeIndexFlag()) return -1;
+ return FLAG_RED.getValue(colorRef);
+ }
+
+ /**
+ * @return index of current palette (color) or -1 if {@link #hasPaletteIndexFlag()} is {@code false}
+ */
+ public int getPaletteIndex() {
+ return (hasPaletteIndexFlag()) ? getIndex() : -1;
+ }
+
+ /**
+ * @return index of system color table or -1 if {@link #hasSysIndexFlag()} is {@code false}
+ *
+ * @see org.apache.poi.sl.usermodel.PresetColor
+ */
+ public int getSysIndex() {
+ return (hasSysIndexFlag()) ? getIndex() : -1;
+ }
+
+ private int getIndex() {
+ return (FLAG_GREEN.getValue(colorRef) << 8) | FLAG_RED.getValue(colorRef);
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherComplexProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherComplexProperty.java
new file mode 100644
index 000000000..9a84e10c1
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherComplexProperty.java
@@ -0,0 +1,156 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.util.Arrays;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * A complex property differs from a simple property in that the data can not fit inside a 32 bit
+ * integer. See the specification for more detailed information regarding exactly what is
+ * stored here.
+ */
+public class EscherComplexProperty extends EscherProperty {
+ // TODO - make private and final
+ protected byte[] _complexData;
+
+ /**
+ * Create a complex property using the property id and a byte array containing the complex
+ * data value.
+ *
+ * @param id The id consists of the property number, a flag indicating whether this is a blip id and a flag
+ * indicating that this is a complex property.
+ * @param complexData The value of this property.
+ */
+ public EscherComplexProperty(short id, byte[] complexData) {
+ super(id);
+ if (complexData == null) {
+ throw new IllegalArgumentException("complexData can't be null");
+ }
+ _complexData = complexData.clone();
+ }
+
+ /**
+ * Create a complex property using the property number, a flag to indicate whether this is a
+ * blip reference and the complex property data.
+ *
+ * @param propertyNumber The property number
+ * @param isBlipId Whether this is a blip id. Should be false.
+ * @param complexData The value of this complex property.
+ */
+ public EscherComplexProperty(short propertyNumber, boolean isBlipId, byte[] complexData) {
+ super(propertyNumber, true, isBlipId);
+ if (complexData == null) {
+ throw new IllegalArgumentException("complexData can't be null");
+ }
+ _complexData = complexData.clone();
+ }
+
+ /**
+ * Serializes the simple part of this property. i.e. the first 6 bytes.
+ */
+ @Override
+ public int serializeSimplePart(byte[] data, int pos) {
+ LittleEndian.putShort(data, pos, getId());
+ LittleEndian.putInt(data, pos + 2, _complexData.length);
+ return 6;
+ }
+
+ /**
+ * Serializes the complex part of this property
+ *
+ * @param data The data array to serialize to
+ * @param pos The offset within data to start serializing to.
+ * @return The number of bytes serialized.
+ */
+ @Override
+ public int serializeComplexPart(byte[] data, int pos) {
+ System.arraycopy(_complexData, 0, data, pos, _complexData.length);
+ return _complexData.length;
+ }
+
+ /**
+ * Get the complex data value.
+ *
+ * @return the complex bytes
+ */
+ public byte[] getComplexData() {
+ return _complexData;
+ }
+
+ /**
+ * Determine whether this property is equal to another property.
+ *
+ * @param o The object to compare to.
+ * @return True if the objects are equal.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof EscherComplexProperty)) {
+ return false;
+ }
+
+ EscherComplexProperty escherComplexProperty = (EscherComplexProperty) o;
+
+ return Arrays.equals(_complexData, escherComplexProperty._complexData);
+
+ }
+
+ /**
+ * Calculates the number of bytes required to serialize this property.
+ *
+ * @return Number of bytes
+ */
+ @Override
+ public int getPropertySize() {
+ return 6 + _complexData.length;
+ }
+
+ @Override
+ public int hashCode() {
+ return getId() * 11;
+ }
+
+ /**
+ * Retrieves the string representation for this property.
+ */
+ @Override
+ public String toString() {
+ String dataStr = HexDump.toHex( _complexData, 32);
+
+ return "propNum: " + getPropertyNumber()
+ + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() )
+ + ", complex: " + isComplex()
+ + ", blipId: " + isBlipId()
+ + ", data: " + System.getProperty("line.separator") + dataStr;
+ }
+
+ @Override
+ public String toXml(String tab){
+ return tab + "<" + getClass().getSimpleName() + " id=\"0x" + HexDump.toHex(getId()) +
+ "\" name=\"" + getName() + "\" blipId=\"" +
+ isBlipId() + "\">\n" +
+ tab + "" + getClass().getSimpleName() + ">\n";
+ //builder.append("\t").append(tab).append(dataStr);
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherContainerRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherContainerRecord.java
new file mode 100644
index 000000000..3b26d4211
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherContainerRecord.java
@@ -0,0 +1,343 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.io.PrintWriter;
+import java.util.*;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * Escher container records store other escher records as children.
+ * The container records themselves never store any information beyond
+ * the standard header used by all escher records. This one record is
+ * used to represent many different types of records.
+ */
+public final class EscherContainerRecord extends EscherRecord {
+ public static final short DGG_CONTAINER = (short)0xF000;
+ public static final short BSTORE_CONTAINER = (short)0xF001;
+ public static final short DG_CONTAINER = (short)0xF002;
+ public static final short SPGR_CONTAINER = (short)0xF003;
+ public static final short SP_CONTAINER = (short)0xF004;
+ public static final short SOLVER_CONTAINER = (short)0xF005;
+
+ private static final POILogger log = POILogFactory.getLogger(EscherContainerRecord.class);
+
+ /**
+ * in case if document contains any charts we have such document structure:
+ * BOF
+ * ...
+ * DrawingRecord
+ * ...
+ * ObjRecord|TxtObjRecord
+ * ...
+ * EOF
+ * ...
+ * BOF(Chart begin)
+ * ...
+ * DrawingRecord
+ * ...
+ * ObjRecord|TxtObjRecord
+ * ...
+ * EOF
+ * So, when we call EscherAggregate.createAggregate() we have not all needed data.
+ * When we got warning "WARNING: " + bytesRemaining + " bytes remaining but no space left"
+ * we should save value of bytesRemaining
+ * and add it to container size when we serialize it
+ */
+ private int _remainingLength;
+
+ private final List _childRecords = new ArrayList();
+
+ @Override
+ public int fillFields(byte[] data, int pOffset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader(data, pOffset);
+ int bytesWritten = 8;
+ int offset = pOffset + 8;
+ while (bytesRemaining > 0 && offset < data.length) {
+ EscherRecord child = recordFactory.createRecord(data, offset);
+ int childBytesWritten = child.fillFields(data, offset, recordFactory);
+ bytesWritten += childBytesWritten;
+ offset += childBytesWritten;
+ bytesRemaining -= childBytesWritten;
+ addChildRecord(child);
+ if (offset >= data.length && bytesRemaining > 0) {
+ _remainingLength = bytesRemaining;
+ if (log.check(POILogger.WARN)) {
+ log.log(POILogger.WARN, "Not enough Escher data: " + bytesRemaining + " bytes remaining but no space left");
+ }
+ }
+ }
+ return bytesWritten;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort(data, offset, getOptions());
+ LittleEndian.putShort(data, offset+2, getRecordId());
+ int remainingBytes = 0;
+ Iterator iterator = _childRecords.iterator();
+ while (iterator.hasNext()) {
+ EscherRecord r = iterator.next();
+ remainingBytes += r.getRecordSize();
+ }
+ remainingBytes += _remainingLength;
+ LittleEndian.putInt(data, offset+4, remainingBytes);
+ int pos = offset+8;
+ iterator = _childRecords.iterator();
+ while (iterator.hasNext()) {
+ EscherRecord r = iterator.next();
+ pos += r.serialize(pos, data, listener );
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ @Override
+ public int getRecordSize() {
+ int childRecordsSize = 0;
+ Iterator iterator = _childRecords.iterator();
+ while (iterator.hasNext()) {
+ EscherRecord r = iterator.next();
+ childRecordsSize += r.getRecordSize();
+ }
+ return 8 + childRecordsSize;
+ }
+
+ /**
+ * Do any of our (top level) children have the given recordId?
+ *
+ * @param recordId the recordId of the child
+ *
+ * @return true, if any child has the given recordId
+ */
+ public boolean hasChildOfType(short recordId) {
+ Iterator iterator = _childRecords.iterator();
+ while (iterator.hasNext()) {
+ EscherRecord r = iterator.next();
+ if(r.getRecordId() == recordId) {
+ return true;
+ }
+ }
+ return false;
+ }
+ @Override
+ public EscherRecord getChild( int index ) {
+ return _childRecords.get(index);
+ }
+
+ /**
+ * @return a copy of the list of all the child records of the container.
+ */
+ @Override
+ public List getChildRecords() {
+ return new ArrayList(_childRecords);
+ }
+
+ /**
+ * @return an iterator over the child records
+ */
+ public Iterator getChildIterator() {
+ return Collections.unmodifiableList(_childRecords).iterator();
+ }
+
+ /**
+ * replaces the internal child list with the contents of the supplied childRecords
+ */
+ @Override
+ public void setChildRecords(List childRecords) {
+ if (childRecords == _childRecords) {
+ throw new IllegalStateException("Child records private data member has escaped");
+ }
+ _childRecords.clear();
+ _childRecords.addAll(childRecords);
+ }
+
+ /**
+ * Removes the given escher record from the child list
+ *
+ * @param toBeRemoved the escher record to be removed
+ * @return true, if the record was found and removed
+ */
+ public boolean removeChildRecord(EscherRecord toBeRemoved) {
+ return _childRecords.remove(toBeRemoved);
+ }
+
+
+
+ /**
+ * Returns all of our children which are also
+ * EscherContainers (may be 0, 1, or vary rarely 2 or 3)
+ *
+ * @return EscherContainer children
+ */
+ public List getChildContainers() {
+ List containers = new ArrayList();
+ Iterator iterator = _childRecords.iterator();
+ while (iterator.hasNext()) {
+ EscherRecord r = iterator.next();
+ if(r instanceof EscherContainerRecord) {
+ containers.add((EscherContainerRecord) r);
+ }
+ }
+ return containers;
+ }
+
+ @Override
+ public String getRecordName() {
+ switch (getRecordId()) {
+ case DGG_CONTAINER:
+ return "DggContainer";
+ case BSTORE_CONTAINER:
+ return "BStoreContainer";
+ case DG_CONTAINER:
+ return "DgContainer";
+ case SPGR_CONTAINER:
+ return "SpgrContainer";
+ case SP_CONTAINER:
+ return "SpContainer";
+ case SOLVER_CONTAINER:
+ return "SolverContainer";
+ default:
+ return "Container 0x" + HexDump.toHex(getRecordId());
+ }
+ }
+
+ @Override
+ public void display(PrintWriter w, int indent) {
+ super.display(w, indent);
+ for (Iterator iterator = _childRecords.iterator(); iterator.hasNext();)
+ {
+ EscherRecord escherRecord = iterator.next();
+ escherRecord.display(w, indent + 1);
+ }
+ }
+
+ /**
+ * Append a child record
+ *
+ * @param record the record to be added
+ */
+ public void addChildRecord(EscherRecord record) {
+ _childRecords.add(record);
+ }
+
+ /**
+ * Add a child record before the record with given recordId
+ *
+ * @param record the record to be added
+ * @param insertBeforeRecordId the recordId of the next sibling
+ */
+ public void addChildBefore(EscherRecord record, int insertBeforeRecordId) {
+ int idx = 0;
+ for (EscherRecord rec : _childRecords) {
+ if(rec.getRecordId() == (short)insertBeforeRecordId) break;
+ // TODO - keep looping? Do we expect multiple matches?
+ idx++;
+ }
+ _childRecords.add(idx, record);
+ }
+
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ StringBuffer children = new StringBuffer();
+ if ( _childRecords.size() > 0 )
+ {
+ children.append( " children: " + nl );
+
+ int count = 0;
+ for ( Iterator iterator = _childRecords.iterator(); iterator
+ .hasNext(); )
+ {
+ EscherRecord record = iterator.next();
+ children.append( " Child " + count + ":" + nl );
+ String childResult = String.valueOf( record );
+ childResult = childResult.replaceAll( "\n", "\n " );
+ children.append( " " );
+ children.append( childResult );
+ children.append( nl );
+ count++;
+ }
+ }
+
+ return getClass().getName() + " (" + getRecordName() + "):" + nl
+ + " isContainer: " + isContainerRecord() + nl
+ + " version: 0x" + HexDump.toHex( getVersion() ) + nl
+ + " instance: 0x" + HexDump.toHex( getInstance() ) + nl
+ + " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl
+ + " numchildren: " + _childRecords.size() + nl
+ + children.toString();
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getRecordName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())));
+ for ( Iterator iterator = _childRecords.iterator(); iterator
+ .hasNext(); )
+ {
+ EscherRecord record = iterator.next();
+ builder.append(record.toXml(tab+"\t"));
+ }
+ builder.append(tab).append("").append(getRecordName()).append(">\n");
+ return builder.toString();
+ }
+
+ public T getChildById( short recordId )
+ {
+ for ( EscherRecord childRecord : _childRecords )
+ {
+ if ( childRecord.getRecordId() == recordId )
+ {
+ @SuppressWarnings( "unchecked" )
+ final T result = (T) childRecord;
+ return result;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Recursively find records with the specified record ID
+ *
+ * @param recordId the recordId to be searched for
+ * @param out - list to store found records
+ */
+ public void getRecordsById(short recordId, List out){
+ Iterator iterator = _childRecords.iterator();
+ while (iterator.hasNext()) {
+ EscherRecord r = iterator.next();
+ if(r instanceof EscherContainerRecord) {
+ EscherContainerRecord c = (EscherContainerRecord)r;
+ c.getRecordsById(recordId, out );
+ } else if (r.getRecordId() == recordId){
+ out.add(r);
+ }
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherDgRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherDgRecord.java
new file mode 100644
index 000000000..7de76e241
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherDgRecord.java
@@ -0,0 +1,169 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * This record simply holds the number of shapes in the drawing group and the
+ * last shape id used for this drawing group.
+ */
+public class EscherDgRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF008;
+ public static final String RECORD_DESCRIPTION = "MsofbtDg";
+
+ private int field_1_numShapes;
+ private int field_2_lastMSOSPID;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ /*int bytesRemaining =*/ readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_numShapes = LittleEndian.getInt( data, pos + size ); size += 4;
+ field_2_lastMSOSPID = LittleEndian.getInt( data, pos + size ); size += 4;
+// bytesRemaining -= size;
+// remainingData = new byte[bytesRemaining];
+// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return getRecordSize();
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ LittleEndian.putInt( data, offset + 4, 8 );
+ LittleEndian.putInt( data, offset + 8, field_1_numShapes );
+ LittleEndian.putInt( data, offset + 12, field_2_lastMSOSPID );
+// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+// int pos = offset + 8 + 18 + remainingData.length;
+
+ listener.afterRecordSerialize( offset + 16, getRecordId(), getRecordSize(), this );
+ return getRecordSize();
+ }
+
+ /**
+ * Returns the number of bytes that are required to serialize this record.
+ *
+ * @return Number of bytes
+ */
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + 8;
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "Dg";
+ }
+
+ /**
+ * Returns the string representation of this record.
+ */
+ @Override
+ public String toString() {
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + '\n' +
+ " Version: 0x" + HexDump.toHex(getVersion()) + '\n' +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + '\n' +
+ " NumShapes: " + field_1_numShapes + '\n' +
+ " LastMSOSPID: " + field_2_lastMSOSPID + '\n';
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(field_1_numShapes).append("\n")
+ .append(tab).append("\t").append("").append(field_2_lastMSOSPID).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * The number of shapes in this drawing group.
+ *
+ * @return the number of shapes
+ */
+ public int getNumShapes()
+ {
+ return field_1_numShapes;
+ }
+
+ /**
+ * The number of shapes in this drawing group.
+ *
+ * @param field_1_numShapes the number of shapes
+ */
+ public void setNumShapes( int field_1_numShapes )
+ {
+ this.field_1_numShapes = field_1_numShapes;
+ }
+
+ /**
+ * The last shape id used in this drawing group.
+ *
+ * @return the last shape id
+ */
+ public int getLastMSOSPID()
+ {
+ return field_2_lastMSOSPID;
+ }
+
+ /**
+ * The last shape id used in this drawing group.
+ *
+ * @param field_2_lastMSOSPID the last shape id
+ */
+ public void setLastMSOSPID( int field_2_lastMSOSPID )
+ {
+ this.field_2_lastMSOSPID = field_2_lastMSOSPID;
+ }
+
+ /**
+ * Gets the drawing group id for this record. This is encoded in the
+ * instance part of the option record.
+ *
+ * @return a drawing group id.
+ */
+ public short getDrawingGroupId()
+ {
+ return (short) ( getOptions() >> 4 );
+ }
+
+ /**
+ * Increments the number of shapes
+ */
+ public void incrementShapeCount()
+ {
+ this.field_1_numShapes++;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherDggRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherDggRecord.java
new file mode 100644
index 000000000..5b1b8b744
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherDggRecord.java
@@ -0,0 +1,300 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
+
+import java.util.*;
+
+/**
+ * This record defines the drawing groups used for a particular sheet.
+ */
+public final class EscherDggRecord extends EscherRecord {
+ public static final short RECORD_ID = (short) 0xF006;
+ public static final String RECORD_DESCRIPTION = "MsofbtDgg";
+
+ private int field_1_shapeIdMax;
+// private int field_2_numIdClusters; // for some reason the number of clusters is actually the real number + 1
+ private int field_3_numShapesSaved;
+ private int field_4_drawingsSaved;
+ private FileIdCluster[] field_5_fileIdClusters;
+ private int maxDgId;
+
+ public static class FileIdCluster
+ {
+ public FileIdCluster( int drawingGroupId, int numShapeIdsUsed )
+ {
+ this.field_1_drawingGroupId = drawingGroupId;
+ this.field_2_numShapeIdsUsed = numShapeIdsUsed;
+ }
+
+ private int field_1_drawingGroupId;
+ private int field_2_numShapeIdsUsed;
+
+ public int getDrawingGroupId()
+ {
+ return field_1_drawingGroupId;
+ }
+
+ public int getNumShapeIdsUsed()
+ {
+ return field_2_numShapeIdsUsed;
+ }
+
+ public void incrementShapeId( )
+ {
+ this.field_2_numShapeIdsUsed++;
+ }
+ }
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_shapeIdMax = LittleEndian.getInt( data, pos + size );size+=4;
+ // field_2_numIdClusters = LittleEndian.getInt( data, pos + size );
+ size+=4;
+ field_3_numShapesSaved = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_drawingsSaved = LittleEndian.getInt( data, pos + size );size+=4;
+ field_5_fileIdClusters = new FileIdCluster[(bytesRemaining-size) / 8]; // Can't rely on field_2_numIdClusters
+ for (int i = 0; i < field_5_fileIdClusters.length; i++)
+ {
+ field_5_fileIdClusters[i] = new FileIdCluster(LittleEndian.getInt( data, pos + size ), LittleEndian.getInt( data, pos + size + 4 ));
+ maxDgId = Math.max(maxDgId, field_5_fileIdClusters[i].getDrawingGroupId());
+ size += 8;
+ }
+ bytesRemaining -= size;
+ if (bytesRemaining != 0)
+ throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s).");
+ return 8 + size + bytesRemaining;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ int remainingBytes = getRecordSize() - 8;
+ LittleEndian.putInt( data, pos, remainingBytes ); pos += 4;
+
+ LittleEndian.putInt( data, pos, field_1_shapeIdMax ); pos += 4;
+ LittleEndian.putInt( data, pos, getNumIdClusters() ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_numShapesSaved ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_drawingsSaved ); pos += 4;
+ for (int i = 0; i < field_5_fileIdClusters.length; i++) {
+ LittleEndian.putInt( data, pos, field_5_fileIdClusters[i].field_1_drawingGroupId ); pos += 4;
+ LittleEndian.putInt( data, pos, field_5_fileIdClusters[i].field_2_numShapeIdsUsed ); pos += 4;
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), getRecordSize(), this );
+ return getRecordSize();
+ }
+
+ @Override
+ public int getRecordSize() {
+ return 8 + 16 + (8 * field_5_fileIdClusters.length);
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "Dgg";
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder field_5_string = new StringBuilder();
+ if(field_5_fileIdClusters != null) for (int i = 0; i < field_5_fileIdClusters.length; i++) {
+ field_5_string.append(" DrawingGroupId").append(i+1).append(": ");
+ field_5_string.append(field_5_fileIdClusters[i].field_1_drawingGroupId);
+ field_5_string.append('\n');
+ field_5_string.append(" NumShapeIdsUsed").append(i+1).append(": ");
+ field_5_string.append(field_5_fileIdClusters[i].field_2_numShapeIdsUsed);
+ field_5_string.append('\n');
+ }
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + '\n' +
+ " Version: 0x" + HexDump.toHex(getVersion()) + '\n' +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + '\n' +
+ " ShapeIdMax: " + field_1_shapeIdMax + '\n' +
+ " NumIdClusters: " + getNumIdClusters() + '\n' +
+ " NumShapesSaved: " + field_3_numShapesSaved + '\n' +
+ " DrawingsSaved: " + field_4_drawingsSaved + '\n' +
+ "" + field_5_string.toString();
+
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(field_1_shapeIdMax).append("\n")
+ .append(tab).append("\t").append("").append(getNumIdClusters()).append("\n")
+ .append(tab).append("\t").append("").append(field_3_numShapesSaved).append("\n")
+ .append(tab).append("\t").append("").append(field_4_drawingsSaved).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Gets the next available shape id
+ *
+ * @return the next available shape id
+ */
+ public int getShapeIdMax() {
+ return field_1_shapeIdMax;
+ }
+
+ /**
+ * The maximum is actually the next available shape id.
+ *
+ * @param shapeIdMax the next available shape id
+ */
+ public void setShapeIdMax(int shapeIdMax) {
+ this.field_1_shapeIdMax = shapeIdMax;
+ }
+
+ /**
+ * Number of id clusters + 1
+ *
+ * @return the number of id clusters + 1
+ */
+ public int getNumIdClusters() {
+ return (field_5_fileIdClusters == null ? 0 : (field_5_fileIdClusters.length + 1));
+ }
+
+ /**
+ * Gets the number of shapes saved
+ *
+ * @return the number of shapes saved
+ */
+ public int getNumShapesSaved() {
+ return field_3_numShapesSaved;
+ }
+
+ /**
+ * Sets the number of shapes saved
+ *
+ * @param numShapesSaved the number of shapes saved
+ */
+ public void setNumShapesSaved(int numShapesSaved) {
+ this.field_3_numShapesSaved = numShapesSaved;
+ }
+
+ /**
+ * Gets the number of drawings saved
+ *
+ * @return the number of drawings saved
+ */
+ public int getDrawingsSaved() {
+ return field_4_drawingsSaved;
+ }
+
+ /**
+ * Sets the number of drawings saved
+ *
+ * @param drawingsSaved the number of drawings saved
+ */
+ public void setDrawingsSaved(int drawingsSaved) {
+ this.field_4_drawingsSaved = drawingsSaved;
+ }
+
+ /**
+ * Gets the maximum drawing group ID
+ *
+ * @return The maximum drawing group ID
+ */
+ public int getMaxDrawingGroupId() {
+ return maxDgId;
+ }
+
+ /**
+ * Sets the maximum drawing group ID
+ *
+ * @param id the maximum drawing group ID
+ */
+ public void setMaxDrawingGroupId(int id) {
+ maxDgId = id;
+ }
+
+ /**
+ * @return the file id clusters
+ */
+ public FileIdCluster[] getFileIdClusters() {
+ return field_5_fileIdClusters;
+ }
+
+ /**
+ * Sets the file id clusters
+ *
+ * @param fileIdClusters the file id clusters
+ */
+ public void setFileIdClusters(FileIdCluster[] fileIdClusters) {
+ this.field_5_fileIdClusters = fileIdClusters.clone();
+ }
+
+
+ /**
+ * Add a new cluster
+ *
+ * @param dgId id of the drawing group (stored in the record options)
+ * @param numShapedUsed initial value of the numShapedUsed field
+ */
+ public void addCluster(int dgId, int numShapedUsed) {
+ addCluster(dgId, numShapedUsed, true);
+ }
+
+ /**
+ * Add a new cluster
+ *
+ * @param dgId id of the drawing group (stored in the record options)
+ * @param numShapedUsed initial value of the numShapedUsed field
+ * @param sort if true then sort clusters by drawing group id.(
+ * In Excel the clusters are sorted but in PPT they are not)
+ */
+ public void addCluster( int dgId, int numShapedUsed, boolean sort ) {
+ List clusters = new ArrayList(Arrays.asList(field_5_fileIdClusters));
+ clusters.add(new FileIdCluster(dgId, numShapedUsed));
+ if(sort) Collections.sort(clusters, MY_COMP );
+ maxDgId = Math.min(maxDgId, dgId);
+ field_5_fileIdClusters = clusters.toArray( new FileIdCluster[clusters.size()] );
+ }
+
+ private static final Comparator MY_COMP = new Comparator() {
+ @Override
+ public int compare(FileIdCluster f1, FileIdCluster f2) {
+ if (f1.getDrawingGroupId() == f2.getDrawingGroupId()) {
+ return 0;
+ }
+ if (f1.getDrawingGroupId() < f2.getDrawingGroupId()) {
+ return -1;
+ }
+ return +1;
+ }
+ };
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherDump.java b/trunk/src/java/org/apache/poi/ddf/EscherDump.java
new file mode 100644
index 000000000..45905c936
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherDump.java
@@ -0,0 +1,924 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.HexRead;
+import org.apache.poi.util.LittleEndian;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * Used to dump the contents of escher records to a PrintStream.
+ */
+public final class EscherDump {
+
+ public EscherDump() {
+ //
+ }
+
+ /**
+ * Decodes the escher stream from a byte array and dumps the results to
+ * a print stream.
+ *
+ * @param data The data array containing the escher records.
+ * @param offset The starting offset within the data array.
+ * @param size The number of bytes to read.
+ * @param out The output stream to write the results to.
+ *
+ */
+ public void dump(byte[] data, int offset, int size, PrintStream out) {
+ EscherRecordFactory recordFactory = new DefaultEscherRecordFactory();
+ int pos = offset;
+ while ( pos < offset + size )
+ {
+ EscherRecord r = recordFactory.createRecord(data, pos);
+ int bytesRead = r.fillFields(data, pos, recordFactory );
+ out.println( r.toString() );
+ pos += bytesRead;
+ }
+ }
+
+ /**
+ * This version of dump is a translation from the open office escher dump routine.
+ *
+ * @param maxLength The number of bytes to read
+ * @param in An input stream to read from.
+ * @param out An output stream to write to.
+ *
+ * @throws IOException if the data can't be read or written
+ * @throws LittleEndian.BufferUnderrunException if an buffer underrun occurs
+ */
+ public void dumpOld(long maxLength, InputStream in, PrintStream out)
+ throws IOException, LittleEndian.BufferUnderrunException {
+ long remainingBytes = maxLength;
+ short options; // 4 bits for the version and 12 bits for the instance
+ short recordId;
+ int recordBytesRemaining; // including enclosing records
+ short nDumpSize;
+ String recordName;
+
+ boolean atEOF = false;
+
+ while (!atEOF && (remainingBytes > 0)) {
+ options = LittleEndian.readShort( in );
+ recordId = LittleEndian.readShort( in );
+ recordBytesRemaining = LittleEndian.readInt( in );
+
+ remainingBytes -= 2 + 2 + 4;
+
+ switch ( recordId )
+ {
+ case (short) 0xF000:
+ recordName = "MsofbtDggContainer";
+ break;
+ case (short) 0xF006:
+ recordName = "MsofbtDgg";
+ break;
+ case (short) 0xF016:
+ recordName = "MsofbtCLSID";
+ break;
+ case (short) 0xF00B:
+ recordName = "MsofbtOPT";
+ break;
+ case (short) 0xF11A:
+ recordName = "MsofbtColorMRU";
+ break;
+ case (short) 0xF11E:
+ recordName = "MsofbtSplitMenuColors";
+ break;
+ case (short) 0xF001:
+ recordName = "MsofbtBstoreContainer";
+ break;
+ case (short) 0xF007:
+ recordName = "MsofbtBSE";
+ break;
+ case (short) 0xF002:
+ recordName = "MsofbtDgContainer";
+ break;
+ case (short) 0xF008:
+ recordName = "MsofbtDg";
+ break;
+ case (short) 0xF118:
+ recordName = "MsofbtRegroupItem";
+ break;
+ case (short) 0xF120:
+ recordName = "MsofbtColorScheme";
+ break;
+ case (short) 0xF003:
+ recordName = "MsofbtSpgrContainer";
+ break;
+ case (short) 0xF004:
+ recordName = "MsofbtSpContainer";
+ break;
+ case (short) 0xF009:
+ recordName = "MsofbtSpgr";
+ break;
+ case (short) 0xF00A:
+ recordName = "MsofbtSp";
+ break;
+ case (short) 0xF00C:
+ recordName = "MsofbtTextbox";
+ break;
+ case (short) 0xF00D:
+ recordName = "MsofbtClientTextbox";
+ break;
+ case (short) 0xF00E:
+ recordName = "MsofbtAnchor";
+ break;
+ case (short) 0xF00F:
+ recordName = "MsofbtChildAnchor";
+ break;
+ case (short) 0xF010:
+ recordName = "MsofbtClientAnchor";
+ break;
+ case (short) 0xF011:
+ recordName = "MsofbtClientData";
+ break;
+ case (short) 0xF11F:
+ recordName = "MsofbtOleObject";
+ break;
+ case (short) 0xF11D:
+ recordName = "MsofbtDeletedPspl";
+ break;
+ case (short) 0xF005:
+ recordName = "MsofbtSolverContainer";
+ break;
+ case (short) 0xF012:
+ recordName = "MsofbtConnectorRule";
+ break;
+ case (short) 0xF013:
+ recordName = "MsofbtAlignRule";
+ break;
+ case (short) 0xF014:
+ recordName = "MsofbtArcRule";
+ break;
+ case (short) 0xF015:
+ recordName = "MsofbtClientRule";
+ break;
+ case (short) 0xF017:
+ recordName = "MsofbtCalloutRule";
+ break;
+ case (short) 0xF119:
+ recordName = "MsofbtSelection";
+ break;
+ case (short) 0xF122:
+ recordName = "MsofbtUDefProp";
+ break;
+ default:
+ if ( recordId >= (short) 0xF018 && recordId <= (short) 0xF117 )
+ recordName = "MsofbtBLIP";
+ else if ( ( options & (short) 0x000F ) == (short) 0x000F )
+ recordName = "UNKNOWN container";
+ else
+ recordName = "UNKNOWN ID";
+ }
+
+ StringBuilder stringBuf = new StringBuilder();
+ stringBuf.append( " " );
+ stringBuf.append( HexDump.toHex( recordId ) );
+ stringBuf.append( " " ).append( recordName ).append( " [" );
+ stringBuf.append( HexDump.toHex( options ) );
+ stringBuf.append( ',' );
+ stringBuf.append( HexDump.toHex( recordBytesRemaining ) );
+ stringBuf.append( "] instance: " );
+ stringBuf.append( HexDump.toHex( ( (short) ( options >> 4 ) ) ) );
+ out.println( stringBuf.toString() );
+ stringBuf.setLength(0);
+
+
+ if ( recordId == (short) 0xF007 && 36 <= remainingBytes && 36 <= recordBytesRemaining )
+ { // BSE, FBSE
+ // ULONG nP = pIn->GetRecPos();
+
+ byte n8;
+ // short n16;
+ // int n32;
+
+ stringBuf = stringBuf.append( " btWin32: " );
+ n8 = (byte) in.read();
+ stringBuf.append( HexDump.toHex( n8 ) );
+ stringBuf.append( getBlipType( n8 ) );
+ stringBuf.append( " btMacOS: " );
+ n8 = (byte) in.read();
+ stringBuf.append( HexDump.toHex( n8 ) );
+ stringBuf.append( getBlipType( n8 ) );
+ out.println( stringBuf.toString() );
+
+ out.println( " rgbUid:" );
+ HexDump.dump( in, out, 0, 16 );
+
+ out.print( " tag: " );
+ outHex( 2, in, out );
+ out.println();
+ out.print( " size: " );
+ outHex( 4, in, out );
+ out.println();
+ out.print( " cRef: " );
+ outHex( 4, in, out );
+ out.println();
+ out.print( " offs: " );
+ outHex( 4, in, out );
+ out.println();
+ out.print( " usage: " );
+ outHex( 1, in, out );
+ out.println();
+ out.print( " cbName: " );
+ outHex( 1, in, out );
+ out.println();
+ out.print( " unused2: " );
+ outHex( 1, in, out );
+ out.println();
+ out.print( " unused3: " );
+ outHex( 1, in, out );
+ out.println();
+
+ // subtract the number of bytes we've read
+ remainingBytes -= 36;
+ //n -= pIn->GetRecPos() - nP;
+ recordBytesRemaining = 0; // loop to MsofbtBLIP
+ }
+ else if ( recordId == (short) 0xF010 && 0x12 <= remainingBytes && 0x12 <= recordBytesRemaining )
+ { // ClientAnchor
+ //ULONG nP = pIn->GetRecPos();
+ // short n16;
+
+ out.print( " Flag: " );
+ outHex( 2, in, out );
+ out.println();
+ out.print( " Col1: " );
+ outHex( 2, in, out );
+ out.print( " dX1: " );
+ outHex( 2, in, out );
+ out.print( " Row1: " );
+ outHex( 2, in, out );
+ out.print( " dY1: " );
+ outHex( 2, in, out );
+ out.println();
+ out.print( " Col2: " );
+ outHex( 2, in, out );
+ out.print( " dX2: " );
+ outHex( 2, in, out );
+ out.print( " Row2: " );
+ outHex( 2, in, out );
+ out.print( " dY2: " );
+ outHex( 2, in, out );
+ out.println();
+
+ remainingBytes -= 18;
+ recordBytesRemaining -= 18;
+
+ }
+ else if ( recordId == (short) 0xF00B || recordId == (short) 0xF122 )
+ { // OPT
+ int nComplex = 0;
+ out.println( " PROPID VALUE" );
+ while ( recordBytesRemaining >= 6 + nComplex && remainingBytes >= 6 + nComplex )
+ {
+ short n16;
+ int n32;
+ n16 = LittleEndian.readShort( in );
+ n32 = LittleEndian.readInt( in );
+
+ recordBytesRemaining -= 6;
+ remainingBytes -= 6;
+ out.print( " " );
+ out.print( HexDump.toHex( n16 ) );
+ out.print( " (" );
+ int propertyId = n16 & (short) 0x3FFF;
+ out.print( " " + propertyId );
+ if ( ( n16 & (short) 0x8000 ) == 0 )
+ {
+ if ( ( n16 & (short) 0x4000 ) != 0 )
+ out.print( ", fBlipID" );
+ out.print( ") " );
+
+ out.print( HexDump.toHex( n32 ) );
+
+ if ( ( n16 & (short) 0x4000 ) == 0 )
+ {
+ out.print( " (" );
+ out.print( dec1616( n32 ) );
+ out.print( ')' );
+ out.print( " {" + propName( (short)propertyId ) + "}" );
+ }
+ out.println();
+ }
+ else
+ {
+ out.print( ", fComplex) " );
+ out.print( HexDump.toHex( n32 ) );
+ out.print( " - Complex prop len" );
+ out.println( " {" + propName( (short)propertyId ) + "}" );
+
+ nComplex += n32;
+ }
+
+ }
+ // complex property data
+ while ( ( nComplex & remainingBytes ) > 0 )
+ {
+ nDumpSize = ( nComplex > (int) remainingBytes ) ? (short) remainingBytes : (short) nComplex;
+ HexDump.dump( in, out, 0, nDumpSize );
+ nComplex -= nDumpSize;
+ recordBytesRemaining -= nDumpSize;
+ remainingBytes -= nDumpSize;
+ }
+ }
+ else if ( recordId == (short) 0xF012 )
+ {
+ out.print( " Connector rule: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " ShapeID A: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " ShapeID B: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " ShapeID connector: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " Connect pt A: " );
+ out.print( LittleEndian.readInt( in ) );
+ out.print( " Connect pt B: " );
+ out.println( LittleEndian.readInt( in ) );
+
+ recordBytesRemaining -= 24;
+ remainingBytes -= 24;
+ }
+ else if ( recordId >= (short) 0xF018 && recordId < (short) 0xF117 )
+ {
+ out.println( " Secondary UID: " );
+ HexDump.dump( in, out, 0, 16 );
+ out.println( " Cache of size: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary top: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary left: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary width: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Boundary height: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " X: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Y: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Cache of saved size: " + HexDump.toHex( LittleEndian.readInt( in ) ) );
+ out.println( " Compression Flag: " + HexDump.toHex( (byte) in.read() ) );
+ out.println( " Filter: " + HexDump.toHex( (byte) in.read() ) );
+ out.println( " Data (after decompression): " );
+
+ recordBytesRemaining -= 34 + 16;
+ remainingBytes -= 34 + 16;
+
+ nDumpSize = ( recordBytesRemaining > (int) remainingBytes ) ? (short) remainingBytes : (short) recordBytesRemaining;
+
+
+ byte[] buf = new byte[nDumpSize];
+ int read = in.read( buf );
+ while ( read != -1 && read < nDumpSize )
+ read += in.read( buf, read, buf.length );
+ ByteArrayInputStream bin = new ByteArrayInputStream( buf );
+
+ InputStream in1 = new InflaterInputStream( bin );
+ int bytesToDump = -1;
+ HexDump.dump( in1, out, 0, bytesToDump );
+
+ recordBytesRemaining -= nDumpSize;
+ remainingBytes -= nDumpSize;
+
+ }
+
+ boolean isContainer = ( options & (short) 0x000F ) == (short) 0x000F;
+ if ( isContainer && remainingBytes >= 0 )
+ { // Container
+ if ( recordBytesRemaining <= (int) remainingBytes )
+ out.println( " completed within" );
+ else
+ out.println( " continued elsewhere" );
+ }
+ else if ( remainingBytes >= 0 )
+ // -> 0x0000 ... 0x0FFF
+ {
+ nDumpSize = ( recordBytesRemaining > (int) remainingBytes ) ? (short) remainingBytes : (short) recordBytesRemaining;
+
+ if ( nDumpSize != 0 )
+ {
+ HexDump.dump( in, out, 0, nDumpSize );
+ remainingBytes -= nDumpSize;
+ }
+ }
+ else
+ out.println( " >> OVERRUN <<" );
+ }
+
+ }
+
+ /**
+ * Returns a property name given a property id. This is used only by the
+ * old escher dump routine.
+ *
+ * @param propertyId The property number for the name
+ * @return A descriptive name.
+ */
+ private String propName(short propertyId) {
+ final class PropName {
+ final int _id;
+ final String _name;
+ public PropName(int id, String name) {
+ _id = id;
+ _name = name;
+ }
+ }
+
+ final PropName[] props = new PropName[] {
+ new PropName(4, "transform.rotation"),
+ new PropName(119, "protection.lockrotation"),
+ new PropName(120, "protection.lockaspectratio"),
+ new PropName(121, "protection.lockposition"),
+ new PropName(122, "protection.lockagainstselect"),
+ new PropName(123, "protection.lockcropping"),
+ new PropName(124, "protection.lockvertices"),
+ new PropName(125, "protection.locktext"),
+ new PropName(126, "protection.lockadjusthandles"),
+ new PropName(127, "protection.lockagainstgrouping"),
+ new PropName(128, "text.textid"),
+ new PropName(129, "text.textleft"),
+ new PropName(130, "text.texttop"),
+ new PropName(131, "text.textright"),
+ new PropName(132, "text.textbottom"),
+ new PropName(133, "text.wraptext"),
+ new PropName(134, "text.scaletext"),
+ new PropName(135, "text.anchortext"),
+ new PropName(136, "text.textflow"),
+ new PropName(137, "text.fontrotation"),
+ new PropName(138, "text.idofnextshape"),
+ new PropName(139, "text.bidir"),
+ new PropName(187, "text.singleclickselects"),
+ new PropName(188, "text.usehostmargins"),
+ new PropName(189, "text.rotatetextwithshape"),
+ new PropName(190, "text.sizeshapetofittext"),
+ new PropName(191, "text.sizetexttofitshape"),
+ new PropName(192, "geotext.unicode"),
+ new PropName(193, "geotext.rtftext"),
+ new PropName(194, "geotext.alignmentoncurve"),
+ new PropName(195, "geotext.defaultpointsize"),
+ new PropName(196, "geotext.textspacing"),
+ new PropName(197, "geotext.fontfamilyname"),
+ new PropName(240, "geotext.reverseroworder"),
+ new PropName(241, "geotext.hastexteffect"),
+ new PropName(242, "geotext.rotatecharacters"),
+ new PropName(243, "geotext.kerncharacters"),
+ new PropName(244, "geotext.tightortrack"),
+ new PropName(245, "geotext.stretchtofitshape"),
+ new PropName(246, "geotext.charboundingbox"),
+ new PropName(247, "geotext.scaletextonpath"),
+ new PropName(248, "geotext.stretchcharheight"),
+ new PropName(249, "geotext.nomeasurealongpath"),
+ new PropName(250, "geotext.boldfont"),
+ new PropName(251, "geotext.italicfont"),
+ new PropName(252, "geotext.underlinefont"),
+ new PropName(253, "geotext.shadowfont"),
+ new PropName(254, "geotext.smallcapsfont"),
+ new PropName(255, "geotext.strikethroughfont"),
+ new PropName(256, "blip.cropfromtop"),
+ new PropName(257, "blip.cropfrombottom"),
+ new PropName(258, "blip.cropfromleft"),
+ new PropName(259, "blip.cropfromright"),
+ new PropName(260, "blip.bliptodisplay"),
+ new PropName(261, "blip.blipfilename"),
+ new PropName(262, "blip.blipflags"),
+ new PropName(263, "blip.transparentcolor"),
+ new PropName(264, "blip.contrastsetting"),
+ new PropName(265, "blip.brightnesssetting"),
+ new PropName(266, "blip.gamma"),
+ new PropName(267, "blip.pictureid"),
+ new PropName(268, "blip.doublemod"),
+ new PropName(269, "blip.picturefillmod"),
+ new PropName(270, "blip.pictureline"),
+ new PropName(271, "blip.printblip"),
+ new PropName(272, "blip.printblipfilename"),
+ new PropName(273, "blip.printflags"),
+ new PropName(316, "blip.nohittestpicture"),
+ new PropName(317, "blip.picturegray"),
+ new PropName(318, "blip.picturebilevel"),
+ new PropName(319, "blip.pictureactive"),
+ new PropName(320, "geometry.left"),
+ new PropName(321, "geometry.top"),
+ new PropName(322, "geometry.right"),
+ new PropName(323, "geometry.bottom"),
+ new PropName(324, "geometry.shapepath"),
+ new PropName(325, "geometry.vertices"),
+ new PropName(326, "geometry.segmentinfo"),
+ new PropName(327, "geometry.adjustvalue"),
+ new PropName(328, "geometry.adjust2value"),
+ new PropName(329, "geometry.adjust3value"),
+ new PropName(330, "geometry.adjust4value"),
+ new PropName(331, "geometry.adjust5value"),
+ new PropName(332, "geometry.adjust6value"),
+ new PropName(333, "geometry.adjust7value"),
+ new PropName(334, "geometry.adjust8value"),
+ new PropName(335, "geometry.adjust9value"),
+ new PropName(336, "geometry.adjust10value"),
+ new PropName(378, "geometry.shadowOK"),
+ new PropName(379, "geometry.3dok"),
+ new PropName(380, "geometry.lineok"),
+ new PropName(381, "geometry.geotextok"),
+ new PropName(382, "geometry.fillshadeshapeok"),
+ new PropName(383, "geometry.fillok"),
+ new PropName(384, "fill.filltype"),
+ new PropName(385, "fill.fillcolor"),
+ new PropName(386, "fill.fillopacity"),
+ new PropName(387, "fill.fillbackcolor"),
+ new PropName(388, "fill.backopacity"),
+ new PropName(389, "fill.crmod"),
+ new PropName(390, "fill.patterntexture"),
+ new PropName(391, "fill.blipfilename"),
+ new PropName(392, "fill.blipflags"),
+ new PropName(393, "fill.width"),
+ new PropName(394, "fill.height"),
+ new PropName(395, "fill.angle"),
+ new PropName(396, "fill.focus"),
+ new PropName(397, "fill.toleft"),
+ new PropName(398, "fill.totop"),
+ new PropName(399, "fill.toright"),
+ new PropName(400, "fill.tobottom"),
+ new PropName(401, "fill.rectleft"),
+ new PropName(402, "fill.recttop"),
+ new PropName(403, "fill.rectright"),
+ new PropName(404, "fill.rectbottom"),
+ new PropName(405, "fill.dztype"),
+ new PropName(406, "fill.shadepreset"),
+ new PropName(407, "fill.shadecolors"),
+ new PropName(408, "fill.originx"),
+ new PropName(409, "fill.originy"),
+ new PropName(410, "fill.shapeoriginx"),
+ new PropName(411, "fill.shapeoriginy"),
+ new PropName(412, "fill.shadetype"),
+ new PropName(443, "fill.filled"),
+ new PropName(444, "fill.hittestfill"),
+ new PropName(445, "fill.shape"),
+ new PropName(446, "fill.userect"),
+ new PropName(447, "fill.nofillhittest"),
+ new PropName(448, "linestyle.color"),
+ new PropName(449, "linestyle.opacity"),
+ new PropName(450, "linestyle.backcolor"),
+ new PropName(451, "linestyle.crmod"),
+ new PropName(452, "linestyle.linetype"),
+ new PropName(453, "linestyle.fillblip"),
+ new PropName(454, "linestyle.fillblipname"),
+ new PropName(455, "linestyle.fillblipflags"),
+ new PropName(456, "linestyle.fillwidth"),
+ new PropName(457, "linestyle.fillheight"),
+ new PropName(458, "linestyle.filldztype"),
+ new PropName(459, "linestyle.linewidth"),
+ new PropName(460, "linestyle.linemiterlimit"),
+ new PropName(461, "linestyle.linestyle"),
+ new PropName(462, "linestyle.linedashing"),
+ new PropName(463, "linestyle.linedashstyle"),
+ new PropName(464, "linestyle.linestartarrowhead"),
+ new PropName(465, "linestyle.lineendarrowhead"),
+ new PropName(466, "linestyle.linestartarrowwidth"),
+ new PropName(467, "linestyle.lineestartarrowlength"),
+ new PropName(468, "linestyle.lineendarrowwidth"),
+ new PropName(469, "linestyle.lineendarrowlength"),
+ new PropName(470, "linestyle.linejoinstyle"),
+ new PropName(471, "linestyle.lineendcapstyle"),
+ new PropName(507, "linestyle.arrowheadsok"),
+ new PropName(508, "linestyle.anyline"),
+ new PropName(509, "linestyle.hitlinetest"),
+ new PropName(510, "linestyle.linefillshape"),
+ new PropName(511, "linestyle.nolinedrawdash"),
+ new PropName(512, "shadowstyle.type"),
+ new PropName(513, "shadowstyle.color"),
+ new PropName(514, "shadowstyle.highlight"),
+ new PropName(515, "shadowstyle.crmod"),
+ new PropName(516, "shadowstyle.opacity"),
+ new PropName(517, "shadowstyle.offsetx"),
+ new PropName(518, "shadowstyle.offsety"),
+ new PropName(519, "shadowstyle.secondoffsetx"),
+ new PropName(520, "shadowstyle.secondoffsety"),
+ new PropName(521, "shadowstyle.scalextox"),
+ new PropName(522, "shadowstyle.scaleytox"),
+ new PropName(523, "shadowstyle.scalextoy"),
+ new PropName(524, "shadowstyle.scaleytoy"),
+ new PropName(525, "shadowstyle.perspectivex"),
+ new PropName(526, "shadowstyle.perspectivey"),
+ new PropName(527, "shadowstyle.weight"),
+ new PropName(528, "shadowstyle.originx"),
+ new PropName(529, "shadowstyle.originy"),
+ new PropName(574, "shadowstyle.shadow"),
+ new PropName(575, "shadowstyle.shadowobsured"),
+ new PropName(576, "perspective.type"),
+ new PropName(577, "perspective.offsetx"),
+ new PropName(578, "perspective.offsety"),
+ new PropName(579, "perspective.scalextox"),
+ new PropName(580, "perspective.scaleytox"),
+ new PropName(581, "perspective.scalextoy"),
+ new PropName(582, "perspective.scaleytox"),
+ new PropName(583, "perspective.perspectivex"),
+ new PropName(584, "perspective.perspectivey"),
+ new PropName(585, "perspective.weight"),
+ new PropName(586, "perspective.originx"),
+ new PropName(587, "perspective.originy"),
+ new PropName(639, "perspective.perspectiveon"),
+ new PropName(640, "3d.specularamount"),
+ new PropName(661, "3d.diffuseamount"),
+ new PropName(662, "3d.shininess"),
+ new PropName(663, "3d.edgethickness"),
+ new PropName(664, "3d.extrudeforward"),
+ new PropName(665, "3d.extrudebackward"),
+ new PropName(666, "3d.extrudeplane"),
+ new PropName(667, "3d.extrusioncolor"),
+ new PropName(648, "3d.crmod"),
+ new PropName(700, "3d.3deffect"),
+ new PropName(701, "3d.metallic"),
+ new PropName(702, "3d.useextrusioncolor"),
+ new PropName(703, "3d.lightface"),
+ new PropName(704, "3dstyle.yrotationangle"),
+ new PropName(705, "3dstyle.xrotationangle"),
+ new PropName(706, "3dstyle.rotationaxisx"),
+ new PropName(707, "3dstyle.rotationaxisy"),
+ new PropName(708, "3dstyle.rotationaxisz"),
+ new PropName(709, "3dstyle.rotationangle"),
+ new PropName(710, "3dstyle.rotationcenterx"),
+ new PropName(711, "3dstyle.rotationcentery"),
+ new PropName(712, "3dstyle.rotationcenterz"),
+ new PropName(713, "3dstyle.rendermode"),
+ new PropName(714, "3dstyle.tolerance"),
+ new PropName(715, "3dstyle.xviewpoint"),
+ new PropName(716, "3dstyle.yviewpoint"),
+ new PropName(717, "3dstyle.zviewpoint"),
+ new PropName(718, "3dstyle.originx"),
+ new PropName(719, "3dstyle.originy"),
+ new PropName(720, "3dstyle.skewangle"),
+ new PropName(721, "3dstyle.skewamount"),
+ new PropName(722, "3dstyle.ambientintensity"),
+ new PropName(723, "3dstyle.keyx"),
+ new PropName(724, "3dstyle.keyy"),
+ new PropName(725, "3dstyle.keyz"),
+ new PropName(726, "3dstyle.keyintensity"),
+ new PropName(727, "3dstyle.fillx"),
+ new PropName(728, "3dstyle.filly"),
+ new PropName(729, "3dstyle.fillz"),
+ new PropName(730, "3dstyle.fillintensity"),
+ new PropName(763, "3dstyle.constrainrotation"),
+ new PropName(764, "3dstyle.rotationcenterauto"),
+ new PropName(765, "3dstyle.parallel"),
+ new PropName(766, "3dstyle.keyharsh"),
+ new PropName(767, "3dstyle.fillharsh"),
+ new PropName(769, "shape.master"),
+ new PropName(771, "shape.connectorstyle"),
+ new PropName(772, "shape.blackandwhitesettings"),
+ new PropName(773, "shape.wmodepurebw"),
+ new PropName(774, "shape.wmodebw"),
+ new PropName(826, "shape.oleicon"),
+ new PropName(827, "shape.preferrelativeresize"),
+ new PropName(828, "shape.lockshapetype"),
+ new PropName(830, "shape.deleteattachedobject"),
+ new PropName(831, "shape.backgroundshape"),
+ new PropName(832, "callout.callouttype"),
+ new PropName(833, "callout.xycalloutgap"),
+ new PropName(834, "callout.calloutangle"),
+ new PropName(835, "callout.calloutdroptype"),
+ new PropName(836, "callout.calloutdropspecified"),
+ new PropName(837, "callout.calloutlengthspecified"),
+ new PropName(889, "callout.iscallout"),
+ new PropName(890, "callout.calloutaccentbar"),
+ new PropName(891, "callout.callouttextborder"),
+ new PropName(892, "callout.calloutminusx"),
+ new PropName(893, "callout.calloutminusy"),
+ new PropName(894, "callout.dropauto"),
+ new PropName(895, "callout.lengthspecified"),
+ new PropName(896, "groupshape.shapename"),
+ new PropName(897, "groupshape.description"),
+ new PropName(898, "groupshape.hyperlink"),
+ new PropName(899, "groupshape.wrappolygonvertices"),
+ new PropName(900, "groupshape.wrapdistleft"),
+ new PropName(901, "groupshape.wrapdisttop"),
+ new PropName(902, "groupshape.wrapdistright"),
+ new PropName(903, "groupshape.wrapdistbottom"),
+ new PropName(904, "groupshape.regroupid"),
+ new PropName(953, "groupshape.editedwrap"),
+ new PropName(954, "groupshape.behinddocument"),
+ new PropName(955, "groupshape.ondblclicknotify"),
+ new PropName(956, "groupshape.isbutton"),
+ new PropName(957, "groupshape.1dadjustment"),
+ new PropName(958, "groupshape.hidden"),
+ new PropName(959, "groupshape.print"),
+ };
+
+ for (int i = 0; i < props.length; i++) {
+ if (props[i]._id == propertyId) {
+ return props[i]._name;
+ }
+ }
+
+ return "unknown property";
+ }
+
+ /**
+ * Returns the blip description given a blip id.
+ *
+ * @param b blip id
+ * @return A description.
+ */
+ private static String getBlipType(byte b) {
+ return EscherBSERecord.getBlipType(b);
+ }
+
+ /**
+ * Straight conversion from OO. Converts a type of float.
+ */
+ private String dec1616( int n32 )
+ {
+ String result = "";
+ result += (short) ( n32 >> 16 );
+ result += '.';
+ result += (short) ( n32 & 0xFFFF );
+ return result;
+ }
+
+ /**
+ * Dumps out a hex value by reading from a input stream.
+ *
+ * @param bytes How many bytes this hex value consists of.
+ * @param in The stream to read the hex value from.
+ * @param out The stream to write the nicely formatted hex value to.
+ */
+ private void outHex( int bytes, InputStream in, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException
+ {
+ switch ( bytes )
+ {
+ case 1:
+ out.print( HexDump.toHex( (byte) in.read() ) );
+ break;
+ case 2:
+ out.print( HexDump.toHex( LittleEndian.readShort( in ) ) );
+ break;
+ case 4:
+ out.print( HexDump.toHex( LittleEndian.readInt( in ) ) );
+ break;
+ default:
+ throw new IOException( "Unable to output variable of that width" );
+ }
+ }
+
+ /**
+ * A simple test stub.
+ *
+ * @param args the args
+ */
+ public static void main( String[] args ) {
+ main(args, System.out);
+ }
+
+ public static void main( String[] args, PrintStream out ) {
+ String dump =
+ "0F 00 00 F0 89 07 00 00 00 00 06 F0 18 00 00 00 " +
+ "05 04 00 00 02 00 00 00 05 00 00 00 01 00 00 00 " +
+ "01 00 00 00 05 00 00 00 4F 00 01 F0 2F 07 00 00 " +
+ "42 00 07 F0 B7 01 00 00 03 04 3F 14 AE 6B 0F 65 " +
+ "B0 48 BF 5E 94 63 80 E8 91 73 FF 00 93 01 00 00 " +
+ "01 00 00 00 00 00 00 00 00 00 FF FF 20 54 1C F0 " +
+ "8B 01 00 00 3F 14 AE 6B 0F 65 B0 48 BF 5E 94 63 " +
+ "80 E8 91 73 92 0E 00 00 00 00 00 00 00 00 00 00 " +
+ "D1 07 00 00 DD 05 00 00 4A AD 6F 00 8A C5 53 00 " +
+ "59 01 00 00 00 FE 78 9C E3 9B C4 00 04 AC 77 D9 " +
+ "2F 32 08 32 FD E7 61 F8 FF 0F C8 FD 05 C5 30 19 " +
+ "10 90 63 90 FA 0F 06 0C 8C 0C 5C 70 19 43 30 EB " +
+ "0E FB 05 86 85 0C DB 18 58 80 72 8C 70 16 0B 83 " +
+ "05 56 51 29 88 C9 60 D9 69 0C 6C 20 26 23 03 C8 " +
+ "74 B0 A8 0E 03 07 FB 45 56 C7 A2 CC C4 1C 06 66 " +
+ "A0 0D 2C 40 39 5E 86 4C 06 3D A0 4E 10 D0 60 D9 " +
+ "C8 58 CC E8 CF B0 80 61 3A 8A 7E 0D C6 23 AC 4F " +
+ "E0 E2 98 B6 12 2B 06 73 9D 12 E3 52 56 59 F6 08 " +
+ "8A CC 52 66 A3 50 FF 96 2B 94 E9 DF 4C A1 FE 2D " +
+ "3A 03 AB 9F 81 C2 F0 A3 54 BF 0F 85 EE A7 54 FF " +
+ "40 FB 7F A0 E3 9F D2 F4 4F 71 FE 19 58 FF 2B 31 " +
+ "7F 67 36 3B 25 4F 99 1B 4E 53 A6 5F 89 25 95 E9 " +
+ "C4 00 C7 83 12 F3 1F 26 35 4A D3 D2 47 0E 0A C3 " +
+ "41 8E C9 8A 52 37 DC 15 A1 D0 0D BC 4C 06 0C 2B " +
+ "28 2C 13 28 D4 EF 43 61 5A A0 58 3F 85 71 E0 4B " +
+ "69 9E 64 65 FE 39 C0 E5 22 30 1D 30 27 0E 74 3A " +
+ "18 60 FD 4A CC B1 2C 13 7D 07 36 2D 2A 31 85 B2 " +
+ "6A 0D 74 1D 1D 22 4D 99 FE 60 0A F5 9B EC 1C 58 " +
+ "FD 67 06 56 3F 38 0D 84 3C A5 30 0E 28 D3 AF C4 " +
+ "A4 CA FA 44 7A 0D 65 6E 60 7F 4D A1 1B 24 58 F7 " +
+ "49 AF A5 CC 0D CC DF 19 FE 03 00 F0 B1 25 4D 42 " +
+ "00 07 F0 E1 01 00 00 03 04 39 50 BE 98 B0 6F 57 " +
+ "24 31 70 5D 23 2F 9F 10 66 FF 00 BD 01 00 00 01 " +
+ "00 00 00 00 00 00 00 00 00 FF FF 20 54 1C F0 B5 " +
+ "01 00 00 39 50 BE 98 B0 6F 57 24 31 70 5D 23 2F " +
+ "9F 10 66 DA 03 00 00 00 00 00 00 00 00 00 00 D1 " +
+ "07 00 00 DD 05 00 00 4A AD 6F 00 8A C5 53 00 83 " +
+ "01 00 00 00 FE 78 9C A5 52 BF 4B 42 51 14 3E F7 " +
+ "DC 77 7A 16 45 48 8B 3C 48 A8 16 15 0D 6C 88 D0 " +
+ "04 C3 40 A3 32 1C 84 96 08 21 04 A1 C5 5C A2 35 " +
+ "82 C0 35 6A AB 1C 6A 6B A8 24 5A 83 68 08 84 84 " +
+ "96 A2 86 A0 7F C2 86 5E E7 5E F5 41 E4 10 BC 03 " +
+ "1F E7 FB F1 CE B9 F7 F1 9E 7C 05 2E 7A 37 9B E0 " +
+ "45 7B 10 EC 6F 96 5F 1D 74 13 55 7E B0 6C 5D 20 " +
+ "60 C0 49 A2 9A BD 99 4F 50 83 1B 30 38 13 0E 33 " +
+ "60 A6 A7 6B B5 37 EB F4 10 FA 14 15 A0 B6 6B 37 " +
+ "0C 1E B3 49 73 5B A5 C2 26 48 3E C1 E0 6C 08 4A " +
+ "30 C9 93 AA 02 B8 20 13 62 05 4E E1 E8 D7 7C C0 " +
+ "B8 14 95 5E BE B8 A7 CF 1E BE 55 2C 56 B9 78 DF " +
+ "08 7E 88 4C 27 FF 7B DB FF 7A DD B7 1A 17 67 34 " +
+ "6A AE BA DA 35 D1 E7 72 BE FE EC 6E FE DA E5 7C " +
+ "3D EC 7A DE 03 FD 50 06 0B 23 F2 0E F3 B2 A5 11 " +
+ "91 0D 4C B5 B5 F3 BF 94 C1 8F 24 F7 D9 6F 60 94 " +
+ "3B C9 9A F3 1C 6B E7 BB F0 2E 49 B2 25 2B C6 B1 " +
+ "EE 69 EE 15 63 4F 71 7D CE 85 CC C8 35 B9 C3 28 " +
+ "28 CE D0 5C 67 79 F2 4A A2 14 23 A4 38 43 73 9D " +
+ "2D 69 2F C1 08 31 9F C5 5C 9B EB 7B C5 69 19 B3 " +
+ "B4 81 F3 DC E3 B4 8E 8B CC B3 94 53 5A E7 41 2A " +
+ "63 9A AA 38 C5 3D 48 BB EC 57 59 6F 2B AD 73 1F " +
+ "1D 60 92 AE 70 8C BB 8F CE 31 C1 3C 49 27 4A EB " +
+ "DC A4 5B 8C D1 0B 0E 73 37 E9 11 A7 99 C7 E8 41 " +
+ "69 B0 7F 00 96 F2 A7 E8 42 00 07 F0 B4 01 00 00 " +
+ "03 04 1A BA F9 D6 A9 B9 3A 03 08 61 E9 90 FF 7B " +
+ "9E E6 FF 00 90 01 00 00 01 00 00 00 00 00 00 00 " +
+ "00 00 FF FF 20 54 1C F0 88 01 00 00 1A BA F9 D6 " +
+ "A9 B9 3A 03 08 61 E9 90 FF 7B 9E E6 12 0E 00 00 " +
+ "00 00 00 00 00 00 00 00 D1 07 00 00 DD 05 00 00 " +
+ "4A AD 6F 00 8A C5 53 00 56 01 00 00 00 FE 78 9C " +
+ "E3 13 62 00 02 D6 BB EC 17 19 04 99 FE F3 30 FC " +
+ "FF 07 E4 FE 82 62 98 0C 08 C8 31 48 FD 07 03 06 " +
+ "46 06 2E B8 8C 21 98 75 87 FD 02 C3 42 86 6D 0C " +
+ "2C 40 39 46 38 8B 85 C1 02 AB A8 14 C4 64 B0 EC " +
+ "34 06 36 10 93 91 01 64 3A 58 54 87 81 83 FD 22 " +
+ "AB 63 51 66 62 0E 03 33 D0 06 16 A0 1C 2F 43 26 " +
+ "83 1E 50 27 08 68 B0 6C 64 2C 66 F4 67 58 C0 30 " +
+ "1D 45 BF 06 E3 11 D6 27 70 71 4C 5B 89 15 83 B9 " +
+ "4E 89 71 29 AB 2C 7B 04 45 66 29 B3 51 A8 7F CB " +
+ "15 CA F4 6F A6 50 FF 16 9D 81 D5 CF 40 61 F8 51 " +
+ "AA DF 87 42 F7 53 AA 7F A0 FD 3F D0 F1 4F 69 FA " +
+ "A7 38 FF 0C AC FF 95 98 BF 33 9B 9D 92 A7 CC 0D " +
+ "A7 29 D3 AF C4 92 CA 74 62 80 E3 41 89 F9 0F 93 " +
+ "1A A5 69 E9 23 07 85 E1 20 C7 64 45 A9 1B EE 8A " +
+ "50 E8 06 5E 26 03 86 15 14 96 09 14 EA F7 A1 30 " +
+ "2D 50 AC 9F C2 38 F0 A5 34 4F B2 32 FF 1C E0 72 " +
+ "11 98 0E 98 13 07 38 1D 28 31 C7 B2 4C F4 1D D8 " +
+ "B4 A0 C4 14 CA AA 35 D0 75 64 88 34 65 FA 83 29 " +
+ "D4 6F B2 73 60 F5 9F A1 54 FF 0E CA D3 40 C8 53 " +
+ "0A E3 E0 09 85 6E 50 65 7D 22 BD 86 32 37 B0 BF " +
+ "A6 D0 0D 12 AC FB A4 D7 52 E6 06 E6 EF 0C FF 01 " +
+ "97 1D 12 C7 42 00 07 F0 C3 01 00 00 03 04 BA 4C " +
+ "B6 23 BA 8B 27 BE C8 55 59 86 24 9F 89 D4 FF 00 " +
+ "9F 01 00 00 01 00 00 00 00 00 00 00 00 00 FF FF " +
+ "20 54 1C F0 97 01 00 00 BA 4C B6 23 BA 8B 27 BE " +
+ "C8 55 59 86 24 9F 89 D4 AE 0E 00 00 00 00 00 00 " +
+ "00 00 00 00 D1 07 00 00 DD 05 00 00 4A AD 6F 00 " +
+ "8A C5 53 00 65 01 00 00 00 FE 78 9C E3 5B C7 00 " +
+ "04 AC 77 D9 2F 32 08 32 FD E7 61 F8 FF 0F C8 FD " +
+ "05 C5 30 19 10 90 63 90 FA 0F 06 0C 8C 0C 5C 70 " +
+ "19 43 30 EB 0E FB 05 86 85 0C DB 18 58 80 72 8C " +
+ "70 16 0B 83 05 56 51 29 88 C9 60 D9 69 0C 6C 20 " +
+ "26 23 03 C8 74 B0 A8 0E 03 07 FB 45 56 C7 A2 CC " +
+ "C4 1C 06 66 A0 0D 2C 40 39 5E 86 4C 06 3D A0 4E " +
+ "10 D0 60 99 C6 B8 98 D1 9F 61 01 C3 74 14 FD 1A " +
+ "8C 2B D8 84 B1 88 4B A5 A5 75 03 01 50 DF 59 46 " +
+ "77 46 0F A8 3C A6 AB 88 15 83 B9 5E 89 B1 8B D5 " +
+ "97 2D 82 22 B3 94 29 D5 BF E5 CA C0 EA DF AC 43 " +
+ "A1 FD 14 EA 67 A0 30 FC 28 D5 EF 43 A1 FB 7D 87 " +
+ "B8 FF 07 3A FE 07 3A FD 53 EA 7E 0A C3 4F 89 F9 " +
+ "0E 73 EA 69 79 CA DC 70 8A 32 FD 4A 2C 5E 4C DF " +
+ "87 7A 3C BC E0 A5 30 1E 3E 31 C5 33 AC A0 30 2F " +
+ "52 A8 DF 87 C2 30 A4 54 3F A5 65 19 85 65 A9 12 " +
+ "D3 2B 16 0D 8A CB 13 4A F3 E3 27 E6 09 03 9D 0E " +
+ "06 58 BF 12 B3 13 CB C1 01 4E 8B 4A 4C 56 AC 91 " +
+ "03 5D 37 86 48 53 A6 3F 98 42 FD 26 3B 07 56 FF " +
+ "99 1D 14 EA A7 CC 7E 70 1A 08 79 42 61 1C 3C A5 " +
+ "D0 0D 9C 6C C2 32 6B 29 73 03 DB 6B CA DC C0 F8 " +
+ "97 F5 AD CC 1A CA DC C0 F4 83 32 37 B0 A4 30 CE " +
+ "FC C7 48 99 1B FE 33 32 FC 07 00 6C CC 2E 23 33 " +
+ "00 0B F0 12 00 00 00 BF 00 08 00 08 00 81 01 09 " +
+ "00 00 08 C0 01 40 00 00 08 40 00 1E F1 10 00 00 " +
+ "00 0D 00 00 08 0C 00 00 08 17 00 00 08 F7 00 00 " +
+ "10 ";
+
+ // Decode the stream to bytes
+ byte[] bytes = HexRead.readFromString(dump);
+ // Create a new instance of the escher dumper
+ EscherDump dumper = new EscherDump();
+ // Dump the contents of scher to screen.
+// dumper.dumpOld( bytes.length, new ByteArrayInputStream( bytes ), System.out );
+ dumper.dump(bytes, 0, bytes.length, out);
+
+ }
+
+ public void dump( int recordSize, byte[] data, PrintStream out ) {
+ dump( data, 0, recordSize, out );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherMetafileBlip.java b/trunk/src/java/org/apache/poi/ddf/EscherMetafileBlip.java
new file mode 100644
index 000000000..740df26dd
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherMetafileBlip.java
@@ -0,0 +1,410 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.hssf.usermodel.HSSFPictureData;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.DeflaterOutputStream;
+
+public final class EscherMetafileBlip extends EscherBlipRecord {
+ private static final POILogger log = POILogFactory.getLogger(EscherMetafileBlip.class);
+
+ public static final short RECORD_ID_EMF = (short) 0xF018 + 2;
+ public static final short RECORD_ID_WMF = (short) 0xF018 + 3;
+ public static final short RECORD_ID_PICT = (short) 0xF018 + 4;
+
+ private static final int HEADER_SIZE = 8;
+
+ private final byte[] field_1_UID = new byte[16];
+ /**
+ * The primary UID is only saved to disk if (blip_instance ^ blip_signature == 1)
+ */
+ private final byte[] field_2_UID = new byte[16];
+ private int field_2_cb;
+ private int field_3_rcBounds_x1;
+ private int field_3_rcBounds_y1;
+ private int field_3_rcBounds_x2;
+ private int field_3_rcBounds_y2;
+ private int field_4_ptSize_w;
+ private int field_4_ptSize_h;
+ private int field_5_cbSave;
+ private byte field_6_fCompression;
+ private byte field_7_fFilter;
+
+ private byte[] raw_pictureData;
+ private byte[] remainingData;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesAfterHeader = readHeader( data, offset );
+ int pos = offset + HEADER_SIZE;
+ System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16;
+
+ if((getOptions() ^ getSignature()) == 0x10){
+ System.arraycopy( data, pos, field_2_UID, 0, 16 ); pos += 16;
+ }
+
+ field_2_cb = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4;
+ field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4;
+ field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4;
+ field_6_fCompression = data[pos]; pos++;
+ field_7_fFilter = data[pos]; pos++;
+
+ raw_pictureData = new byte[field_5_cbSave];
+ System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave );
+ pos += field_5_cbSave;
+
+ // 0 means DEFLATE compression
+ // 0xFE means no compression
+ if (field_6_fCompression == 0) {
+ field_pictureData = inflatePictureData(raw_pictureData);
+ } else {
+ field_pictureData = raw_pictureData;
+ }
+
+ int remaining = bytesAfterHeader - pos + offset + HEADER_SIZE;
+ if(remaining > 0) {
+ remainingData = new byte[remaining];
+ System.arraycopy( data, pos, remainingData, 0, remaining );
+ }
+ return bytesAfterHeader + HEADER_SIZE;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize(offset, getRecordId(), this);
+
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ LittleEndian.putInt( data, pos, getRecordSize() - HEADER_SIZE ); pos += 4;
+
+ System.arraycopy( field_1_UID, 0, data, pos, field_1_UID.length ); pos += field_1_UID.length;
+ if ((getOptions() ^ getSignature()) == 0x10) {
+ System.arraycopy( field_2_UID, 0, data, pos, field_2_UID.length ); pos += field_2_UID.length;
+ }
+ LittleEndian.putInt( data, pos, field_2_cb ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4;
+ LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4;
+ data[pos] = field_6_fCompression; pos++;
+ data[pos] = field_7_fFilter; pos++;
+
+ System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length ); pos += raw_pictureData.length;
+ if(remainingData != null) {
+ System.arraycopy( remainingData, 0, data, pos, remainingData.length ); pos += remainingData.length;
+ }
+
+ listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this);
+ return getRecordSize();
+ }
+
+ /**
+ * Decompresses the provided data, returning the inflated result.
+ *
+ * @param data the deflated picture data.
+ * @return the inflated picture data.
+ */
+ private static byte[] inflatePictureData(byte[] data) {
+ try {
+ InflaterInputStream in = new InflaterInputStream(
+ new ByteArrayInputStream( data ) );
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int readBytes;
+ while ((readBytes = in.read(buf)) > 0) {
+ out.write(buf, 0, readBytes);
+ }
+ return out.toByteArray();
+ } catch (IOException e) {
+ log.log(POILogger.WARN, "Possibly corrupt compression or non-compressed data", e);
+ return data;
+ }
+ }
+
+ @Override
+ public int getRecordSize() {
+ int size = 8 + 50 + raw_pictureData.length;
+ if(remainingData != null) size += remainingData.length;
+ if((getOptions() ^ getSignature()) == 0x10){
+ size += field_2_UID.length;
+ }
+ return size;
+ }
+
+ /**
+ * Gets the first MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @return the first MD4
+ */
+ public byte[] getUID() {
+ return field_1_UID;
+ }
+
+ /**
+ * Sets the first MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @param uid the first MD4
+ */
+ public void setUID(byte[] uid) {
+ if (uid == null || uid.length != 16) {
+ throw new IllegalArgumentException("uid must be byte[16]");
+ }
+ System.arraycopy(uid, 0, field_1_UID, 0, field_1_UID.length);
+ }
+
+ /**
+ * Gets the second MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @return the second MD4
+ */
+ public byte[] getPrimaryUID() {
+ return field_2_UID;
+ }
+
+ /**
+ * Sets the second MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @param primaryUID the second MD4
+ */
+ public void setPrimaryUID(byte[] primaryUID) {
+ if (primaryUID == null || primaryUID.length != 16) {
+ throw new IllegalArgumentException("primaryUID must be byte[16]");
+ }
+ System.arraycopy(primaryUID, 0, field_2_UID, 0, field_2_UID.length);
+ }
+
+ /**
+ * Gets the uncompressed size (in bytes)
+ *
+ * @return the uncompressed size
+ */
+ public int getUncompressedSize() {
+ return field_2_cb;
+ }
+
+ /**
+ * Sets the uncompressed size (in bytes)
+ *
+ * @param uncompressedSize the uncompressed size
+ */
+ public void setUncompressedSize(int uncompressedSize) {
+ field_2_cb = uncompressedSize;
+ }
+
+ /**
+ * Get the clipping region of the metafile
+ *
+ * @return the clipping region
+ */
+ public Rectangle getBounds() {
+ return new Rectangle(field_3_rcBounds_x1,
+ field_3_rcBounds_y1,
+ field_3_rcBounds_x2 - field_3_rcBounds_x1,
+ field_3_rcBounds_y2 - field_3_rcBounds_y1);
+ }
+
+ /**
+ * Sets the clipping region
+ *
+ * @param bounds the clipping region
+ */
+ public void setBounds(Rectangle bounds) {
+ field_3_rcBounds_x1 = bounds.x;
+ field_3_rcBounds_y1 = bounds.y;
+ field_3_rcBounds_x2 = bounds.x + bounds.width;
+ field_3_rcBounds_y2 = bounds.y + bounds.height;
+ }
+
+ /**
+ * Gets the dimensions of the metafile
+ *
+ * @return the dimensions of the metafile
+ */
+ public Dimension getSizeEMU() {
+ return new Dimension(field_4_ptSize_w, field_4_ptSize_h);
+ }
+
+ /**
+ * Gets the dimensions of the metafile
+ *
+ * @param sizeEMU the dimensions of the metafile
+ */
+ public void setSizeEMU(Dimension sizeEMU) {
+ field_4_ptSize_w = sizeEMU.width;
+ field_4_ptSize_h = sizeEMU.height;
+ }
+
+ /**
+ * Gets the compressed size of the metafile (in bytes)
+ *
+ * @return the compressed size
+ */
+ public int getCompressedSize() {
+ return field_5_cbSave;
+ }
+
+ /**
+ * Sets the compressed size of the metafile (in bytes)
+ *
+ * @param compressedSize the compressed size
+ */
+ public void setCompressedSize(int compressedSize) {
+ field_5_cbSave = compressedSize;
+ }
+
+ /**
+ * Gets the compression of the metafile
+ *
+ * @return true, if the metafile is compressed
+ */
+ public boolean isCompressed() {
+ return (field_6_fCompression == 0);
+ }
+
+ /**
+ * Sets the compression of the metafile
+ *
+ * @param compressed the compression state, true if it's compressed
+ */
+ public void setCompressed(boolean compressed) {
+ field_6_fCompression = compressed ? 0 : (byte)0xFE;
+ }
+
+ /**
+ * Returns any remaining bytes
+ *
+ * @return any remaining bytes
+ */
+ public byte[] getRemainingData() {
+ return remainingData;
+ }
+
+ // filtering is always 254 according to available docs, so no point giving it a setter method.
+
+ @Override
+ public String toString() {
+ String extraData = "";//HexDump.toHex(field_pictureData, 32);
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex( getRecordId() ) + '\n' +
+ " Version: 0x" + HexDump.toHex( getVersion() ) + '\n' +
+ " Instance: 0x" + HexDump.toHex( getInstance() ) + '\n' +
+ " UID: 0x" + HexDump.toHex( field_1_UID ) + '\n' +
+ (field_2_UID == null ? "" : (" UID2: 0x" + HexDump.toHex( field_2_UID ) + '\n')) +
+ " Uncompressed Size: " + HexDump.toHex( field_2_cb ) + '\n' +
+ " Bounds: " + getBounds() + '\n' +
+ " Size in EMU: " + getSizeEMU() + '\n' +
+ " Compressed Size: " + HexDump.toHex( field_5_cbSave ) + '\n' +
+ " Compression: " + HexDump.toHex( field_6_fCompression ) + '\n' +
+ " Filter: " + HexDump.toHex( field_7_fFilter ) + '\n' +
+ " Extra Data:" + '\n' + extraData +
+ (remainingData == null ? null : ("\n" +
+ " Remaining Data: " + HexDump.toHex(remainingData, 32)));
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String extraData = "";
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_1_UID ) + '\n' +
+ (field_2_UID == null ? "" : (" UID2: 0x" + HexDump.toHex( field_2_UID ) + '\n'))).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_2_cb )).append("\n")
+ .append(tab).append("\t").append("").append(getBounds()).append("\n")
+ .append(tab).append("\t").append("").append(getSizeEMU()).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_5_cbSave )).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_6_fCompression )).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_7_fFilter )).append("\n")
+ .append(tab).append("\t").append("").append(extraData).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(remainingData, 32)).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Return the blip signature
+ *
+ * @return the blip signature
+ */
+ public short getSignature() {
+ switch (getRecordId()) {
+ case RECORD_ID_EMF: return HSSFPictureData.MSOBI_EMF;
+ case RECORD_ID_WMF: return HSSFPictureData.MSOBI_WMF;
+ case RECORD_ID_PICT: return HSSFPictureData.MSOBI_PICT;
+ }
+ if (log.check(POILogger.WARN)) {
+ log.log(POILogger.WARN, "Unknown metafile: " + getRecordId());
+ }
+ return 0;
+ }
+
+ @Override
+ public void setPictureData(byte[] pictureData) {
+ super.setPictureData(pictureData);
+ setUncompressedSize(pictureData.length);
+
+ // info of chicago project:
+ // "... LZ compression algorithm in the format used by GNU Zip deflate/inflate with a 32k window ..."
+ // not sure what to do, when lookup tables exceed 32k ...
+
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DeflaterOutputStream dos = new DeflaterOutputStream(bos);
+ dos.write(pictureData);
+ dos.close();
+ raw_pictureData = bos.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException("Can't compress metafile picture data", e);
+ }
+
+ setCompressedSize(raw_pictureData.length);
+ setCompressed(true);
+ }
+
+ /**
+ * Sets the filter byte - usually this is 0xFE
+ *
+ * @param filter the filter byte
+ */
+ public void setFilter(byte filter) {
+ field_7_fFilter = filter;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherOptRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherOptRecord.java
new file mode 100644
index 000000000..cb6c7ad4c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherOptRecord.java
@@ -0,0 +1,74 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.Internal;
+
+/**
+ * The opt record is used to store property values for a shape. It is the key to
+ * determining the attributes of a shape. Properties can be of two types: simple
+ * or complex. Simple types are fixed length. Complex properties are variable
+ * length.
+ */
+public class EscherOptRecord extends AbstractEscherOptRecord
+{
+ public static final String RECORD_DESCRIPTION = "msofbtOPT";
+ public static final short RECORD_ID = (short) 0xF00B;
+
+ @Override
+ public short getInstance()
+ {
+ setInstance( (short) properties.size() );
+ return super.getInstance();
+ }
+
+ /**
+ * Automatically recalculate the correct option
+ */
+ @Override
+ @Internal
+ public short getOptions()
+ {
+ // update values
+ getInstance();
+ getVersion();
+ return super.getOptions();
+ }
+
+ @Override
+ public String getRecordName()
+ {
+ return "Opt";
+ }
+
+ @Override
+ public short getVersion()
+ {
+ setVersion( (short) 0x3 );
+ return super.getVersion();
+ }
+
+ @Override
+ public void setVersion( short value )
+ {
+ if ( value != 0x3 )
+ throw new IllegalArgumentException( RECORD_DESCRIPTION
+ + " can have only '0x3' version" );
+
+ super.setVersion( value );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherPictBlip.java b/trunk/src/java/org/apache/poi/ddf/EscherPictBlip.java
new file mode 100644
index 000000000..7c2acd9a9
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherPictBlip.java
@@ -0,0 +1,298 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.InflaterInputStream;
+
+public final class EscherPictBlip extends EscherBlipRecord {
+ private static final POILogger log = POILogFactory.getLogger(EscherPictBlip.class);
+
+ public static final short RECORD_ID_EMF = (short) 0xF018 + 2;
+ public static final short RECORD_ID_WMF = (short) 0xF018 + 3;
+ public static final short RECORD_ID_PICT = (short) 0xF018 + 4;
+
+ private static final int HEADER_SIZE = 8;
+
+ private final byte[] field_1_UID = new byte[16];
+ private int field_2_cb;
+ private int field_3_rcBounds_x1;
+ private int field_3_rcBounds_y1;
+ private int field_3_rcBounds_x2;
+ private int field_3_rcBounds_y2;
+ private int field_4_ptSize_w;
+ private int field_4_ptSize_h;
+ private int field_5_cbSave;
+ private byte field_6_fCompression;
+ private byte field_7_fFilter;
+
+ private byte[] raw_pictureData;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesAfterHeader = readHeader(data, offset);
+ int pos = offset + HEADER_SIZE;
+
+ System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16;
+ field_2_cb = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4;
+ field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4;
+ field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4;
+ field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4;
+ field_6_fCompression = data[pos]; pos++;
+ field_7_fFilter = data[pos]; pos++;
+
+ raw_pictureData = new byte[field_5_cbSave];
+ System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave );
+
+ // 0 means DEFLATE compression
+ // 0xFE means no compression
+ if (field_6_fCompression == 0)
+ {
+ field_pictureData = inflatePictureData(raw_pictureData);
+ }
+ else
+ {
+ field_pictureData = raw_pictureData;
+ }
+
+ return bytesAfterHeader + HEADER_SIZE;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize(offset, getRecordId(), this);
+
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ LittleEndian.putInt( data, 0, getRecordSize() - HEADER_SIZE ); pos += 4;
+
+ System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16;
+ LittleEndian.putInt( data, pos, field_2_cb ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4;
+ LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4;
+ data[pos] = field_6_fCompression; pos++;
+ data[pos] = field_7_fFilter; pos++;
+
+ System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length );
+
+ listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this);
+ return HEADER_SIZE + 16 + 1 + raw_pictureData.length;
+ }
+
+ /**
+ * Decompresses the provided data, returning the inflated result.
+ *
+ * @param data the deflated picture data.
+ * @return the inflated picture data.
+ */
+ private static byte[] inflatePictureData(byte[] data) {
+ try {
+ InflaterInputStream in = new InflaterInputStream(new ByteArrayInputStream(data));
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] buf = new byte[4096];
+ int readBytes;
+ while ((readBytes = in.read(buf)) > 0) {
+ out.write(buf, 0, readBytes);
+ }
+ return out.toByteArray();
+ } catch (IOException e) {
+ log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e);
+ return data;
+ }
+ }
+
+ @Override
+ public int getRecordSize() {
+ return 8 + 50 + raw_pictureData.length;
+ }
+
+ /**
+ * Gets the first MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @return the first MD4
+ */
+ public byte[] getUID() {
+ return field_1_UID;
+ }
+
+ /**
+ * Sets the first MD4, that specifies the unique identifier of the
+ * uncompressed blip data
+ *
+ * @param uid the first MD4
+ */
+ public void setUID(byte[] uid) {
+ if (uid == null || uid.length != 16) {
+ throw new IllegalArgumentException("uid must be byte[16]");
+ }
+ System.arraycopy(uid, 0, field_1_UID, 0, field_1_UID.length);
+ }
+
+ /**
+ * Gets the uncompressed size (in bytes)
+ *
+ * @return the uncompressed size
+ */
+ public int getUncompressedSize() {
+ return field_2_cb;
+ }
+
+ /**
+ * Sets the uncompressed size (in bytes)
+ *
+ * @param uncompressedSize the uncompressed size
+ */
+ public void setUncompressedSize(int uncompressedSize) {
+ field_2_cb = uncompressedSize;
+ }
+
+ /**
+ * Get the clipping region of the pict file
+ *
+ * @return the clipping region
+ */
+ public Rectangle getBounds() {
+ return new Rectangle(field_3_rcBounds_x1,
+ field_3_rcBounds_y1,
+ field_3_rcBounds_x2 - field_3_rcBounds_x1,
+ field_3_rcBounds_y2 - field_3_rcBounds_y1);
+ }
+
+ /**
+ * Sets the clipping region
+ *
+ * @param bounds the clipping region
+ */
+ public void setBounds(Rectangle bounds) {
+ field_3_rcBounds_x1 = bounds.x;
+ field_3_rcBounds_y1 = bounds.y;
+ field_3_rcBounds_x2 = bounds.x + bounds.width;
+ field_3_rcBounds_y2 = bounds.y + bounds.height;
+ }
+
+ /**
+ * Gets the dimensions of the metafile
+ *
+ * @return the dimensions of the metafile
+ */
+ public Dimension getSizeEMU() {
+ return new Dimension(field_4_ptSize_w, field_4_ptSize_h);
+ }
+
+ /**
+ * Gets the dimensions of the metafile
+ *
+ * @param sizeEMU the dimensions of the metafile
+ */
+ public void setSizeEMU(Dimension sizeEMU) {
+ field_4_ptSize_w = sizeEMU.width;
+ field_4_ptSize_h = sizeEMU.height;
+ }
+
+ /**
+ * Gets the compressed size of the metafile (in bytes)
+ *
+ * @return the compressed size
+ */
+ public int getCompressedSize() {
+ return field_5_cbSave;
+ }
+
+ /**
+ * Sets the compressed size of the metafile (in bytes)
+ *
+ * @param compressedSize the compressed size
+ */
+ public void setCompressedSize(int compressedSize) {
+ field_5_cbSave = compressedSize;
+ }
+
+ /**
+ * Gets the compression of the metafile
+ *
+ * @return true, if the metafile is compressed
+ */
+ public boolean isCompressed() {
+ return (field_6_fCompression == 0);
+ }
+
+ /**
+ * Sets the compression of the metafile
+ *
+ * @param compressed the compression state, true if it's compressed
+ */
+ public void setCompressed(boolean compressed) {
+ field_6_fCompression = compressed ? 0 : (byte)0xFE;
+ }
+
+ // filtering is always 254 according to available docs, so no point giving it a setter method.
+
+ @Override
+ public String toString() {
+ String extraData = HexDump.toHex(field_pictureData, 32);
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex( getRecordId() ) + '\n' +
+ " Version: 0x" + HexDump.toHex( getVersion() ) + '\n' +
+ " Instance: 0x" + HexDump.toHex( getInstance() ) + '\n' +
+ " UID: 0x" + HexDump.toHex( field_1_UID ) + '\n' +
+ " Uncompressed Size: " + HexDump.toHex( field_2_cb ) + '\n' +
+ " Bounds: " + getBounds() + '\n' +
+ " Size in EMU: " + getSizeEMU() + '\n' +
+ " Compressed Size: " + HexDump.toHex( field_5_cbSave ) + '\n' +
+ " Compression: " + HexDump.toHex( field_6_fCompression ) + '\n' +
+ " Filter: " + HexDump.toHex( field_7_fFilter ) + '\n' +
+ " Extra Data:" + '\n' + extraData;
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String extraData = "";
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_1_UID )).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_2_cb )).append("\n")
+ .append(tab).append("\t").append("").append(getBounds()).append("\n")
+ .append(tab).append("\t").append("").append(getSizeEMU()).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_5_cbSave )).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_6_fCompression )).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex( field_7_fFilter )).append("\n")
+ .append(tab).append("\t").append("").append(extraData).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherProperties.java b/trunk/src/java/org/apache/poi/ddf/EscherProperties.java
new file mode 100644
index 000000000..096a20c57
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherProperties.java
@@ -0,0 +1,660 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides a list of all known escher properties including the description and
+ * type.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public final class EscherProperties {
+
+ // Property constants
+ public static final short TRANSFORM__ROTATION = 4;
+ public static final short PROTECTION__LOCKROTATION = 119;
+ public static final short PROTECTION__LOCKASPECTRATIO = 120;
+ public static final short PROTECTION__LOCKPOSITION = 121;
+ public static final short PROTECTION__LOCKAGAINSTSELECT = 122;
+ public static final short PROTECTION__LOCKCROPPING = 123;
+ public static final short PROTECTION__LOCKVERTICES = 124;
+ public static final short PROTECTION__LOCKTEXT = 125;
+ public static final short PROTECTION__LOCKADJUSTHANDLES = 126;
+ public static final short PROTECTION__LOCKAGAINSTGROUPING = 127;
+ public static final short TEXT__TEXTID = 128;
+ public static final short TEXT__TEXTLEFT = 129;
+ public static final short TEXT__TEXTTOP = 130;
+ public static final short TEXT__TEXTRIGHT = 131;
+ public static final short TEXT__TEXTBOTTOM = 132;
+ public static final short TEXT__WRAPTEXT = 133;
+ public static final short TEXT__SCALETEXT = 134;
+ public static final short TEXT__ANCHORTEXT = 135;
+ public static final short TEXT__TEXTFLOW = 136;
+ public static final short TEXT__FONTROTATION = 137;
+ public static final short TEXT__IDOFNEXTSHAPE = 138;
+ public static final short TEXT__BIDIR = 139;
+ public static final short TEXT__SINGLECLICKSELECTS = 187;
+ public static final short TEXT__USEHOSTMARGINS = 188;
+ public static final short TEXT__ROTATETEXTWITHSHAPE = 189;
+ public static final short TEXT__SIZESHAPETOFITTEXT = 190;
+ public static final short TEXT__SIZE_TEXT_TO_FIT_SHAPE = 191;
+ public static final short GEOTEXT__UNICODE = 192;
+ public static final short GEOTEXT__RTFTEXT = 193;
+ public static final short GEOTEXT__ALIGNMENTONCURVE = 194;
+ public static final short GEOTEXT__DEFAULTPOINTSIZE = 195;
+ public static final short GEOTEXT__TEXTSPACING = 196;
+ public static final short GEOTEXT__FONTFAMILYNAME = 197;
+ public static final short GEOTEXT__REVERSEROWORDER = 240;
+ public static final short GEOTEXT__HASTEXTEFFECT = 241;
+ public static final short GEOTEXT__ROTATECHARACTERS = 242;
+ public static final short GEOTEXT__KERNCHARACTERS = 243;
+ public static final short GEOTEXT__TIGHTORTRACK = 244;
+ public static final short GEOTEXT__STRETCHTOFITSHAPE = 245;
+ public static final short GEOTEXT__CHARBOUNDINGBOX = 246;
+ public static final short GEOTEXT__SCALETEXTONPATH = 247;
+ public static final short GEOTEXT__STRETCHCHARHEIGHT = 248;
+ public static final short GEOTEXT__NOMEASUREALONGPATH = 249;
+ public static final short GEOTEXT__BOLDFONT = 250;
+ public static final short GEOTEXT__ITALICFONT = 251;
+ public static final short GEOTEXT__UNDERLINEFONT = 252;
+ public static final short GEOTEXT__SHADOWFONT = 253;
+ public static final short GEOTEXT__SMALLCAPSFONT = 254;
+ public static final short GEOTEXT__STRIKETHROUGHFONT = 255;
+ public static final short BLIP__CROPFROMTOP = 256;
+ public static final short BLIP__CROPFROMBOTTOM = 257;
+ public static final short BLIP__CROPFROMLEFT = 258;
+ public static final short BLIP__CROPFROMRIGHT = 259;
+ public static final short BLIP__BLIPTODISPLAY = 260;
+ public static final short BLIP__BLIPFILENAME = 261;
+ public static final short BLIP__BLIPFLAGS = 262;
+ public static final short BLIP__TRANSPARENTCOLOR = 263;
+ public static final short BLIP__CONTRASTSETTING = 264;
+ public static final short BLIP__BRIGHTNESSSETTING = 265;
+ public static final short BLIP__GAMMA = 266;
+ public static final short BLIP__PICTUREID = 267;
+ public static final short BLIP__DOUBLEMOD = 268;
+ public static final short BLIP__PICTUREFILLMOD = 269;
+ public static final short BLIP__PICTURELINE = 270;
+ public static final short BLIP__PRINTBLIP = 271;
+ public static final short BLIP__PRINTBLIPFILENAME = 272;
+ public static final short BLIP__PRINTFLAGS = 273;
+ public static final short BLIP__NOHITTESTPICTURE = 316;
+ public static final short BLIP__PICTUREGRAY = 317;
+ public static final short BLIP__PICTUREBILEVEL = 318;
+ public static final short BLIP__PICTUREACTIVE = 319;
+ public static final short GEOMETRY__LEFT = 320;
+ public static final short GEOMETRY__TOP = 321;
+ public static final short GEOMETRY__RIGHT = 322;
+ public static final short GEOMETRY__BOTTOM = 323;
+ public static final short GEOMETRY__SHAPEPATH = 324;
+ public static final short GEOMETRY__VERTICES = 325;
+ public static final short GEOMETRY__SEGMENTINFO = 326;
+ public static final short GEOMETRY__ADJUSTVALUE = 327;
+ public static final short GEOMETRY__ADJUST2VALUE = 328;
+ public static final short GEOMETRY__ADJUST3VALUE = 329;
+ public static final short GEOMETRY__ADJUST4VALUE = 330;
+ public static final short GEOMETRY__ADJUST5VALUE = 331;
+ public static final short GEOMETRY__ADJUST6VALUE = 332;
+ public static final short GEOMETRY__ADJUST7VALUE = 333;
+ public static final short GEOMETRY__ADJUST8VALUE = 334;
+ public static final short GEOMETRY__ADJUST9VALUE = 335;
+ public static final short GEOMETRY__ADJUST10VALUE = 336;
+ public static final short GEOMETRY__SHADOWok = 378;
+ public static final short GEOMETRY__3DOK = 379;
+ public static final short GEOMETRY__LINEOK = 380;
+ public static final short GEOMETRY__GEOTEXTOK = 381;
+ public static final short GEOMETRY__FILLSHADESHAPEOK = 382;
+ public static final short GEOMETRY__FILLOK = 383;
+ public static final short FILL__FILLTYPE = 384;
+ public static final short FILL__FILLCOLOR = 385;
+ public static final short FILL__FILLOPACITY = 386;
+ public static final short FILL__FILLBACKCOLOR = 387;
+ public static final short FILL__BACKOPACITY = 388;
+ public static final short FILL__CRMOD = 389;
+ public static final short FILL__PATTERNTEXTURE = 390;
+ public static final short FILL__BLIPFILENAME = 391;
+ public static final short FILL__BLIPFLAGS = 392;
+ public static final short FILL__WIDTH = 393;
+ public static final short FILL__HEIGHT = 394;
+ public static final short FILL__ANGLE = 395;
+ public static final short FILL__FOCUS = 396;
+ public static final short FILL__TOLEFT = 397;
+ public static final short FILL__TOTOP = 398;
+ public static final short FILL__TORIGHT = 399;
+ public static final short FILL__TOBOTTOM = 400;
+ public static final short FILL__RECTLEFT = 401;
+ public static final short FILL__RECTTOP = 402;
+ public static final short FILL__RECTRIGHT = 403;
+ public static final short FILL__RECTBOTTOM = 404;
+ public static final short FILL__DZTYPE = 405;
+ public static final short FILL__SHADEPRESET = 406;
+ public static final short FILL__SHADECOLORS = 407;
+ public static final short FILL__ORIGINX = 408;
+ public static final short FILL__ORIGINY = 409;
+ public static final short FILL__SHAPEORIGINX = 410;
+ public static final short FILL__SHAPEORIGINY = 411;
+ public static final short FILL__SHADETYPE = 412;
+ public static final short FILL__FILLED = 443;
+ public static final short FILL__HITTESTFILL = 444;
+ public static final short FILL__SHAPE = 445;
+ public static final short FILL__USERECT = 446;
+ public static final short FILL__NOFILLHITTEST = 447;
+ public static final short LINESTYLE__COLOR = 448;
+ public static final short LINESTYLE__OPACITY = 449;
+ public static final short LINESTYLE__BACKCOLOR = 450;
+ public static final short LINESTYLE__CRMOD = 451;
+ public static final short LINESTYLE__LINETYPE = 452;
+ public static final short LINESTYLE__FILLBLIP = 453;
+ public static final short LINESTYLE__FILLBLIPNAME = 454;
+ public static final short LINESTYLE__FILLBLIPFLAGS = 455;
+ public static final short LINESTYLE__FILLWIDTH = 456;
+ public static final short LINESTYLE__FILLHEIGHT = 457;
+ public static final short LINESTYLE__FILLDZTYPE = 458;
+ public static final short LINESTYLE__LINEWIDTH = 459;
+ public static final short LINESTYLE__LINEMITERLIMIT = 460;
+ public static final short LINESTYLE__LINESTYLE = 461;
+ public static final short LINESTYLE__LINEDASHING = 462;
+ public static final short LINESTYLE__LINEDASHSTYLE = 463;
+ public static final short LINESTYLE__LINESTARTARROWHEAD = 464;
+ public static final short LINESTYLE__LINEENDARROWHEAD = 465;
+ public static final short LINESTYLE__LINESTARTARROWWIDTH = 466;
+ public static final short LINESTYLE__LINESTARTARROWLENGTH = 467;
+ public static final short LINESTYLE__LINEENDARROWWIDTH = 468;
+ public static final short LINESTYLE__LINEENDARROWLENGTH = 469;
+ public static final short LINESTYLE__LINEJOINSTYLE = 470;
+ public static final short LINESTYLE__LINEENDCAPSTYLE = 471;
+ public static final short LINESTYLE__ARROWHEADSOK = 507;
+ public static final short LINESTYLE__ANYLINE = 508;
+ public static final short LINESTYLE__HITLINETEST = 509;
+ public static final short LINESTYLE__LINEFILLSHAPE = 510;
+ public static final short LINESTYLE__NOLINEDRAWDASH = 511;
+ public static final short LINESTYLE__NOLINEDRAWDASH_LEFT = 0x057F;
+ public static final short LINESTYLE__NOLINEDRAWDASH_TOP = 0x05BF;
+ public static final short LINESTYLE__NOLINEDRAWDASH_BOTTOM = 0x063F;
+ public static final short LINESTYLE__NOLINEDRAWDASH_RIGHT = 0x05FF;
+ public static final short SHADOWSTYLE__TYPE = 512;
+ public static final short SHADOWSTYLE__COLOR = 513;
+ public static final short SHADOWSTYLE__HIGHLIGHT = 514;
+ public static final short SHADOWSTYLE__CRMOD = 515;
+ public static final short SHADOWSTYLE__OPACITY = 516;
+ public static final short SHADOWSTYLE__OFFSETX = 517;
+ public static final short SHADOWSTYLE__OFFSETY = 518;
+ public static final short SHADOWSTYLE__SECONDOFFSETX = 519;
+ public static final short SHADOWSTYLE__SECONDOFFSETY = 520;
+ public static final short SHADOWSTYLE__SCALEXTOX = 521;
+ public static final short SHADOWSTYLE__SCALEYTOX = 522;
+ public static final short SHADOWSTYLE__SCALEXTOY = 523;
+ public static final short SHADOWSTYLE__SCALEYTOY = 524;
+ public static final short SHADOWSTYLE__PERSPECTIVEX = 525;
+ public static final short SHADOWSTYLE__PERSPECTIVEY = 526;
+ public static final short SHADOWSTYLE__WEIGHT = 527;
+ public static final short SHADOWSTYLE__ORIGINX = 528;
+ public static final short SHADOWSTYLE__ORIGINY = 529;
+ public static final short SHADOWSTYLE__SHADOW = 574;
+ public static final short SHADOWSTYLE__SHADOWOBSURED = 575;
+ public static final short PERSPECTIVE__TYPE = 576;
+ public static final short PERSPECTIVE__OFFSETX = 577;
+ public static final short PERSPECTIVE__OFFSETY = 578;
+ public static final short PERSPECTIVE__SCALEXTOX = 579;
+ public static final short PERSPECTIVE__SCALEYTOX = 580;
+ public static final short PERSPECTIVE__SCALEXTOY = 581;
+ public static final short PERSPECTIVE__SCALEYTOY = 582;
+ public static final short PERSPECTIVE__PERSPECTIVEX = 583;
+ public static final short PERSPECTIVE__PERSPECTIVEY = 584;
+ public static final short PERSPECTIVE__WEIGHT = 585;
+ public static final short PERSPECTIVE__ORIGINX = 586;
+ public static final short PERSPECTIVE__ORIGINY = 587;
+ public static final short PERSPECTIVE__PERSPECTIVEON = 639;
+ public static final short THREED__SPECULARAMOUNT = 640;
+ public static final short THREED__DIFFUSEAMOUNT = 661;
+ public static final short THREED__SHININESS = 662;
+ public static final short THREED__EDGETHICKNESS = 663;
+ public static final short THREED__EXTRUDEFORWARD = 664;
+ public static final short THREED__EXTRUDEBACKWARD = 665;
+ public static final short THREED__EXTRUDEPLANE = 666;
+ public static final short THREED__EXTRUSIONCOLOR = 667;
+ public static final short THREED__CRMOD = 648;
+ public static final short THREED__3DEFFECT = 700;
+ public static final short THREED__METALLIC = 701;
+ public static final short THREED__USEEXTRUSIONCOLOR = 702;
+ public static final short THREED__LIGHTFACE = 703;
+ public static final short THREEDSTYLE__YROTATIONANGLE = 704;
+ public static final short THREEDSTYLE__XROTATIONANGLE = 705;
+ public static final short THREEDSTYLE__ROTATIONAXISX = 706;
+ public static final short THREEDSTYLE__ROTATIONAXISY = 707;
+ public static final short THREEDSTYLE__ROTATIONAXISZ = 708;
+ public static final short THREEDSTYLE__ROTATIONANGLE = 709;
+ public static final short THREEDSTYLE__ROTATIONCENTERX = 710;
+ public static final short THREEDSTYLE__ROTATIONCENTERY = 711;
+ public static final short THREEDSTYLE__ROTATIONCENTERZ = 712;
+ public static final short THREEDSTYLE__RENDERMODE = 713;
+ public static final short THREEDSTYLE__TOLERANCE = 714;
+ public static final short THREEDSTYLE__XVIEWPOINT = 715;
+ public static final short THREEDSTYLE__YVIEWPOINT = 716;
+ public static final short THREEDSTYLE__ZVIEWPOINT = 717;
+ public static final short THREEDSTYLE__ORIGINX = 718;
+ public static final short THREEDSTYLE__ORIGINY = 719;
+ public static final short THREEDSTYLE__SKEWANGLE = 720;
+ public static final short THREEDSTYLE__SKEWAMOUNT = 721;
+ public static final short THREEDSTYLE__AMBIENTINTENSITY = 722;
+ public static final short THREEDSTYLE__KEYX = 723;
+ public static final short THREEDSTYLE__KEYY = 724;
+ public static final short THREEDSTYLE__KEYZ = 725;
+ public static final short THREEDSTYLE__KEYINTENSITY = 726;
+ public static final short THREEDSTYLE__FILLX = 727;
+ public static final short THREEDSTYLE__FILLY = 728;
+ public static final short THREEDSTYLE__FILLZ = 729;
+ public static final short THREEDSTYLE__FILLINTENSITY = 730;
+ public static final short THREEDSTYLE__CONSTRAINROTATION = 763;
+ public static final short THREEDSTYLE__ROTATIONCENTERAUTO = 764;
+ public static final short THREEDSTYLE__PARALLEL = 765;
+ public static final short THREEDSTYLE__KEYHARSH = 766;
+ public static final short THREEDSTYLE__FILLHARSH = 767;
+ public static final short SHAPE__MASTER = 769;
+ public static final short SHAPE__CONNECTORSTYLE = 771;
+ public static final short SHAPE__BLACKANDWHITESETTINGS = 772;
+ public static final short SHAPE__WMODEPUREBW = 773;
+ public static final short SHAPE__WMODEBW = 774;
+ public static final short SHAPE__OLEICON = 826;
+ public static final short SHAPE__PREFERRELATIVERESIZE = 827;
+ public static final short SHAPE__LOCKSHAPETYPE = 828;
+ public static final short SHAPE__DELETEATTACHEDOBJECT = 830;
+ public static final short SHAPE__BACKGROUNDSHAPE = 831;
+ public static final short CALLOUT__CALLOUTTYPE = 832;
+ public static final short CALLOUT__XYCALLOUTGAP = 833;
+ public static final short CALLOUT__CALLOUTANGLE = 834;
+ public static final short CALLOUT__CALLOUTDROPTYPE = 835;
+ public static final short CALLOUT__CALLOUTDROPSPECIFIED = 836;
+ public static final short CALLOUT__CALLOUTLENGTHSPECIFIED = 837;
+ public static final short CALLOUT__ISCALLOUT = 889;
+ public static final short CALLOUT__CALLOUTACCENTBAR = 890;
+ public static final short CALLOUT__CALLOUTTEXTBORDER = 891;
+ public static final short CALLOUT__CALLOUTMINUSX = 892;
+ public static final short CALLOUT__CALLOUTMINUSY = 893;
+ public static final short CALLOUT__DROPAUTO = 894;
+ public static final short CALLOUT__LENGTHSPECIFIED = 895;
+ public static final short GROUPSHAPE__SHAPENAME = 0x0380;
+ public static final short GROUPSHAPE__DESCRIPTION = 0x0381;
+ public static final short GROUPSHAPE__HYPERLINK = 0x0382;
+ public static final short GROUPSHAPE__WRAPPOLYGONVERTICES = 0x0383;
+ public static final short GROUPSHAPE__WRAPDISTLEFT = 0x0384;
+ public static final short GROUPSHAPE__WRAPDISTTOP = 0x0385;
+ public static final short GROUPSHAPE__WRAPDISTRIGHT = 0x0386;
+ public static final short GROUPSHAPE__WRAPDISTBOTTOM = 0x0387;
+ public static final short GROUPSHAPE__REGROUPID = 0x0388;
+ public static final short GROUPSHAPE__UNUSED906 = 0x038A;
+ public static final short GROUPSHAPE__TOOLTIP = 0x038D;
+ public static final short GROUPSHAPE__SCRIPT = 0x038E;
+ public static final short GROUPSHAPE__POSH = 0x038F;
+ public static final short GROUPSHAPE__POSRELH = 0x0390;
+ public static final short GROUPSHAPE__POSV = 0x0391;
+ public static final short GROUPSHAPE__POSRELV = 0x0392;
+ public static final short GROUPSHAPE__HR_PCT = 0x0393;
+ public static final short GROUPSHAPE__HR_ALIGN = 0x0394;
+ public static final short GROUPSHAPE__HR_HEIGHT = 0x0395;
+ public static final short GROUPSHAPE__HR_WIDTH = 0x0396;
+ public static final short GROUPSHAPE__SCRIPTEXT = 0x0397;
+ public static final short GROUPSHAPE__SCRIPTLANG = 0x0398;
+ public static final short GROUPSHAPE__BORDERTOPCOLOR = 0x039B;
+ public static final short GROUPSHAPE__BORDERLEFTCOLOR = 0x039C;
+ public static final short GROUPSHAPE__BORDERBOTTOMCOLOR = 0x039D;
+ public static final short GROUPSHAPE__BORDERRIGHTCOLOR = 0x039E;
+ public static final short GROUPSHAPE__TABLEPROPERTIES = 0x039F;
+ public static final short GROUPSHAPE__TABLEROWPROPERTIES = 0x03A0;
+ public static final short GROUPSHAPE__WEBBOT = 0x03A5;
+ public static final short GROUPSHAPE__METROBLOB = 0x03A9;
+ public static final short GROUPSHAPE__ZORDER = 0x03AA;
+ public static final short GROUPSHAPE__FLAGS = 0x03BF;
+ public static final short GROUPSHAPE__EDITEDWRAP = 953;
+ public static final short GROUPSHAPE__BEHINDDOCUMENT = 954;
+ public static final short GROUPSHAPE__ONDBLCLICKNOTIFY = 955;
+ public static final short GROUPSHAPE__ISBUTTON = 956;
+ public static final short GROUPSHAPE__1DADJUSTMENT = 957;
+ public static final short GROUPSHAPE__HIDDEN = 958;
+ public static final short GROUPSHAPE__PRINT = 959;
+
+ private static final Map properties = initProps();
+
+ private static Map initProps() {
+ Map m = new HashMap();
+ addProp(m, TRANSFORM__ROTATION, "transform.rotation");
+ addProp(m, PROTECTION__LOCKROTATION, "protection.lockrotation");
+ addProp(m, PROTECTION__LOCKASPECTRATIO, "protection.lockaspectratio");
+ addProp(m, PROTECTION__LOCKPOSITION, "protection.lockposition");
+ addProp(m, PROTECTION__LOCKAGAINSTSELECT, "protection.lockagainstselect");
+ addProp(m, PROTECTION__LOCKCROPPING, "protection.lockcropping");
+ addProp(m, PROTECTION__LOCKVERTICES, "protection.lockvertices");
+ addProp(m, PROTECTION__LOCKTEXT, "protection.locktext");
+ addProp(m, PROTECTION__LOCKADJUSTHANDLES, "protection.lockadjusthandles");
+ addProp(m, PROTECTION__LOCKAGAINSTGROUPING, "protection.lockagainstgrouping", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, TEXT__TEXTID, "text.textid");
+ addProp(m, TEXT__TEXTLEFT, "text.textleft");
+ addProp(m, TEXT__TEXTTOP, "text.texttop");
+ addProp(m, TEXT__TEXTRIGHT, "text.textright");
+ addProp(m, TEXT__TEXTBOTTOM, "text.textbottom");
+ addProp(m, TEXT__WRAPTEXT, "text.wraptext");
+ addProp(m, TEXT__SCALETEXT, "text.scaletext");
+ addProp(m, TEXT__ANCHORTEXT, "text.anchortext");
+ addProp(m, TEXT__TEXTFLOW, "text.textflow");
+ addProp(m, TEXT__FONTROTATION, "text.fontrotation");
+ addProp(m, TEXT__IDOFNEXTSHAPE, "text.idofnextshape");
+ addProp(m, TEXT__BIDIR, "text.bidir");
+ addProp(m, TEXT__SINGLECLICKSELECTS, "text.singleclickselects");
+ addProp(m, TEXT__USEHOSTMARGINS, "text.usehostmargins");
+ addProp(m, TEXT__ROTATETEXTWITHSHAPE, "text.rotatetextwithshape");
+ addProp(m, TEXT__SIZESHAPETOFITTEXT, "text.sizeshapetofittext");
+ addProp(m, TEXT__SIZE_TEXT_TO_FIT_SHAPE, "text.sizetexttofitshape", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, GEOTEXT__UNICODE, "geotext.unicode");
+ addProp(m, GEOTEXT__RTFTEXT, "geotext.rtftext");
+ addProp(m, GEOTEXT__ALIGNMENTONCURVE, "geotext.alignmentoncurve");
+ addProp(m, GEOTEXT__DEFAULTPOINTSIZE, "geotext.defaultpointsize");
+ addProp(m, GEOTEXT__TEXTSPACING, "geotext.textspacing");
+ addProp(m, GEOTEXT__FONTFAMILYNAME, "geotext.fontfamilyname");
+ addProp(m, GEOTEXT__REVERSEROWORDER, "geotext.reverseroworder");
+ addProp(m, GEOTEXT__HASTEXTEFFECT, "geotext.hastexteffect");
+ addProp(m, GEOTEXT__ROTATECHARACTERS, "geotext.rotatecharacters");
+ addProp(m, GEOTEXT__KERNCHARACTERS, "geotext.kerncharacters");
+ addProp(m, GEOTEXT__TIGHTORTRACK, "geotext.tightortrack");
+ addProp(m, GEOTEXT__STRETCHTOFITSHAPE, "geotext.stretchtofitshape");
+ addProp(m, GEOTEXT__CHARBOUNDINGBOX, "geotext.charboundingbox");
+ addProp(m, GEOTEXT__SCALETEXTONPATH, "geotext.scaletextonpath");
+ addProp(m, GEOTEXT__STRETCHCHARHEIGHT, "geotext.stretchcharheight");
+ addProp(m, GEOTEXT__NOMEASUREALONGPATH, "geotext.nomeasurealongpath");
+ addProp(m, GEOTEXT__BOLDFONT, "geotext.boldfont");
+ addProp(m, GEOTEXT__ITALICFONT, "geotext.italicfont");
+ addProp(m, GEOTEXT__UNDERLINEFONT, "geotext.underlinefont");
+ addProp(m, GEOTEXT__SHADOWFONT, "geotext.shadowfont");
+ addProp(m, GEOTEXT__SMALLCAPSFONT, "geotext.smallcapsfont");
+ addProp(m, GEOTEXT__STRIKETHROUGHFONT, "geotext.strikethroughfont");
+ addProp(m, BLIP__CROPFROMTOP, "blip.cropfromtop");
+ addProp(m, BLIP__CROPFROMBOTTOM, "blip.cropfrombottom");
+ addProp(m, BLIP__CROPFROMLEFT, "blip.cropfromleft");
+ addProp(m, BLIP__CROPFROMRIGHT, "blip.cropfromright");
+ addProp(m, BLIP__BLIPTODISPLAY, "blip.bliptodisplay");
+ addProp(m, BLIP__BLIPFILENAME, "blip.blipfilename");
+ addProp(m, BLIP__BLIPFLAGS, "blip.blipflags");
+ addProp(m, BLIP__TRANSPARENTCOLOR, "blip.transparentcolor");
+ addProp(m, BLIP__CONTRASTSETTING, "blip.contrastsetting");
+ addProp(m, BLIP__BRIGHTNESSSETTING, "blip.brightnesssetting");
+ addProp(m, BLIP__GAMMA, "blip.gamma");
+ addProp(m, BLIP__PICTUREID, "blip.pictureid");
+ addProp(m, BLIP__DOUBLEMOD, "blip.doublemod");
+ addProp(m, BLIP__PICTUREFILLMOD, "blip.picturefillmod");
+ addProp(m, BLIP__PICTURELINE, "blip.pictureline");
+ addProp(m, BLIP__PRINTBLIP, "blip.printblip");
+ addProp(m, BLIP__PRINTBLIPFILENAME, "blip.printblipfilename");
+ addProp(m, BLIP__PRINTFLAGS, "blip.printflags");
+ addProp(m, BLIP__NOHITTESTPICTURE, "blip.nohittestpicture");
+ addProp(m, BLIP__PICTUREGRAY, "blip.picturegray");
+ addProp(m, BLIP__PICTUREBILEVEL, "blip.picturebilevel");
+ addProp(m, BLIP__PICTUREACTIVE, "blip.pictureactive");
+ addProp(m, GEOMETRY__LEFT, "geometry.left");
+ addProp(m, GEOMETRY__TOP, "geometry.top");
+ addProp(m, GEOMETRY__RIGHT, "geometry.right");
+ addProp(m, GEOMETRY__BOTTOM, "geometry.bottom");
+ addProp(m, GEOMETRY__SHAPEPATH, "geometry.shapepath", EscherPropertyMetaData.TYPE_SHAPEPATH);
+ addProp(m, GEOMETRY__VERTICES, "geometry.vertices", EscherPropertyMetaData.TYPE_ARRAY);
+ addProp(m, GEOMETRY__SEGMENTINFO, "geometry.segmentinfo", EscherPropertyMetaData.TYPE_ARRAY);
+ addProp(m, GEOMETRY__ADJUSTVALUE, "geometry.adjustvalue");
+ addProp(m, GEOMETRY__ADJUST2VALUE, "geometry.adjust2value");
+ addProp(m, GEOMETRY__ADJUST3VALUE, "geometry.adjust3value");
+ addProp(m, GEOMETRY__ADJUST4VALUE, "geometry.adjust4value");
+ addProp(m, GEOMETRY__ADJUST5VALUE, "geometry.adjust5value");
+ addProp(m, GEOMETRY__ADJUST6VALUE, "geometry.adjust6value");
+ addProp(m, GEOMETRY__ADJUST7VALUE, "geometry.adjust7value");
+ addProp(m, GEOMETRY__ADJUST8VALUE, "geometry.adjust8value");
+ addProp(m, GEOMETRY__ADJUST9VALUE, "geometry.adjust9value");
+ addProp(m, GEOMETRY__ADJUST10VALUE, "geometry.adjust10value");
+ addProp(m, GEOMETRY__SHADOWok, "geometry.shadowOK");
+ addProp(m, GEOMETRY__3DOK, "geometry.3dok");
+ addProp(m, GEOMETRY__LINEOK, "geometry.lineok");
+ addProp(m, GEOMETRY__GEOTEXTOK, "geometry.geotextok");
+ addProp(m, GEOMETRY__FILLSHADESHAPEOK, "geometry.fillshadeshapeok");
+ addProp(m, GEOMETRY__FILLOK, "geometry.fillok", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, FILL__FILLTYPE, "fill.filltype");
+ addProp(m, FILL__FILLCOLOR, "fill.fillcolor", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, FILL__FILLOPACITY, "fill.fillopacity");
+ addProp(m, FILL__FILLBACKCOLOR, "fill.fillbackcolor", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, FILL__BACKOPACITY, "fill.backopacity");
+ addProp(m, FILL__CRMOD, "fill.crmod");
+ addProp(m, FILL__PATTERNTEXTURE, "fill.patterntexture");
+ addProp(m, FILL__BLIPFILENAME, "fill.blipfilename");
+ addProp(m, FILL__BLIPFLAGS, "fill.blipflags");
+ addProp(m, FILL__WIDTH, "fill.width");
+ addProp(m, FILL__HEIGHT, "fill.height");
+ addProp(m, FILL__ANGLE, "fill.angle");
+ addProp(m, FILL__FOCUS, "fill.focus");
+ addProp(m, FILL__TOLEFT, "fill.toleft");
+ addProp(m, FILL__TOTOP, "fill.totop");
+ addProp(m, FILL__TORIGHT, "fill.toright");
+ addProp(m, FILL__TOBOTTOM, "fill.tobottom");
+ addProp(m, FILL__RECTLEFT, "fill.rectleft");
+ addProp(m, FILL__RECTTOP, "fill.recttop");
+ addProp(m, FILL__RECTRIGHT, "fill.rectright");
+ addProp(m, FILL__RECTBOTTOM, "fill.rectbottom");
+ addProp(m, FILL__DZTYPE, "fill.dztype");
+ addProp(m, FILL__SHADEPRESET, "fill.shadepreset");
+ addProp(m, FILL__SHADECOLORS, "fill.shadecolors", EscherPropertyMetaData.TYPE_ARRAY);
+ addProp(m, FILL__ORIGINX, "fill.originx");
+ addProp(m, FILL__ORIGINY, "fill.originy");
+ addProp(m, FILL__SHAPEORIGINX, "fill.shapeoriginx");
+ addProp(m, FILL__SHAPEORIGINY, "fill.shapeoriginy");
+ addProp(m, FILL__SHADETYPE, "fill.shadetype");
+ addProp(m, FILL__FILLED, "fill.filled");
+ addProp(m, FILL__HITTESTFILL, "fill.hittestfill");
+ addProp(m, FILL__SHAPE, "fill.shape");
+ addProp(m, FILL__USERECT, "fill.userect");
+ addProp(m, FILL__NOFILLHITTEST, "fill.nofillhittest", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, LINESTYLE__COLOR, "linestyle.color", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, LINESTYLE__OPACITY, "linestyle.opacity");
+ addProp(m, LINESTYLE__BACKCOLOR, "linestyle.backcolor", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, LINESTYLE__CRMOD, "linestyle.crmod");
+ addProp(m, LINESTYLE__LINETYPE, "linestyle.linetype");
+ addProp(m, LINESTYLE__FILLBLIP, "linestyle.fillblip");
+ addProp(m, LINESTYLE__FILLBLIPNAME, "linestyle.fillblipname");
+ addProp(m, LINESTYLE__FILLBLIPFLAGS, "linestyle.fillblipflags");
+ addProp(m, LINESTYLE__FILLWIDTH, "linestyle.fillwidth");
+ addProp(m, LINESTYLE__FILLHEIGHT, "linestyle.fillheight");
+ addProp(m, LINESTYLE__FILLDZTYPE, "linestyle.filldztype");
+ addProp(m, LINESTYLE__LINEWIDTH, "linestyle.linewidth");
+ addProp(m, LINESTYLE__LINEMITERLIMIT, "linestyle.linemiterlimit");
+ addProp(m, LINESTYLE__LINESTYLE, "linestyle.linestyle");
+ addProp(m, LINESTYLE__LINEDASHING, "linestyle.linedashing");
+ addProp(m, LINESTYLE__LINEDASHSTYLE, "linestyle.linedashstyle", EscherPropertyMetaData.TYPE_ARRAY);
+ addProp(m, LINESTYLE__LINESTARTARROWHEAD, "linestyle.linestartarrowhead");
+ addProp(m, LINESTYLE__LINEENDARROWHEAD, "linestyle.lineendarrowhead");
+ addProp(m, LINESTYLE__LINESTARTARROWWIDTH, "linestyle.linestartarrowwidth");
+ addProp(m, LINESTYLE__LINESTARTARROWLENGTH, "linestyle.linestartarrowlength");
+ addProp(m, LINESTYLE__LINEENDARROWWIDTH, "linestyle.lineendarrowwidth");
+ addProp(m, LINESTYLE__LINEENDARROWLENGTH, "linestyle.lineendarrowlength");
+ addProp(m, LINESTYLE__LINEJOINSTYLE, "linestyle.linejoinstyle");
+ addProp(m, LINESTYLE__LINEENDCAPSTYLE, "linestyle.lineendcapstyle");
+ addProp(m, LINESTYLE__ARROWHEADSOK, "linestyle.arrowheadsok");
+ addProp(m, LINESTYLE__ANYLINE, "linestyle.anyline");
+ addProp(m, LINESTYLE__HITLINETEST, "linestyle.hitlinetest");
+ addProp(m, LINESTYLE__LINEFILLSHAPE, "linestyle.linefillshape");
+ addProp(m, LINESTYLE__NOLINEDRAWDASH, "linestyle.nolinedrawdash", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, LINESTYLE__NOLINEDRAWDASH_LEFT, "linestyle.nolinedrawdash.left", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, LINESTYLE__NOLINEDRAWDASH_TOP, "linestyle.nolinedrawdash.top", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, LINESTYLE__NOLINEDRAWDASH_BOTTOM, "linestyle.nolinedrawdash.bottom", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, LINESTYLE__NOLINEDRAWDASH_RIGHT, "linestyle.nolinedrawdash.right", EscherPropertyMetaData.TYPE_BOOLEAN);
+ addProp(m, SHADOWSTYLE__TYPE, "shadowstyle.type");
+ addProp(m, SHADOWSTYLE__COLOR, "shadowstyle.color", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, SHADOWSTYLE__HIGHLIGHT, "shadowstyle.highlight");
+ addProp(m, SHADOWSTYLE__CRMOD, "shadowstyle.crmod");
+ addProp(m, SHADOWSTYLE__OPACITY, "shadowstyle.opacity");
+ addProp(m, SHADOWSTYLE__OFFSETX, "shadowstyle.offsetx");
+ addProp(m, SHADOWSTYLE__OFFSETY, "shadowstyle.offsety");
+ addProp(m, SHADOWSTYLE__SECONDOFFSETX, "shadowstyle.secondoffsetx");
+ addProp(m, SHADOWSTYLE__SECONDOFFSETY, "shadowstyle.secondoffsety");
+ addProp(m, SHADOWSTYLE__SCALEXTOX, "shadowstyle.scalextox");
+ addProp(m, SHADOWSTYLE__SCALEYTOX, "shadowstyle.scaleytox");
+ addProp(m, SHADOWSTYLE__SCALEXTOY, "shadowstyle.scalextoy");
+ addProp(m, SHADOWSTYLE__SCALEYTOY, "shadowstyle.scaleytoy");
+ addProp(m, SHADOWSTYLE__PERSPECTIVEX, "shadowstyle.perspectivex");
+ addProp(m, SHADOWSTYLE__PERSPECTIVEY, "shadowstyle.perspectivey");
+ addProp(m, SHADOWSTYLE__WEIGHT, "shadowstyle.weight");
+ addProp(m, SHADOWSTYLE__ORIGINX, "shadowstyle.originx");
+ addProp(m, SHADOWSTYLE__ORIGINY, "shadowstyle.originy");
+ addProp(m, SHADOWSTYLE__SHADOW, "shadowstyle.shadow");
+ addProp(m, SHADOWSTYLE__SHADOWOBSURED, "shadowstyle.shadowobscured");
+ addProp(m, PERSPECTIVE__TYPE, "perspective.type");
+ addProp(m, PERSPECTIVE__OFFSETX, "perspective.offsetx");
+ addProp(m, PERSPECTIVE__OFFSETY, "perspective.offsety");
+ addProp(m, PERSPECTIVE__SCALEXTOX, "perspective.scalextox");
+ addProp(m, PERSPECTIVE__SCALEYTOX, "perspective.scaleytox");
+ addProp(m, PERSPECTIVE__SCALEXTOY, "perspective.scalextoy");
+ addProp(m, PERSPECTIVE__SCALEYTOY, "perspective.scaleytoy");
+ addProp(m, PERSPECTIVE__PERSPECTIVEX, "perspective.perspectivex");
+ addProp(m, PERSPECTIVE__PERSPECTIVEY, "perspective.perspectivey");
+ addProp(m, PERSPECTIVE__WEIGHT, "perspective.weight");
+ addProp(m, PERSPECTIVE__ORIGINX, "perspective.originx");
+ addProp(m, PERSPECTIVE__ORIGINY, "perspective.originy");
+ addProp(m, PERSPECTIVE__PERSPECTIVEON, "perspective.perspectiveon");
+ addProp(m, THREED__SPECULARAMOUNT, "3d.specularamount");
+ addProp(m, THREED__DIFFUSEAMOUNT, "3d.diffuseamount");
+ addProp(m, THREED__SHININESS, "3d.shininess");
+ addProp(m, THREED__EDGETHICKNESS, "3d.edgethickness");
+ addProp(m, THREED__EXTRUDEFORWARD, "3d.extrudeforward");
+ addProp(m, THREED__EXTRUDEBACKWARD, "3d.extrudebackward");
+ addProp(m, THREED__EXTRUDEPLANE, "3d.extrudeplane");
+ addProp(m, THREED__EXTRUSIONCOLOR, "3d.extrusioncolor", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, THREED__CRMOD, "3d.crmod");
+ addProp(m, THREED__3DEFFECT, "3d.3deffect");
+ addProp(m, THREED__METALLIC, "3d.metallic");
+ addProp(m, THREED__USEEXTRUSIONCOLOR, "3d.useextrusioncolor", EscherPropertyMetaData.TYPE_RGB);
+ addProp(m, THREED__LIGHTFACE, "3d.lightface");
+ addProp(m, THREEDSTYLE__YROTATIONANGLE, "3dstyle.yrotationangle");
+ addProp(m, THREEDSTYLE__XROTATIONANGLE, "3dstyle.xrotationangle");
+ addProp(m, THREEDSTYLE__ROTATIONAXISX, "3dstyle.rotationaxisx");
+ addProp(m, THREEDSTYLE__ROTATIONAXISY, "3dstyle.rotationaxisy");
+ addProp(m, THREEDSTYLE__ROTATIONAXISZ, "3dstyle.rotationaxisz");
+ addProp(m, THREEDSTYLE__ROTATIONANGLE, "3dstyle.rotationangle");
+ addProp(m, THREEDSTYLE__ROTATIONCENTERX, "3dstyle.rotationcenterx");
+ addProp(m, THREEDSTYLE__ROTATIONCENTERY, "3dstyle.rotationcentery");
+ addProp(m, THREEDSTYLE__ROTATIONCENTERZ, "3dstyle.rotationcenterz");
+ addProp(m, THREEDSTYLE__RENDERMODE, "3dstyle.rendermode");
+ addProp(m, THREEDSTYLE__TOLERANCE, "3dstyle.tolerance");
+ addProp(m, THREEDSTYLE__XVIEWPOINT, "3dstyle.xviewpoint");
+ addProp(m, THREEDSTYLE__YVIEWPOINT, "3dstyle.yviewpoint");
+ addProp(m, THREEDSTYLE__ZVIEWPOINT, "3dstyle.zviewpoint");
+ addProp(m, THREEDSTYLE__ORIGINX, "3dstyle.originx");
+ addProp(m, THREEDSTYLE__ORIGINY, "3dstyle.originy");
+ addProp(m, THREEDSTYLE__SKEWANGLE, "3dstyle.skewangle");
+ addProp(m, THREEDSTYLE__SKEWAMOUNT, "3dstyle.skewamount");
+ addProp(m, THREEDSTYLE__AMBIENTINTENSITY, "3dstyle.ambientintensity");
+ addProp(m, THREEDSTYLE__KEYX, "3dstyle.keyx");
+ addProp(m, THREEDSTYLE__KEYY, "3dstyle.keyy");
+ addProp(m, THREEDSTYLE__KEYZ, "3dstyle.keyz");
+ addProp(m, THREEDSTYLE__KEYINTENSITY, "3dstyle.keyintensity");
+ addProp(m, THREEDSTYLE__FILLX, "3dstyle.fillx");
+ addProp(m, THREEDSTYLE__FILLY, "3dstyle.filly");
+ addProp(m, THREEDSTYLE__FILLZ, "3dstyle.fillz");
+ addProp(m, THREEDSTYLE__FILLINTENSITY, "3dstyle.fillintensity");
+ addProp(m, THREEDSTYLE__CONSTRAINROTATION, "3dstyle.constrainrotation");
+ addProp(m, THREEDSTYLE__ROTATIONCENTERAUTO, "3dstyle.rotationcenterauto");
+ addProp(m, THREEDSTYLE__PARALLEL, "3dstyle.parallel");
+ addProp(m, THREEDSTYLE__KEYHARSH, "3dstyle.keyharsh");
+ addProp(m, THREEDSTYLE__FILLHARSH, "3dstyle.fillharsh");
+ addProp(m, SHAPE__MASTER, "shape.master");
+ addProp(m, SHAPE__CONNECTORSTYLE, "shape.connectorstyle");
+ addProp(m, SHAPE__BLACKANDWHITESETTINGS, "shape.blackandwhitesettings");
+ addProp(m, SHAPE__WMODEPUREBW, "shape.wmodepurebw");
+ addProp(m, SHAPE__WMODEBW, "shape.wmodebw");
+ addProp(m, SHAPE__OLEICON, "shape.oleicon");
+ addProp(m, SHAPE__PREFERRELATIVERESIZE, "shape.preferrelativeresize");
+ addProp(m, SHAPE__LOCKSHAPETYPE, "shape.lockshapetype");
+ addProp(m, SHAPE__DELETEATTACHEDOBJECT, "shape.deleteattachedobject");
+ addProp(m, SHAPE__BACKGROUNDSHAPE, "shape.backgroundshape");
+ addProp(m, CALLOUT__CALLOUTTYPE, "callout.callouttype");
+ addProp(m, CALLOUT__XYCALLOUTGAP, "callout.xycalloutgap");
+ addProp(m, CALLOUT__CALLOUTANGLE, "callout.calloutangle");
+ addProp(m, CALLOUT__CALLOUTDROPTYPE, "callout.calloutdroptype");
+ addProp(m, CALLOUT__CALLOUTDROPSPECIFIED, "callout.calloutdropspecified");
+ addProp(m, CALLOUT__CALLOUTLENGTHSPECIFIED, "callout.calloutlengthspecified");
+ addProp(m, CALLOUT__ISCALLOUT, "callout.iscallout");
+ addProp(m, CALLOUT__CALLOUTACCENTBAR, "callout.calloutaccentbar");
+ addProp(m, CALLOUT__CALLOUTTEXTBORDER, "callout.callouttextborder");
+ addProp(m, CALLOUT__CALLOUTMINUSX, "callout.calloutminusx");
+ addProp(m, CALLOUT__CALLOUTMINUSY, "callout.calloutminusy");
+ addProp(m, CALLOUT__DROPAUTO, "callout.dropauto");
+ addProp(m, CALLOUT__LENGTHSPECIFIED, "callout.lengthspecified");
+ addProp(m, GROUPSHAPE__SHAPENAME, "groupshape.shapename");
+ addProp(m, GROUPSHAPE__DESCRIPTION, "groupshape.description");
+ addProp(m, GROUPSHAPE__HYPERLINK, "groupshape.hyperlink");
+ addProp(m, GROUPSHAPE__WRAPPOLYGONVERTICES, "groupshape.wrappolygonvertices", EscherPropertyMetaData.TYPE_ARRAY);
+ addProp(m, GROUPSHAPE__WRAPDISTLEFT, "groupshape.wrapdistleft");
+ addProp(m, GROUPSHAPE__WRAPDISTTOP, "groupshape.wrapdisttop");
+ addProp(m, GROUPSHAPE__WRAPDISTRIGHT, "groupshape.wrapdistright");
+ addProp(m, GROUPSHAPE__WRAPDISTBOTTOM, "groupshape.wrapdistbottom");
+ addProp(m, GROUPSHAPE__REGROUPID, "groupshape.regroupid");
+ addProp( m, GROUPSHAPE__UNUSED906, "unused906" ); // 0x038A;
+ addProp( m, GROUPSHAPE__TOOLTIP, "groupshape.wzTooltip" ); // 0x038D;
+ addProp( m, GROUPSHAPE__SCRIPT, "groupshape.wzScript" ); // 0x038E;
+ addProp( m, GROUPSHAPE__POSH, "groupshape.posh" ); // 0x038F;
+ addProp( m, GROUPSHAPE__POSRELH, "groupshape.posrelh" ); // 0x0390;
+ addProp( m, GROUPSHAPE__POSV, "groupshape.posv" ); // 0x0391;
+ addProp( m, GROUPSHAPE__POSRELV, "groupshape.posrelv" ); // 0x0392;
+ addProp( m, GROUPSHAPE__HR_PCT, "groupshape.pctHR" ); // 0x0393;
+ addProp( m, GROUPSHAPE__HR_ALIGN, "groupshape.alignHR" ); // 0x0394;
+ addProp( m, GROUPSHAPE__HR_HEIGHT, "groupshape.dxHeightHR" ); // 0x0395;
+ addProp( m, GROUPSHAPE__HR_WIDTH, "groupshape.dxWidthHR" ); // 0x0396;
+ addProp( m, GROUPSHAPE__SCRIPTEXT, "groupshape.wzScriptExtAttr" ); // 0x0397;
+ addProp( m, GROUPSHAPE__SCRIPTLANG, "groupshape.scriptLang" ); // 0x0398;
+ addProp( m, GROUPSHAPE__BORDERTOPCOLOR, "groupshape.borderTopColor" ); // 0x039B;
+ addProp( m, GROUPSHAPE__BORDERLEFTCOLOR, "groupshape.borderLeftColor" ); // 0x039C;
+ addProp( m, GROUPSHAPE__BORDERBOTTOMCOLOR, "groupshape.borderBottomColor" ); // 0x039D;
+ addProp( m, GROUPSHAPE__BORDERRIGHTCOLOR, "groupshape.borderRightColor" ); // 0x039E;
+ addProp( m, GROUPSHAPE__TABLEPROPERTIES, "groupshape.tableProperties" ); // 0x039F;
+ addProp( m, GROUPSHAPE__TABLEROWPROPERTIES, "groupshape.tableRowProperties" ); // 0x03A0;
+ addProp( m, GROUPSHAPE__WEBBOT, "groupshape.wzWebBot" ); // 0x03A5;
+ addProp( m, GROUPSHAPE__METROBLOB, "groupshape.metroBlob" ); // 0x03A9;
+ addProp( m, GROUPSHAPE__ZORDER, "groupshape.dhgt" ); // 0x03AA;
+ addProp( m, GROUPSHAPE__FLAGS, "groupshape.GroupShapeBooleanProperties" ); // 0x03BF;
+
+ addProp(m, GROUPSHAPE__EDITEDWRAP, "groupshape.editedwrap");
+ addProp(m, GROUPSHAPE__BEHINDDOCUMENT, "groupshape.behinddocument");
+ addProp(m, GROUPSHAPE__ONDBLCLICKNOTIFY, "groupshape.ondblclicknotify");
+ addProp(m, GROUPSHAPE__ISBUTTON, "groupshape.isbutton");
+ addProp(m, GROUPSHAPE__1DADJUSTMENT, "groupshape.1dadjustment");
+ addProp(m, GROUPSHAPE__HIDDEN, "groupshape.hidden");
+ addProp(m, GROUPSHAPE__PRINT, "groupshape.print", EscherPropertyMetaData.TYPE_BOOLEAN);
+ return m;
+ }
+
+ private static void addProp(Map m, int s, String propName) {
+ m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName));
+ }
+
+ private static void addProp(Map m, int s, String propName, byte type) {
+ m.put(Short.valueOf((short) s), new EscherPropertyMetaData(propName, type));
+ }
+
+ public static String getPropertyName(short propertyId) {
+ EscherPropertyMetaData o = properties.get(Short.valueOf(propertyId));
+ return o == null ? "unknown" : o.getDescription();
+ }
+
+ public static byte getPropertyType(short propertyId) {
+ EscherPropertyMetaData escherPropertyMetaData = properties.get(Short.valueOf(propertyId));
+ return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherProperty.java
new file mode 100644
index 000000000..d883fe0d0
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherProperty.java
@@ -0,0 +1,110 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+/**
+ * This is the abstract base class for all escher properties.
+ *
+ * @see EscherOptRecord
+ */
+public abstract class EscherProperty {
+ private short _id;
+
+ /**
+ * The id is distinct from the actual property number. The id includes the property number the blip id
+ * flag and an indicator whether the property is complex or not.
+ *
+ * @param id the combined id
+ */
+ public EscherProperty(short id) {
+ _id = id;
+ }
+
+ /**
+ * Constructs a new escher property. The three parameters are combined to form a property
+ * id.
+ *
+ * @param propertyNumber the property number
+ * @param isComplex true, if this is a complex property
+ * @param isBlipId true, if this property is a blip id
+ */
+ public EscherProperty(short propertyNumber, boolean isComplex, boolean isBlipId) {
+ _id = (short)(propertyNumber +
+ (isComplex ? 0x8000 : 0x0) +
+ (isBlipId ? 0x4000 : 0x0));
+ }
+
+ public short getId() {
+ return _id;
+ }
+
+ public short getPropertyNumber() {
+ return (short) (_id & (short) 0x3FFF);
+ }
+
+ public boolean isComplex() {
+ return (_id & (short) 0x8000) != 0;
+ }
+
+ public boolean isBlipId() {
+ return (_id & (short) 0x4000) != 0;
+ }
+
+ public String getName() {
+ return EscherProperties.getPropertyName(getPropertyNumber());
+ }
+
+ /**
+ * Most properties are just 6 bytes in length. Override this if we're
+ * dealing with complex properties.
+ *
+ * @return size of this property (in bytes)
+ */
+ public int getPropertySize() {
+ return 6;
+ }
+
+ public String toXml(String tab){
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append("<").append(getClass().getSimpleName()).append(" id=\"").append(getId()).append("\" name=\"").append(getName()).append("\" blipId=\"")
+ .append(isBlipId()).append("\"/>\n");
+ return builder.toString();
+ }
+
+ /**
+ * Escher properties consist of a simple fixed length part and a complex variable length part.
+ * The fixed length part is serialized first.
+ *
+ * @param data the buffer to write to
+ * @param pos the starting position
+ *
+ * @return the length of the part
+ */
+ abstract public int serializeSimplePart( byte[] data, int pos );
+
+ /**
+ * Escher properties consist of a simple fixed length part and a complex variable length part.
+ * The fixed length part is serialized first.
+ *
+ * @param data the buffer to write to
+ * @param pos the starting position
+ *
+ * @return the length of the part
+ */
+ abstract public int serializeComplexPart( byte[] data, int pos );
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherPropertyFactory.java b/trunk/src/java/org/apache/poi/ddf/EscherPropertyFactory.java
new file mode 100644
index 000000000..059e29ba9
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherPropertyFactory.java
@@ -0,0 +1,94 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Generates a property given a reference into the byte array storing that property.
+ */
+public final class EscherPropertyFactory {
+ /**
+ * Create new properties from a byte array.
+ *
+ * @param data The byte array containing the property
+ * @param offset The starting offset into the byte array
+ * @param numProperties The number of properties to be read
+ * @return The new properties
+ */
+ public List createProperties(byte[] data, int offset, short numProperties) {
+ List results = new ArrayList();
+
+ int pos = offset;
+
+ for (int i = 0; i < numProperties; i++) {
+ short propId;
+ int propData;
+ propId = LittleEndian.getShort( data, pos );
+ propData = LittleEndian.getInt( data, pos + 2 );
+ short propNumber = (short) ( propId & (short) 0x3FFF );
+ boolean isComplex = ( propId & (short) 0x8000 ) != 0;
+ // boolean isBlipId = ( propId & (short) 0x4000 ) != 0;
+
+ byte propertyType = EscherProperties.getPropertyType(propNumber);
+ if ( propertyType == EscherPropertyMetaData.TYPE_BOOLEAN )
+ results.add( new EscherBoolProperty( propId, propData ) );
+ else if ( propertyType == EscherPropertyMetaData.TYPE_RGB )
+ results.add( new EscherRGBProperty( propId, propData ) );
+ else if ( propertyType == EscherPropertyMetaData.TYPE_SHAPEPATH )
+ results.add( new EscherShapePathProperty( propId, propData ) );
+ else
+ {
+ if ( !isComplex )
+ results.add( new EscherSimpleProperty( propId, propData ) );
+ else
+ {
+ if ( propertyType == EscherPropertyMetaData.TYPE_ARRAY)
+ results.add( new EscherArrayProperty( propId, new byte[propData]) );
+ else
+ results.add( new EscherComplexProperty( propId, new byte[propData]) );
+ }
+ }
+ pos += 6;
+ }
+
+ // Get complex data
+ for (EscherProperty p : results) {
+ if (p instanceof EscherComplexProperty) {
+ if (p instanceof EscherArrayProperty) {
+ pos += ((EscherArrayProperty)p).setArrayData(data, pos);
+ } else {
+ byte[] complexData = ((EscherComplexProperty)p).getComplexData();
+
+ int leftover = data.length - pos;
+ if (leftover < complexData.length) {
+ throw new IllegalStateException("Could not read complex escher property, lenght was " + complexData.length + ", but had only " +
+ leftover + " bytes left");
+ }
+
+ System.arraycopy(data, pos, complexData, 0, complexData.length);
+ pos += complexData.length;
+ }
+ }
+ }
+ return results;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java b/trunk/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java
new file mode 100644
index 000000000..b38e52df3
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java
@@ -0,0 +1,69 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+/**
+ * This class stores the type and description of an escher property.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherPropertyMetaData
+{
+ // Escher property types.
+ public final static byte TYPE_UNKNOWN = (byte) 0;
+ public final static byte TYPE_BOOLEAN = (byte) 1;
+ public final static byte TYPE_RGB = (byte) 2;
+ public final static byte TYPE_SHAPEPATH = (byte) 3;
+ public final static byte TYPE_SIMPLE = (byte)4;
+ public final static byte TYPE_ARRAY = (byte)5;
+
+ private String description;
+ private byte type;
+
+
+ /**
+ * @param description The description of the escher property.
+ */
+ public EscherPropertyMetaData( String description )
+ {
+ this.description = description;
+ }
+
+ /**
+ *
+ * @param description The description of the escher property.
+ * @param type The type of the property.
+ */
+ public EscherPropertyMetaData( String description, byte type )
+ {
+ this.description = description;
+ this.type = type;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public byte getType()
+ {
+ return type;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherRGBProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherRGBProperty.java
new file mode 100644
index 000000000..155db7f4f
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherRGBProperty.java
@@ -0,0 +1,74 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+
+/**
+ * A color property.
+ */
+public class EscherRGBProperty
+ extends EscherSimpleProperty
+{
+
+ public EscherRGBProperty( short propertyNumber, int rgbColor )
+ {
+ super( propertyNumber, rgbColor );
+ }
+
+ /**
+ * @return the rgb color as int value
+ */
+ public int getRgbColor()
+ {
+ return propertyValue;
+ }
+
+ /**
+ * @return the red part
+ */
+ public byte getRed()
+ {
+ return (byte) ( propertyValue & 0xFF );
+ }
+
+ /**
+ * @return the green part
+ */
+ public byte getGreen()
+ {
+ return (byte) ( (propertyValue >> 8) & 0xFF );
+ }
+
+ /**
+ * @return the blue part
+ */
+ public byte getBlue()
+ {
+ return (byte) ( (propertyValue >> 16) & 0xFF );
+ }
+
+ @Override
+ public String toXml(String tab){
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append("<").append(getClass().getSimpleName()).append(" id=\"0x").append(HexDump.toHex(getId()))
+ .append("\" name=\"").append(getName()).append("\" blipId=\"")
+ .append(isBlipId()).append("\" value=\"0x").append(HexDump.toHex(propertyValue)).append("\"/>\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherRecord.java
new file mode 100644
index 000000000..65926a8c1
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherRecord.java
@@ -0,0 +1,328 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * The base abstract record from which all escher records are defined. Subclasses will need
+ * to define methods for serialization/deserialization and for determining the record size.
+ */
+public abstract class EscherRecord implements Cloneable {
+ private static final BitField fInstance = BitFieldFactory.getInstance(0xfff0);
+ private static final BitField fVersion = BitFieldFactory.getInstance(0x000f);
+
+ private short _options;
+ private short _recordId;
+
+ /**
+ * Create a new instance
+ */
+ public EscherRecord() {
+ // fields uninitialised
+ }
+
+ /**
+ * Delegates to fillFields(byte[], int, EscherRecordFactory)
+ *
+ * @param data they bytes to serialize from
+ * @param f the escher record factory
+ * @return The number of bytes written.
+ *
+ * @see #fillFields(byte[], int, org.apache.poi.ddf.EscherRecordFactory)
+ */
+ protected int fillFields( byte[] data, EscherRecordFactory f )
+ {
+ return fillFields( data, 0, f );
+ }
+
+ /**
+ * The contract of this method is to deserialize an escher record including
+ * it's children.
+ *
+ * @param data The byte array containing the serialized escher
+ * records.
+ * @param offset The offset into the byte array.
+ * @param recordFactory A factory for creating new escher records.
+ * @return The number of bytes written.
+ */
+ public abstract int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory );
+
+ /**
+ * Reads the 8 byte header information and populates the options
+ * and recordId records.
+ *
+ * @param data the byte array to read from
+ * @param offset the offset to start reading from
+ * @return the number of bytes remaining in this record. This
+ * may include the children if this is a container.
+ */
+ protected int readHeader( byte[] data, int offset ) {
+ _options = LittleEndian.getShort( data, offset );
+ _recordId = LittleEndian.getShort( data, offset + 2 );
+ return LittleEndian.getInt( data, offset + 4 );
+ }
+
+ /**
+ * Read the options field from header and return instance part of it.
+ * @param data the byte array to read from
+ * @param offset the offset to start reading from
+ * @return value of instance part of options field
+ */
+ protected static short readInstance( byte data[], int offset ) {
+ final short options = LittleEndian.getShort( data, offset );
+ return fInstance.getShortValue( options );
+ }
+
+ /**
+ * Determine whether this is a container record by inspecting the option field.
+ *
+ * @return true is this is a container field.
+ */
+ public boolean isContainerRecord() {
+ return getVersion() == (short)0x000f;
+ }
+
+ /**
+ * Note that options is an internal field.
+ * Use {@link #setInstance(short)} ()} and {@link #setVersion(short)} ()} to set the actual fields.
+ *
+ * @return The options field for this record. All records have one.
+ */
+ @Internal
+ public short getOptions()
+ {
+ return _options;
+ }
+
+ /**
+ * Set the options this this record. Container records should have the
+ * last nibble set to 0xF.
+ *
+ * Note that {@code options} is an internal field.
+ * Use {@link #getInstance()} and {@link #getVersion()} to access actual fields.
+ *
+ * @param options the record options
+ */
+ @Internal
+ public void setOptions( short options ) {
+ // call to handle correct/incorrect values
+ setVersion( fVersion.getShortValue( options ) );
+ setInstance( fInstance.getShortValue( options ) );
+ _options = options;
+ }
+
+ /**
+ * Serializes to a new byte array. This is done by delegating to
+ * serialize(int, byte[]);
+ *
+ * @return the serialized record.
+ * @see #serialize(int, byte[])
+ */
+ public byte[] serialize()
+ {
+ byte[] retval = new byte[getRecordSize()];
+
+ serialize( 0, retval );
+ return retval;
+ }
+
+ /**
+ * Serializes to an existing byte array without serialization listener.
+ * This is done by delegating to serialize(int, byte[], EscherSerializationListener).
+ *
+ * @param offset the offset within the data byte array.
+ * @param data the data array to serialize to.
+ * @return The number of bytes written.
+ *
+ * @see #serialize(int, byte[], org.apache.poi.ddf.EscherSerializationListener)
+ */
+ public int serialize( int offset, byte[] data)
+ {
+ return serialize( offset, data, new NullEscherSerializationListener() );
+ }
+
+ /**
+ * Serializes the record to an existing byte array.
+ *
+ * @param offset the offset within the byte array
+ * @param data the data array to serialize to
+ * @param listener a listener for begin and end serialization events. This
+ * is useful because the serialization is
+ * hierarchical/recursive and sometimes you need to be able
+ * break into that.
+ * @return the number of bytes written.
+ */
+ public abstract int serialize( int offset, byte[] data, EscherSerializationListener listener );
+
+ /**
+ * Subclasses should effeciently return the number of bytes required to
+ * serialize the record.
+ *
+ * @return number of bytes
+ */
+ abstract public int getRecordSize();
+
+ /**
+ * Return the current record id.
+ *
+ * @return The 16 bit record id.
+ */
+ public short getRecordId() {
+ return _recordId;
+ }
+
+ /**
+ * Sets the record id for this record.
+ *
+ * @param recordId the record id
+ */
+ public void setRecordId( short recordId ) {
+ _recordId = recordId;
+ }
+
+ /**
+ * @return Returns the children of this record. By default this will
+ * be an empty list. EscherCotainerRecord is the only record
+ * that may contain children.
+ *
+ * @see EscherContainerRecord
+ */
+ public List getChildRecords() { return Collections.emptyList(); }
+
+ /**
+ * Sets the child records for this record. By default this will throw
+ * an exception as only EscherContainerRecords may have children.
+ *
+ * @param childRecords Not used in base implementation.
+ */
+ public void setChildRecords(List childRecords) {
+ throw new UnsupportedOperationException("This record does not support child records.");
+ }
+
+ /**
+ * Escher records may need to be clonable in the future.
+ *
+ * @return the cloned object
+ *
+ * @throws CloneNotSupportedException if the subclass hasn't implemented {@link Cloneable}
+ */
+ @Override
+ public EscherRecord clone() throws CloneNotSupportedException {
+ return (EscherRecord)super.clone();
+ }
+
+ /**
+ * Returns the indexed child record.
+ *
+ * @param index the index of the child within the child records
+ * @return the indexed child record
+ */
+ public EscherRecord getChild( int index ) {
+ return getChildRecords().get(index);
+ }
+
+ /**
+ * The display methods allows escher variables to print the record names
+ * according to their hierarchy.
+ *
+ * @param w The print writer to output to.
+ * @param indent The current indent level.
+ */
+ public void display(PrintWriter w, int indent)
+ {
+ for (int i = 0; i < indent * 4; i++) w.print(' ');
+ w.println(getRecordName());
+ }
+
+ /**
+ * Subclasses should return the short name for this escher record.
+ *
+ * @return the short name for this escher record
+ */
+ public abstract String getRecordName();
+
+ /**
+ * Returns the instance part of the option record.
+ *
+ * @return The instance part of the record
+ */
+ public short getInstance()
+ {
+ return fInstance.getShortValue( _options );
+ }
+
+ /**
+ * Sets the instance part of record
+ *
+ * @param value instance part value
+ */
+ public void setInstance( short value )
+ {
+ _options = fInstance.setShortValue( _options, value );
+ }
+
+ /**
+ * Returns the version part of the option record.
+ *
+ * @return The version part of the option record
+ */
+ public short getVersion()
+ {
+ return fVersion.getShortValue( _options );
+ }
+
+ /**
+ * Sets the version part of record
+ *
+ * @param value version part value
+ */
+ public void setVersion( short value )
+ {
+ _options = fVersion.setShortValue( _options, value );
+ }
+
+ /**
+ * @param tab - each children must be a right of his parent
+ * @return xml representation of this record
+ */
+ public String toXml(String tab){
+ return tab + "<" + getClass().getSimpleName() + ">\n" +
+ tab + "\t" + "0x" + HexDump.toHex(_recordId) + "\n" +
+ tab + "\t" + "" + _options + "\n" +
+ tab + "" + getClass().getSimpleName() + ">\n";
+ }
+
+ protected String formatXmlRecordHeader(String className, String recordId, String version, String instance){
+ return "<" + className + " recordId=\"0x" + recordId + "\" version=\"0x" +
+ version + "\" instance=\"0x" + instance + "\" size=\"" + getRecordSize() + "\">\n";
+ }
+
+ public String toXml(){
+ return toXml("");
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherRecordFactory.java b/trunk/src/java/org/apache/poi/ddf/EscherRecordFactory.java
new file mode 100644
index 000000000..401bbd08c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherRecordFactory.java
@@ -0,0 +1,34 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+/**
+ * The escher record factory interface allows for the creation of escher
+ * records from a pointer into a data array.
+ */
+public interface EscherRecordFactory {
+ /**
+ * Generates an escher record including any children contained under that record.
+ * An exception is thrown if the record could not be generated.
+ *
+ * @param data The byte array containing the records
+ * @param offset The starting offset into the byte array
+ * @return The generated escher record
+ */
+ EscherRecord createRecord( byte[] data, int offset );
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherSerializationListener.java b/trunk/src/java/org/apache/poi/ddf/EscherSerializationListener.java
new file mode 100644
index 000000000..dcc5eeeb8
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherSerializationListener.java
@@ -0,0 +1,44 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+/**
+ * Interface for listening to escher serialization events.
+ */
+public interface EscherSerializationListener
+{
+ /**
+ * Fired before a given escher record is serialized.
+ *
+ * @param offset The position in the data array at which the record will be serialized.
+ * @param recordId The id of the record about to be serialized.
+ * @param record The record to be serialized
+ */
+ void beforeRecordSerialize(int offset, short recordId, EscherRecord record);
+
+ /**
+ * Fired after a record has been serialized.
+ *
+ * @param offset The position of the end of the serialized record + 1
+ * @param recordId The id of the record about to be serialized
+ * @param size The number of bytes written for this record. If it is a container
+ * record then this will include the size of any included records.
+ * @param record The record which was serialized
+ */
+ void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record);
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherShapePathProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherShapePathProperty.java
new file mode 100644
index 000000000..f495f2639
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherShapePathProperty.java
@@ -0,0 +1,37 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+/**
+ * Defines the constants for the various possible shape paths.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public class EscherShapePathProperty extends EscherSimpleProperty {
+
+ public static final int LINE_OF_STRAIGHT_SEGMENTS = 0;
+ public static final int CLOSED_POLYGON = 1;
+ public static final int CURVES = 2;
+ public static final int CLOSED_CURVES = 3;
+ public static final int COMPLEX = 4;
+
+ public EscherShapePathProperty( short propertyNumber, int shapePath )
+ {
+ super( propertyNumber, false, false, shapePath );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherSimpleProperty.java b/trunk/src/java/org/apache/poi/ddf/EscherSimpleProperty.java
new file mode 100644
index 000000000..3872aa905
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherSimpleProperty.java
@@ -0,0 +1,140 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.HexDump;
+
+/**
+ * A simple property is of fixed length and as a property number in addition
+ * to a 32-bit value. Properties that can't be stored in only 32-bits are
+ * stored as EscherComplexProperty objects.
+ */
+public class EscherSimpleProperty extends EscherProperty
+{
+ protected int propertyValue;
+
+ /**
+ * The id is distinct from the actual property number. The id includes the property number the blip id
+ * flag and an indicator whether the property is complex or not.
+ *
+ * @param id the property id
+ * @param propertyValue the property value
+ */
+ public EscherSimpleProperty( short id, int propertyValue )
+ {
+ super( id );
+ this.propertyValue = propertyValue;
+ }
+
+ /**
+ * Constructs a new escher property. The three parameters are combined to form a property id.
+ *
+ * @param propertyNumber the property number
+ * @param isComplex true, if its a complex property
+ * @param isBlipId true, if its a blip
+ * @param propertyValue the property value
+ */
+ public EscherSimpleProperty( short propertyNumber, boolean isComplex, boolean isBlipId, int propertyValue )
+ {
+ super( propertyNumber, isComplex, isBlipId );
+ this.propertyValue = propertyValue;
+ }
+
+ /**
+ * Serialize the simple part of the escher record.
+ *
+ * @return the number of bytes serialized.
+ */
+ @Override
+ public int serializeSimplePart( byte[] data, int offset )
+ {
+ LittleEndian.putShort(data, offset, getId());
+ LittleEndian.putInt(data, offset + 2, propertyValue);
+ return 6;
+ }
+
+ /**
+ * Escher properties consist of a simple fixed length part and a complex variable length part.
+ * The fixed length part is serialized first.
+ */
+ @Override
+ public int serializeComplexPart( byte[] data, int pos )
+ {
+ return 0;
+ }
+
+ /**
+ * @return Return the 32 bit value of this property.
+ */
+ public int getPropertyValue()
+ {
+ return propertyValue;
+ }
+
+ /**
+ * Returns true if one escher property is equal to another.
+ */
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o ) return true;
+ if ( !( o instanceof EscherSimpleProperty ) ) return false;
+
+ final EscherSimpleProperty escherSimpleProperty = (EscherSimpleProperty) o;
+
+ if ( propertyValue != escherSimpleProperty.propertyValue ) return false;
+ if ( getId() != escherSimpleProperty.getId() ) return false;
+
+ return true;
+ }
+
+ /**
+ * Returns a hashcode so that this object can be stored in collections that
+ * require the use of such things.
+ */
+ @Override
+ public int hashCode()
+ {
+ return propertyValue;
+ }
+
+ /**
+ * @return the string representation of this property.
+ */
+ @Override
+ public String toString()
+ {
+ return "propNum: " + getPropertyNumber()
+ + ", RAW: 0x" + HexDump.toHex( getId() )
+ + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() )
+ + ", complex: " + isComplex()
+ + ", blipId: " + isBlipId()
+ + ", value: " + propertyValue + " (0x" + HexDump.toHex(propertyValue) + ")";
+ }
+
+ @Override
+ public String toXml(String tab){
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append("<").append(getClass().getSimpleName()).append(" id=\"0x").append(HexDump.toHex(getId()))
+ .append("\" name=\"").append(getName()).append("\" blipId=\"")
+ .append(isBlipId()).append("\" complex=\"").append(isComplex()).append("\" value=\"").append("0x")
+ .append(HexDump.toHex(propertyValue)).append("\"/>\n");
+ return builder.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherSpRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherSpRecord.java
new file mode 100644
index 000000000..3fec761ab
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherSpRecord.java
@@ -0,0 +1,245 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Together the the EscherOptRecord this record defines some of the basic
+ * properties of a shape.
+ */
+public class EscherSpRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF00A;
+ public static final String RECORD_DESCRIPTION = "MsofbtSp";
+
+ public static final int FLAG_GROUP = 0x0001;
+ public static final int FLAG_CHILD = 0x0002;
+ public static final int FLAG_PATRIARCH = 0x0004;
+ public static final int FLAG_DELETED = 0x0008;
+ public static final int FLAG_OLESHAPE = 0x0010;
+ public static final int FLAG_HAVEMASTER = 0x0020;
+ public static final int FLAG_FLIPHORIZ = 0x0040;
+ public static final int FLAG_FLIPVERT = 0x0080;
+ public static final int FLAG_CONNECTOR = 0x0100;
+ public static final int FLAG_HAVEANCHOR = 0x0200;
+ public static final int FLAG_BACKGROUND = 0x0400;
+ public static final int FLAG_HASSHAPETYPE = 0x0800;
+
+ private int field_1_shapeId;
+ private int field_2_flags;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ /*int bytesRemaining =*/ readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_shapeId = LittleEndian.getInt( data, pos + size ); size += 4;
+ field_2_flags = LittleEndian.getInt( data, pos + size ); size += 4;
+// bytesRemaining -= size;
+// remainingData = new byte[bytesRemaining];
+// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return getRecordSize();
+ }
+
+ /**
+ * This method serializes this escher record into a byte array.
+ *
+ * @param offset The offset into data to start writing the record data to.
+ * @param data The byte array to serialize to.
+ * @param listener A listener to retrieve start and end callbacks. Use a NullEscherSerailizationListener to ignore these events.
+ * @return The number of bytes written.
+ *
+ * @see NullEscherSerializationListener
+ */
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = 8;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+ LittleEndian.putInt( data, offset + 8, field_1_shapeId );
+ LittleEndian.putInt( data, offset + 12, field_2_flags );
+// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+// int pos = offset + 8 + 18 + remainingData.length;
+ listener.afterRecordSerialize( offset + getRecordSize(), getRecordId(), getRecordSize(), this );
+ return 8 + 8;
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + 8;
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "Sp";
+ }
+
+
+ /**
+ * @return the string representing this shape.
+ */
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty("line.separator");
+
+ return getClass().getName() + ":" + nl +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl +
+ " Version: 0x" + HexDump.toHex(getVersion()) + nl +
+ " ShapeType: 0x" + HexDump.toHex(getShapeType()) + nl +
+ " ShapeId: " + field_1_shapeId + nl +
+ " Flags: " + decodeFlags(field_2_flags) + " (0x" + HexDump.toHex(field_2_flags) + ")" + nl;
+
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(getShapeType())).append("\n")
+ .append(tab).append("\t").append("").append(field_1_shapeId).append("\n")
+ .append(tab).append("\t").append("").append(decodeFlags(field_2_flags) + " (0x" + HexDump.toHex(field_2_flags) + ")").append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Converts the shape flags into a more descriptive name.
+ */
+ private String decodeFlags( int flags )
+ {
+ StringBuffer result = new StringBuffer();
+ result.append( ( flags & FLAG_GROUP ) != 0 ? "|GROUP" : "" );
+ result.append( ( flags & FLAG_CHILD ) != 0 ? "|CHILD" : "" );
+ result.append( ( flags & FLAG_PATRIARCH ) != 0 ? "|PATRIARCH" : "" );
+ result.append( ( flags & FLAG_DELETED ) != 0 ? "|DELETED" : "" );
+ result.append( ( flags & FLAG_OLESHAPE ) != 0 ? "|OLESHAPE" : "" );
+ result.append( ( flags & FLAG_HAVEMASTER ) != 0 ? "|HAVEMASTER" : "" );
+ result.append( ( flags & FLAG_FLIPHORIZ ) != 0 ? "|FLIPHORIZ" : "" );
+ result.append( ( flags & FLAG_FLIPVERT ) != 0 ? "|FLIPVERT" : "" );
+ result.append( ( flags & FLAG_CONNECTOR ) != 0 ? "|CONNECTOR" : "" );
+ result.append( ( flags & FLAG_HAVEANCHOR ) != 0 ? "|HAVEANCHOR" : "" );
+ result.append( ( flags & FLAG_BACKGROUND ) != 0 ? "|BACKGROUND" : "" );
+ result.append( ( flags & FLAG_HASSHAPETYPE ) != 0 ? "|HASSHAPETYPE" : "" );
+
+ //need to check, else blows up on some records - bug 34435
+ if(result.length() > 0) {
+ result.deleteCharAt(0);
+ }
+ return result.toString();
+ }
+
+ /**
+ * @return A number that identifies this shape
+ */
+ public int getShapeId()
+ {
+ return field_1_shapeId;
+ }
+
+ /**
+ * Sets a number that identifies this shape.
+ *
+ * @param field_1_shapeId the shape id
+ */
+ public void setShapeId( int field_1_shapeId )
+ {
+ this.field_1_shapeId = field_1_shapeId;
+ }
+
+ /**
+ * The flags that apply to this shape.
+ *
+ * @return the flags
+ *
+ * @see #FLAG_GROUP
+ * @see #FLAG_CHILD
+ * @see #FLAG_PATRIARCH
+ * @see #FLAG_DELETED
+ * @see #FLAG_OLESHAPE
+ * @see #FLAG_HAVEMASTER
+ * @see #FLAG_FLIPHORIZ
+ * @see #FLAG_FLIPVERT
+ * @see #FLAG_CONNECTOR
+ * @see #FLAG_HAVEANCHOR
+ * @see #FLAG_BACKGROUND
+ * @see #FLAG_HASSHAPETYPE
+ */
+ public int getFlags()
+ {
+ return field_2_flags;
+ }
+
+ /**
+ * The flags that apply to this shape.
+ *
+ * @param field_2_flags the flags
+ *
+ * @see #FLAG_GROUP
+ * @see #FLAG_CHILD
+ * @see #FLAG_PATRIARCH
+ * @see #FLAG_DELETED
+ * @see #FLAG_OLESHAPE
+ * @see #FLAG_HAVEMASTER
+ * @see #FLAG_FLIPHORIZ
+ * @see #FLAG_FLIPVERT
+ * @see #FLAG_CONNECTOR
+ * @see #FLAG_HAVEANCHOR
+ * @see #FLAG_BACKGROUND
+ * @see #FLAG_HASSHAPETYPE
+ */
+ public void setFlags( int field_2_flags )
+ {
+ this.field_2_flags = field_2_flags;
+ }
+
+ /**
+ * Returns shape type. Must be one of MSOSPT values (see [MS-ODRAW] for
+ * details).
+ *
+ * @return shape type
+ */
+ public short getShapeType()
+ {
+ return getInstance();
+ }
+
+ /**
+ * Sets shape type. Must be one of MSOSPT values (see [MS-ODRAW] for
+ * details).
+ *
+ * @param value
+ * new shape type
+ */
+ public void setShapeType( short value )
+ {
+ setInstance( value );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherSpgrRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherSpgrRecord.java
new file mode 100644
index 000000000..a4d30b39a
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherSpgrRecord.java
@@ -0,0 +1,194 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * The spgr record defines information about a shape group. Groups in escher
+ * are simply another form of shape that you can't physically see.
+ */
+public class EscherSpgrRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF009;
+ public static final String RECORD_DESCRIPTION = "MsofbtSpgr";
+
+ private int field_1_rectX1;
+ private int field_2_rectY1;
+ private int field_3_rectX2;
+ private int field_4_rectY2;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_rectX1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_2_rectY1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_rectX2 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_rectY2 = LittleEndian.getInt( data, pos + size );size+=4;
+ bytesRemaining -= size;
+ if (bytesRemaining != 0) throw new RecordFormatException("Expected no remaining bytes but got " + bytesRemaining);
+// remainingData = new byte[bytesRemaining];
+// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining );
+ return 8 + size + bytesRemaining;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+ LittleEndian.putShort( data, offset, getOptions() );
+ LittleEndian.putShort( data, offset + 2, getRecordId() );
+ int remainingBytes = 16;
+ LittleEndian.putInt( data, offset + 4, remainingBytes );
+ LittleEndian.putInt( data, offset + 8, field_1_rectX1 );
+ LittleEndian.putInt( data, offset + 12, field_2_rectY1 );
+ LittleEndian.putInt( data, offset + 16, field_3_rectX2 );
+ LittleEndian.putInt( data, offset + 20, field_4_rectY2 );
+// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length );
+// int pos = offset + 8 + 18 + remainingData.length;
+ listener.afterRecordSerialize( offset + getRecordSize(), getRecordId(), offset + getRecordSize(), this );
+ return 8 + 16;
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + 16;
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "Spgr";
+ }
+
+ /**
+ * @return the string representation of this record.
+ */
+ @Override
+ public String toString() {
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + '\n' +
+ " Version: 0x" + HexDump.toHex(getVersion()) + '\n' +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + '\n' +
+ " RectX: " + field_1_rectX1 + '\n' +
+ " RectY: " + field_2_rectY1 + '\n' +
+ " RectWidth: " + field_3_rectX2 + '\n' +
+ " RectHeight: " + field_4_rectY2 + '\n';
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(field_1_rectX1).append("\n")
+ .append(tab).append("\t").append("").append(field_2_rectY1).append("\n")
+ .append(tab).append("\t").append("").append(field_3_rectX2).append("\n")
+ .append(tab).append("\t").append("").append(field_4_rectY2).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * The starting top-left coordinate of child records.
+ *
+ * @return the top-left x coordinate
+ */
+ public int getRectX1()
+ {
+ return field_1_rectX1;
+ }
+
+ /**
+ * The top-left coordinate of child records.
+ *
+ * @param x1 the top-left x coordinate
+ */
+ public void setRectX1( int x1 )
+ {
+ this.field_1_rectX1 = x1;
+ }
+
+ /**
+ * The top-left coordinate of child records.
+ *
+ * @return the top-left y coordinate
+ */
+ public int getRectY1()
+ {
+ return field_2_rectY1;
+ }
+
+ /**
+ * The top-left y coordinate of child records.
+ *
+ * @param y1 the top-left y coordinate
+ */
+ public void setRectY1( int y1 )
+ {
+ this.field_2_rectY1 = y1;
+ }
+
+ /**
+ * The bottom-right x coordinate of child records.
+ *
+ * @return the bottom-right x coordinate
+ */
+ public int getRectX2()
+ {
+ return field_3_rectX2;
+ }
+
+ /**
+ * The bottom-right x coordinate of child records.
+ *
+ * @param x2 the bottom-right x coordinate
+ */
+ public void setRectX2( int x2 )
+ {
+ this.field_3_rectX2 = x2;
+ }
+
+ /**
+ * The bottom-right y coordinate of child records.
+ *
+ * @return the bottom-right y coordinate
+ */
+ public int getRectY2()
+ {
+ return field_4_rectY2;
+ }
+
+ /**
+ * The bottom-right y coordinate of child records.
+ *
+ * @param rectY2 the bottom-right y coordinate
+ */
+ public void setRectY2(int rectY2) {
+ this.field_4_rectY2 = rectY2;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java
new file mode 100644
index 000000000..3467d0a44
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java
@@ -0,0 +1,187 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * A list of the most recently used colours for the drawings contained in
+ * this document.
+ */
+public class EscherSplitMenuColorsRecord
+ extends EscherRecord
+{
+ public static final short RECORD_ID = (short) 0xF11E;
+ public static final String RECORD_DESCRIPTION = "MsofbtSplitMenuColors";
+
+ private int field_1_color1;
+ private int field_2_color2;
+ private int field_3_color3;
+ private int field_4_color4;
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ int pos = offset + 8;
+ int size = 0;
+ field_1_color1 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_2_color2 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_3_color3 = LittleEndian.getInt( data, pos + size );size+=4;
+ field_4_color4 = LittleEndian.getInt( data, pos + size );size+=4;
+ bytesRemaining -= size;
+ if (bytesRemaining != 0)
+ throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s).");
+ return 8 + size + bytesRemaining;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener ) {
+// int field_2_numIdClusters = field_5_fileIdClusters.length + 1;
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ int pos = offset;
+ LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
+ LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
+ int remainingBytes = getRecordSize() - 8;
+
+ LittleEndian.putInt( data, pos, remainingBytes ); pos += 4;
+ LittleEndian.putInt( data, pos, field_1_color1 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_2_color2 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_3_color3 ); pos += 4;
+ LittleEndian.putInt( data, pos, field_4_color4 ); pos += 4;
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return getRecordSize();
+ }
+
+ @Override
+ public int getRecordSize() {
+ return 8 + 4 * 4;
+ }
+
+ @Override
+ public short getRecordId() {
+ return RECORD_ID;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "SplitMenuColors";
+ }
+
+ /**
+ * @return a string representation of this record.
+ */
+ @Override
+ public String toString() {
+ return getClass().getName() + ":" + '\n' +
+ " RecordId: 0x" + HexDump.toHex(RECORD_ID) + '\n' +
+ " Version: 0x" + HexDump.toHex(getVersion()) + '\n' +
+ " Instance: 0x" + HexDump.toHex(getInstance()) + '\n' +
+ " Color1: 0x" + HexDump.toHex(field_1_color1) + '\n' +
+ " Color2: 0x" + HexDump.toHex(field_2_color2) + '\n' +
+ " Color3: 0x" + HexDump.toHex(field_3_color3) + '\n' +
+ " Color4: 0x" + HexDump.toHex(field_4_color4) + '\n' +
+ "";
+ }
+
+ @Override
+ public String toXml(String tab) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(field_1_color1)).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(field_2_color2)).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(field_3_color3)).append("\n")
+ .append(tab).append("\t").append("0x").append(HexDump.toHex(field_4_color4)).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ /**
+ * Gets the fill color
+ *
+ * @return the fill color
+ */
+ public int getColor1() {
+ return field_1_color1;
+ }
+
+ /**
+ * Sets the fill color
+ *
+ * @param field_1_color1 the fill color
+ */
+ public void setColor1( int field_1_color1 ) {
+ this.field_1_color1 = field_1_color1;
+ }
+
+ /**
+ * Gets the line color
+ *
+ * @return the line color
+ */
+ public int getColor2() {
+ return field_2_color2;
+ }
+
+ /**
+ * Sets the line color
+ *
+ * @param field_2_color2 the line color
+ */
+ public void setColor2( int field_2_color2 ) {
+ this.field_2_color2 = field_2_color2;
+ }
+
+ /**
+ * Gets the shadow color
+ *
+ * @return the shadow color
+ */
+ public int getColor3() {
+ return field_3_color3;
+ }
+
+ /**
+ * Sets the shadow color
+ *
+ * @param field_3_color3 the shadow color
+ */
+ public void setColor3( int field_3_color3 ) {
+ this.field_3_color3 = field_3_color3;
+ }
+
+ /**
+ * Gets the 3-D color
+ *
+ * @return the 3-D color
+ */
+ public int getColor4() {
+ return field_4_color4;
+ }
+
+ /**
+ * Sets the 3-D color
+ *
+ * @param field_4_color4 the 3-D color
+ */
+ public void setColor4( int field_4_color4 ) {
+ this.field_4_color4 = field_4_color4;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherTertiaryOptRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherTertiaryOptRecord.java
new file mode 100644
index 000000000..618294211
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherTertiaryOptRecord.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.ddf;
+
+/**
+ * "The OfficeArtTertiaryFOPT record specifies a table of OfficeArtRGFOPTE properties, as defined in section 2.3.1."
+ * -- [MS-ODRAW] -- v20110608; Office Drawing Binary File Format
+ */
+public class EscherTertiaryOptRecord extends AbstractEscherOptRecord
+{
+ public static final short RECORD_ID = (short) 0xF122;
+
+ @Override
+ public String getRecordName()
+ {
+ return "TertiaryOpt";
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/EscherTextboxRecord.java b/trunk/src/java/org/apache/poi/ddf/EscherTextboxRecord.java
new file mode 100644
index 000000000..779808000
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/EscherTextboxRecord.java
@@ -0,0 +1,183 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Holds data from the parent application. Most commonly used to store
+ * text in the format of the parent application, rather than in
+ * Escher format. We don't attempt to understand the contents, since
+ * they will be in the parent's format, not Escher format.
+ */
+public final class EscherTextboxRecord extends EscherRecord implements Cloneable {
+ public static final short RECORD_ID = (short)0xF00D;
+ public static final String RECORD_DESCRIPTION = "msofbtClientTextbox";
+
+ private static final byte[] NO_BYTES = new byte[0];
+
+ /** The data for this record not including the the 8 byte header */
+ private byte[] thedata = NO_BYTES;
+
+ public EscherTextboxRecord()
+ {
+ }
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+
+ // Save the data, ready for the calling code to do something
+ // useful with it
+ thedata = new byte[bytesRemaining];
+ System.arraycopy( data, offset + 8, thedata, 0, bytesRemaining );
+ return bytesRemaining + 8;
+ }
+
+ @Override
+ public int serialize( int offset, byte[] data, EscherSerializationListener listener )
+ {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort(data, offset, getOptions());
+ LittleEndian.putShort(data, offset+2, getRecordId());
+ int remainingBytes = thedata.length;
+ LittleEndian.putInt(data, offset+4, remainingBytes);
+ System.arraycopy(thedata, 0, data, offset+8, thedata.length);
+ int pos = offset+8+thedata.length;
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ int size = pos - offset;
+ if (size != getRecordSize())
+ throw new RecordFormatException(size + " bytes written but getRecordSize() reports " + getRecordSize());
+ return size;
+ }
+
+ /**
+ * Returns any extra data associated with this record. In practice excel
+ * does not seem to put anything here, but with PowerPoint this will
+ * contain the bytes that make up a TextHeaderAtom followed by a
+ * TextBytesAtom/TextCharsAtom
+ *
+ * @return the extra data
+ */
+ public byte[] getData()
+ {
+ return thedata;
+ }
+
+ /**
+ * Sets the extra data (in the parent application's format) to be
+ * contained by the record. Used when the parent application changes
+ * the contents.
+ *
+ * @param b the buffer which contains the data
+ * @param start the start position in the buffer
+ * @param length the length of the block
+ */
+ public void setData(byte[] b, int start, int length)
+ {
+ thedata = new byte[length];
+ System.arraycopy(b,start,thedata,0,length);
+ }
+
+ /**
+ * Sets the extra data (in the parent application's format) to be
+ * contained by the record. Used when the parent application changes
+ * the contents.
+ *
+ * @param b the data
+ */
+ public void setData(byte[] b) {
+ setData(b,0,b.length);
+ }
+
+ @Override
+ public int getRecordSize()
+ {
+ return 8 + thedata.length;
+ }
+
+ @Override
+ public EscherTextboxRecord clone() {
+ EscherTextboxRecord etr = new EscherTextboxRecord();
+ etr.setOptions(this.getOptions());
+ etr.setRecordId(this.getRecordId());
+ etr.thedata = this.thedata.clone();
+ return etr;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "ClientTextbox";
+ }
+
+ @Override
+ public String toString()
+ {
+ String nl = System.getProperty( "line.separator" );
+
+ String theDumpHex = "";
+ try
+ {
+ if (thedata.length != 0)
+ {
+ theDumpHex = " Extra Data:" + nl;
+ theDumpHex += HexDump.dump(thedata, 0, 0);
+ }
+ }
+ catch ( Exception e )
+ {
+ theDumpHex = "Error!!";
+ }
+
+ return getClass().getName() + ":" + nl +
+ " isContainer: " + isContainerRecord() + nl +
+ " version: 0x" + HexDump.toHex( getVersion() ) + nl +
+ " instance: 0x" + HexDump.toHex( getInstance() ) + nl +
+ " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl +
+ " numchildren: " + getChildRecords().size() + nl +
+ theDumpHex;
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String theDumpHex = "";
+ try
+ {
+ if (thedata.length != 0)
+ {
+ theDumpHex += HexDump.dump(thedata, 0, 0);
+ }
+ }
+ catch ( Exception e )
+ {
+ theDumpHex = "Error!!";
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(theDumpHex).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+}
+
+
+
diff --git a/trunk/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java b/trunk/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java
new file mode 100644
index 000000000..28a8c94a6
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java
@@ -0,0 +1,33 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+/**
+ * Ignores all serialization events.
+ */
+public class NullEscherSerializationListener implements EscherSerializationListener {
+ @Override
+ public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) {
+ // do nothing
+ }
+
+ @Override
+ public void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record) {
+ // do nothing
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/UnknownEscherRecord.java b/trunk/src/java/org/apache/poi/ddf/UnknownEscherRecord.java
new file mode 100644
index 000000000..6f871528a
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/UnknownEscherRecord.java
@@ -0,0 +1,177 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ddf;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * This record is used whenever a escher record is encountered that
+ * we do not explicitly support.
+ */
+public final class UnknownEscherRecord extends EscherRecord implements Cloneable {
+ private static final byte[] NO_BYTES = new byte[0];
+
+ /** The data for this record not including the the 8 byte header */
+ private byte[] thedata = NO_BYTES;
+ private List _childRecords;
+
+ public UnknownEscherRecord() {
+ _childRecords = new ArrayList();
+ }
+
+ @Override
+ public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
+ int bytesRemaining = readHeader( data, offset );
+ /*
+ * Have a check between avaliable bytes and bytesRemaining,
+ * take the avaliable length if the bytesRemaining out of range.
+ */
+ int avaliable = data.length - (offset + 8);
+ if (bytesRemaining > avaliable) {
+ bytesRemaining = avaliable;
+ }
+
+ if (isContainerRecord()) {
+ int bytesWritten = 0;
+ thedata = new byte[0];
+ offset += 8;
+ bytesWritten += 8;
+ while ( bytesRemaining > 0 )
+ {
+ EscherRecord child = recordFactory.createRecord( data, offset );
+ int childBytesWritten = child.fillFields( data, offset, recordFactory );
+ bytesWritten += childBytesWritten;
+ offset += childBytesWritten;
+ bytesRemaining -= childBytesWritten;
+ getChildRecords().add( child );
+ }
+ return bytesWritten;
+ }
+
+ thedata = new byte[bytesRemaining];
+ System.arraycopy( data, offset + 8, thedata, 0, bytesRemaining );
+ return bytesRemaining + 8;
+ }
+
+ @Override
+ public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
+ listener.beforeRecordSerialize( offset, getRecordId(), this );
+
+ LittleEndian.putShort(data, offset, getOptions());
+ LittleEndian.putShort(data, offset+2, getRecordId());
+ int remainingBytes = thedata.length;
+ for (EscherRecord r : _childRecords) {
+ remainingBytes += r.getRecordSize();
+ }
+ LittleEndian.putInt(data, offset+4, remainingBytes);
+ System.arraycopy(thedata, 0, data, offset+8, thedata.length);
+ int pos = offset+8+thedata.length;
+ for (EscherRecord r : _childRecords) {
+ pos += r.serialize(pos, data, listener );
+ }
+
+ listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this );
+ return pos - offset;
+ }
+
+ /**
+ * @return the data which makes up this record
+ */
+ public byte[] getData() {
+ return thedata;
+ }
+
+ @Override
+ public int getRecordSize() {
+ return 8 + thedata.length;
+ }
+
+ @Override
+ public List getChildRecords() {
+ return _childRecords;
+ }
+
+ @Override
+ public void setChildRecords(List childRecords) {
+ _childRecords = childRecords;
+ }
+
+ @Override
+ public UnknownEscherRecord clone() {
+ UnknownEscherRecord uer = new UnknownEscherRecord();
+ uer.thedata = this.thedata.clone();
+ uer.setOptions(this.getOptions());
+ uer.setRecordId(this.getRecordId());
+ return uer;
+ }
+
+ @Override
+ public String getRecordName() {
+ return "Unknown 0x" + HexDump.toHex(getRecordId());
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer children = new StringBuffer();
+ if (getChildRecords().size() > 0) {
+ children.append( " children: " + '\n' );
+ for (EscherRecord record : _childRecords) {
+ children.append( record.toString() );
+ children.append( '\n' );
+ }
+ }
+
+ String theDumpHex = HexDump.toHex(thedata, 32);
+
+ return getClass().getName() + ":" + '\n' +
+ " isContainer: " + isContainerRecord() + '\n' +
+ " version: 0x" + HexDump.toHex( getVersion() ) + '\n' +
+ " instance: 0x" + HexDump.toHex( getInstance() ) + '\n' +
+ " recordId: 0x" + HexDump.toHex( getRecordId() ) + '\n' +
+ " numchildren: " + getChildRecords().size() + '\n' +
+ theDumpHex +
+ children.toString();
+ }
+
+ @Override
+ public String toXml(String tab) {
+ String theDumpHex = HexDump.toHex(thedata, 32);
+ StringBuilder builder = new StringBuilder();
+ builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance())))
+ .append(tab).append("\t").append("").append(isContainerRecord()).append("\n")
+ .append(tab).append("\t").append("").append(HexDump.toHex(_childRecords.size())).append("\n");
+ for ( Iterator iterator = _childRecords.iterator(); iterator
+ .hasNext(); )
+ {
+ EscherRecord record = iterator.next();
+ builder.append(record.toXml(tab+"\t"));
+ }
+ builder.append(theDumpHex).append("\n");
+ builder.append(tab).append("").append(getClass().getSimpleName()).append(">\n");
+ return builder.toString();
+ }
+
+ public void addChildRecord(EscherRecord childRecord) {
+ getChildRecords().add( childRecord );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/ddf/package.html b/trunk/src/java/org/apache/poi/ddf/package.html
new file mode 100644
index 000000000..6a3393d63
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/ddf/package.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
This package contains classes for decoding the Microsoft Office
+ Drawing format otherwise known as escher henceforth known in POI
+ as the Dreadful Drawing Format.
+
+
+
+
diff --git a/trunk/src/java/org/apache/poi/dev/RecordGenerator.java b/trunk/src/java/org/apache/poi/dev/RecordGenerator.java
new file mode 100644
index 000000000..585003c52
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/dev/RecordGenerator.java
@@ -0,0 +1,160 @@
+
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.dev;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Properties;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.poi.util.XMLHelper;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * Description of the Class
+ *
+ *@author andy
+ *@since May 10, 2002
+ */
+public class RecordGenerator {
+ /**
+ * The main program for the RecordGenerator class
+ *
+ *@param args The command line arguments
+ *@exception Exception Description of the Exception
+ */
+ public static void main(String[] args)
+ throws Exception {
+ // Force load so that we don't start generating records and realise this hasn't compiled yet.
+ Class.forName("org.apache.poi.generator.FieldIterator");
+
+ if (args.length != 4) {
+ System.out.println("Usage:");
+ System.out.println(" java org.apache.poi.hssf.util.RecordGenerator RECORD_DEFINTIONS RECORD_STYLES DEST_SRC_PATH TEST_SRC_PATH");
+ } else {
+ generateRecords(args[0], args[1], args[2], args[3]);
+ }
+ }
+
+
+ private static void generateRecords(String defintionsDir, String recordStyleDir, String destSrcPathDir, String testSrcPathDir)
+ throws Exception {
+ File definitionsFiles[] = new File(defintionsDir).listFiles();
+ if (definitionsFiles == null) {
+ System.err.println(defintionsDir+" is not a directory.");
+ return;
+ }
+
+ for (File file : definitionsFiles) {
+ if (file.isFile() &&
+ (file.getName().endsWith("_record.xml") ||
+ file.getName().endsWith("_type.xml")
+ )
+ ) {
+ // Get record name and package
+ DocumentBuilderFactory factory = XMLHelper.getDocumentBuilderFactory();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document document = builder.parse(file);
+ Element record = document.getDocumentElement();
+ String extendstg = record.getElementsByTagName("extends").item(0).getFirstChild().getNodeValue();
+ String suffix = record.getElementsByTagName("suffix").item(0).getFirstChild().getNodeValue();
+ String recordName = record.getAttributes().getNamedItem("name").getNodeValue();
+ String packageName = record.getAttributes().getNamedItem("package").getNodeValue();
+ packageName = packageName.replace('.', '/');
+
+ // Generate record
+ String destinationPath = destSrcPathDir + "/" + packageName;
+ File destinationPathFile = new File(destinationPath);
+ if(!destinationPathFile.mkdirs()) {
+ throw new IOException("Could not create directory " + destinationPathFile);
+ } else {
+ System.out.println("Created destination directory: " + destinationPath);
+ }
+ String destinationFilepath = destinationPath + "/" + recordName + suffix + ".java";
+ transform(file, new File(destinationFilepath),
+ new File(recordStyleDir + "/" + extendstg.toLowerCase(Locale.ROOT) + ".xsl"));
+ System.out.println("Generated " + suffix + ": " + destinationFilepath);
+
+ // Generate test (if not already generated)
+ destinationPath = testSrcPathDir + "/" + packageName;
+ destinationPathFile = new File(destinationPath);
+ if(!destinationPathFile.mkdirs()) {
+ throw new IOException("Could not create directory " + destinationPathFile);
+ } else {
+ System.out.println("Created destination directory: " + destinationPath);
+ }
+ destinationFilepath = destinationPath + "/Test" + recordName + suffix + ".java";
+ if (!new File(destinationFilepath).exists()) {
+ String temp = (recordStyleDir + "/" + extendstg.toLowerCase(Locale.ROOT) + "_test.xsl");
+ transform(file, new File(destinationFilepath), new File(temp));
+ System.out.println("Generated test: " + destinationFilepath);
+ } else {
+ System.out.println("Skipped test generation: " + destinationFilepath);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ *
Executes an XSL transformation. This process transforms an XML input
+ * file into a text output file controlled by an XSLT specification.
+ *
+ * @param in the XML input file
+ * @param out the text output file
+ * @param xslt the XSLT specification, i.e. an XSL style sheet
+ * @throws FileNotFoundException
+ * @throws TransformerException
+ */
+ private static void transform(final File in, final File out, final File xslt)
+ throws FileNotFoundException, TransformerException
+ {
+ final StreamSource ss = new StreamSource(xslt);
+ final TransformerFactory tf = TransformerFactory.newInstance();
+ final Transformer t;
+ try
+ {
+ t = tf.newTransformer(ss);
+ }
+ catch (TransformerException ex)
+ {
+ System.err.println("Error compiling XSL style sheet " + xslt);
+ throw ex;
+ }
+ final Properties p = new Properties();
+ p.setProperty(OutputKeys.METHOD, "text");
+ t.setOutputProperties(p);
+ final Result result = new StreamResult(out);
+ t.transform(new StreamSource(in), result);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java b/trunk/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java
new file mode 100644
index 000000000..737e9e351
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/extractor/OLE2ExtractorFactory.java
@@ -0,0 +1,270 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.extractor;
+
+import static org.apache.poi.hssf.model.InternalWorkbook.OLD_WORKBOOK_DIR_ENTRY_NAME;
+import static org.apache.poi.hssf.model.InternalWorkbook.WORKBOOK_DIR_ENTRY_NAMES;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.POIOLE2TextExtractor;
+import org.apache.poi.POITextExtractor;
+import org.apache.poi.hssf.OldExcelFormatException;
+import org.apache.poi.hssf.extractor.EventBasedExcelExtractor;
+import org.apache.poi.hssf.extractor.ExcelExtractor;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * Figures out the correct POIOLE2TextExtractor for your supplied
+ * document, and returns it.
+ *
+ *
Note 1 - will fail for many file formats if the POI Scratchpad jar is
+ * not present on the runtime classpath
+ *
Note 2 - for text extractor creation across all formats, use
+ * {@link org.apache.poi.extractor.ExtractorFactory} contained within
+ * the OOXML jar.
+ *
Note 3 - rather than using this, for most cases you would be better
+ * off switching to Apache Tika instead!
+ */
+@SuppressWarnings("WeakerAccess")
+public class OLE2ExtractorFactory {
+ private static final POILogger LOGGER = POILogFactory.getLogger(OLE2ExtractorFactory.class);
+
+ /** Should this thread prefer event based over usermodel based extractors? */
+ private static final ThreadLocal threadPreferEventExtractors = new ThreadLocal() {
+ @Override
+ protected Boolean initialValue() { return Boolean.FALSE; }
+ };
+
+ /** Should all threads prefer event based over usermodel based extractors? */
+ private static Boolean allPreferEventExtractors;
+
+ /**
+ * Should this thread prefer event based over usermodel based extractors?
+ * (usermodel extractors tend to be more accurate, but use more memory)
+ * Default is false.
+ */
+ public static boolean getThreadPrefersEventExtractors() {
+ return threadPreferEventExtractors.get();
+ }
+
+ /**
+ * Should all threads prefer event based over usermodel based extractors?
+ * (usermodel extractors tend to be more accurate, but use more memory)
+ * Default is to use the thread level setting, which defaults to false.
+ */
+ public static Boolean getAllThreadsPreferEventExtractors() {
+ return allPreferEventExtractors;
+ }
+
+ /**
+ * Should this thread prefer event based over usermodel based extractors?
+ * Will only be used if the All Threads setting is null.
+ */
+ public static void setThreadPrefersEventExtractors(boolean preferEventExtractors) {
+ threadPreferEventExtractors.set(preferEventExtractors);
+ }
+
+ /**
+ * Should all threads prefer event based over usermodel based extractors?
+ * If set, will take preference over the Thread level setting.
+ */
+ public static void setAllThreadsPreferEventExtractors(Boolean preferEventExtractors) {
+ allPreferEventExtractors = preferEventExtractors;
+ }
+
+ /**
+ * Should this thread use event based extractors is available?
+ * Checks the all-threads one first, then thread specific.
+ */
+ protected static boolean getPreferEventExtractor() {
+ if(allPreferEventExtractors != null) {
+ return allPreferEventExtractors;
+ }
+ return threadPreferEventExtractors.get();
+ }
+
+ public static POIOLE2TextExtractor createExtractor(POIFSFileSystem fs) throws IOException {
+ // Only ever an OLE2 one from the root of the FS
+ return (POIOLE2TextExtractor)createExtractor(fs.getRoot());
+ }
+ public static POIOLE2TextExtractor createExtractor(NPOIFSFileSystem fs) throws IOException {
+ // Only ever an OLE2 one from the root of the FS
+ return (POIOLE2TextExtractor)createExtractor(fs.getRoot());
+ }
+ public static POIOLE2TextExtractor createExtractor(OPOIFSFileSystem fs) throws IOException {
+ // Only ever an OLE2 one from the root of the FS
+ return (POIOLE2TextExtractor)createExtractor(fs.getRoot());
+ }
+
+ public static POITextExtractor createExtractor(InputStream input) throws IOException {
+ Class> cls = getOOXMLClass();
+ if (cls != null) {
+ // Use Reflection to get us the full OOXML-enabled version
+ try {
+ Method m = cls.getDeclaredMethod("createExtractor", InputStream.class);
+ return (POITextExtractor)m.invoke(null, input);
+ } catch (IllegalArgumentException iae) {
+ throw iae;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Error creating Extractor for InputStream", e);
+ }
+ } else {
+ // Best hope it's OLE2....
+ return createExtractor(new NPOIFSFileSystem(input));
+ }
+ }
+
+ private static Class> getOOXMLClass() {
+ try {
+ return OLE2ExtractorFactory.class.getClassLoader().loadClass(
+ "org.apache.poi.extractor.ExtractorFactory"
+ );
+ } catch (ClassNotFoundException e) {
+ LOGGER.log(POILogger.WARN, "POI OOXML jar missing");
+ return null;
+ }
+ }
+ private static Class> getScratchpadClass() {
+ try {
+ return OLE2ExtractorFactory.class.getClassLoader().loadClass(
+ "org.apache.poi.extractor.OLE2ScratchpadExtractorFactory"
+ );
+ } catch (ClassNotFoundException e) {
+ LOGGER.log(POILogger.ERROR, "POI Scratchpad jar missing");
+ throw new IllegalStateException("POI Scratchpad jar missing, required for ExtractorFactory");
+ }
+ }
+
+ /**
+ * Create the Extractor, if possible. Generally needs the Scratchpad jar.
+ * Note that this won't check for embedded OOXML resources either, use
+ * {@link org.apache.poi.extractor.ExtractorFactory} for that.
+ */
+ public static POITextExtractor createExtractor(DirectoryNode poifsDir)
+ throws IOException
+ {
+ // Look for certain entries in the stream, to figure it
+ // out from
+ for (String workbookName : WORKBOOK_DIR_ENTRY_NAMES) {
+ if (poifsDir.hasEntry(workbookName)) {
+ if (getPreferEventExtractor()) {
+ return new EventBasedExcelExtractor(poifsDir);
+ }
+ return new ExcelExtractor(poifsDir);
+ }
+ }
+ if (poifsDir.hasEntry(OLD_WORKBOOK_DIR_ENTRY_NAME)) {
+ throw new OldExcelFormatException("Old Excel Spreadsheet format (1-95) "
+ + "found. Please call OldExcelExtractor directly for basic text extraction");
+ }
+
+ // Ask Scratchpad, or fail trying
+ Class> cls = getScratchpadClass();
+ try {
+ Method m = cls.getDeclaredMethod("createExtractor", DirectoryNode.class);
+ POITextExtractor ext = (POITextExtractor)m.invoke(null, poifsDir);
+ if (ext != null) return ext;
+ } catch (IllegalArgumentException iae) {
+ throw iae;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Error creating Scratchpad Extractor", e);
+ }
+
+ throw new IllegalArgumentException("No supported documents found in the OLE2 stream");
+ }
+
+ /**
+ * Returns an array of text extractors, one for each of
+ * the embedded documents in the file (if there are any).
+ * If there are no embedded documents, you'll get back an
+ * empty array. Otherwise, you'll get one open
+ * {@link POITextExtractor} for each embedded file.
+ */
+ public static POITextExtractor[] getEmbededDocsTextExtractors(POIOLE2TextExtractor ext)
+ throws IOException
+ {
+ // All the embedded directories we spotted
+ List dirs = new ArrayList();
+ // For anything else not directly held in as a POIFS directory
+ List nonPOIFS = new ArrayList();
+
+ // Find all the embedded directories
+ DirectoryEntry root = ext.getRoot();
+ if(root == null) {
+ throw new IllegalStateException("The extractor didn't know which POIFS it came from!");
+ }
+
+ if(ext instanceof ExcelExtractor) {
+ // These are in MBD... under the root
+ Iterator it = root.getEntries();
+ while(it.hasNext()) {
+ Entry entry = it.next();
+ if(entry.getName().startsWith("MBD")) {
+ dirs.add(entry);
+ }
+ }
+ } else {
+ // Ask Scratchpad, or fail trying
+ Class> cls = getScratchpadClass();
+ try {
+ Method m = cls.getDeclaredMethod(
+ "identifyEmbeddedResources", POIOLE2TextExtractor.class, List.class, List.class);
+ m.invoke(null, ext, dirs, nonPOIFS);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Error checking for Scratchpad embedded resources", e);
+ }
+ }
+
+ // Create the extractors
+ if(dirs.size() == 0 && nonPOIFS.size() == 0){
+ return new POITextExtractor[0];
+ }
+
+ ArrayList e = new ArrayList();
+ for (Entry dir : dirs) {
+ e.add(createExtractor(
+ (DirectoryNode) dir
+ ));
+ }
+ for (InputStream nonPOIF : nonPOIFS) {
+ try {
+ e.add(createExtractor(nonPOIF));
+ } catch (IllegalArgumentException ie) {
+ // Ignore, just means it didn't contain
+ // a format we support as yet
+ LOGGER.log(POILogger.WARN, ie);
+ } catch (Exception xe) {
+ // Ignore, invalid format
+ LOGGER.log(POILogger.WARN, xe);
+ }
+ }
+ return e.toArray(new POITextExtractor[e.size()]);
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Array.java b/trunk/src/java/org/apache/poi/hpsf/Array.java
new file mode 100644
index 000000000..8850ab9cd
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Array.java
@@ -0,0 +1,137 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Array
+{
+ static class ArrayDimension
+ {
+ static final int SIZE = 8;
+
+ private int _indexOffset;
+ private long _size;
+
+ ArrayDimension( byte[] data, int offset )
+ {
+ _size = LittleEndian.getUInt( data, offset );
+ _indexOffset = LittleEndian.getInt( data, offset
+ + LittleEndian.INT_SIZE );
+ }
+ }
+
+ static class ArrayHeader
+ {
+ private ArrayDimension[] _dimensions;
+ private int _type;
+
+ ArrayHeader( byte[] data, int startOffset )
+ {
+ int offset = startOffset;
+
+ _type = LittleEndian.getInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ long numDimensionsUnsigned = LittleEndian.getUInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ if ( !( 1 <= numDimensionsUnsigned && numDimensionsUnsigned <= 31 ) )
+ throw new IllegalPropertySetDataException(
+ "Array dimension number " + numDimensionsUnsigned
+ + " is not in [1; 31] range" );
+ int numDimensions = (int) numDimensionsUnsigned;
+
+ _dimensions = new ArrayDimension[numDimensions];
+ for ( int i = 0; i < numDimensions; i++ )
+ {
+ _dimensions[i] = new ArrayDimension( data, offset );
+ offset += ArrayDimension.SIZE;
+ }
+ }
+
+ long getNumberOfScalarValues()
+ {
+ long result = 1;
+ for ( ArrayDimension dimension : _dimensions )
+ result *= dimension._size;
+ return result;
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE * 2 + _dimensions.length
+ * ArrayDimension.SIZE;
+ }
+
+ int getType()
+ {
+ return _type;
+ }
+ }
+
+ private ArrayHeader _header;
+ private TypedPropertyValue[] _values;
+
+ Array()
+ {
+ }
+
+ Array( final byte[] data, final int offset )
+ {
+ read( data, offset );
+ }
+
+ int read( final byte[] data, final int startOffset )
+ {
+ int offset = startOffset;
+
+ _header = new ArrayHeader( data, offset );
+ offset += _header.getSize();
+
+ long numberOfScalarsLong = _header.getNumberOfScalarValues();
+ if ( numberOfScalarsLong > Integer.MAX_VALUE )
+ throw new UnsupportedOperationException(
+ "Sorry, but POI can't store array of properties with size of "
+ + numberOfScalarsLong + " in memory" );
+ int numberOfScalars = (int) numberOfScalarsLong;
+
+ _values = new TypedPropertyValue[numberOfScalars];
+ final int type = _header._type;
+ if ( type == Variant.VT_VARIANT )
+ {
+ for ( int i = 0; i < numberOfScalars; i++ )
+ {
+ TypedPropertyValue typedPropertyValue = new TypedPropertyValue();
+ offset += typedPropertyValue.read( data, offset );
+ }
+ }
+ else
+ {
+ for ( int i = 0; i < numberOfScalars; i++ )
+ {
+ TypedPropertyValue typedPropertyValue = new TypedPropertyValue(
+ type, null );
+ offset += typedPropertyValue.readValuePadded( data, offset );
+ }
+ }
+
+ return offset - startOffset;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Blob.java b/trunk/src/java/org/apache/poi/hpsf/Blob.java
new file mode 100644
index 000000000..547e2392b
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Blob.java
@@ -0,0 +1,45 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Blob
+{
+ private byte[] _value;
+
+ Blob( byte[] data, int offset )
+ {
+ int size = LittleEndian.getInt( data, offset );
+
+ if ( size == 0 )
+ {
+ _value = new byte[0];
+ return;
+ }
+
+ _value = LittleEndian.getByteArray( data, offset
+ + LittleEndian.INT_SIZE, size );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/ClassID.java b/trunk/src/java/org/apache/poi/hpsf/ClassID.java
new file mode 100644
index 000000000..1c06db015
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/ClassID.java
@@ -0,0 +1,265 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.StringUtil;
+
+/**
+ *
Represents a class ID (16 bytes). Unlike other little-endian
+ * type the {@link ClassID} is not just 16 bytes stored in the wrong
+ * order. Instead, it is a double word (4 bytes) followed by two
+ * words (2 bytes each) followed by 8 bytes.
+ */
+public class ClassID
+{
+ public static final ClassID OLE10_PACKAGE = new ClassID("{0003000C-0000-0000-C000-000000000046}");
+ public static final ClassID PPT_SHOW = new ClassID("{64818D10-4F9B-11CF-86EA-00AA00B929E8}");
+ public static final ClassID XLS_WORKBOOK = new ClassID("{00020841-0000-0000-C000-000000000046}");
+ public static final ClassID TXT_ONLY = new ClassID("{5e941d80-bf96-11cd-b579-08002b30bfeb}");
+ public static final ClassID EXCEL97 = new ClassID("{00020820-0000-0000-C000-000000000046}");
+ public static final ClassID EXCEL95 = new ClassID("{00020810-0000-0000-C000-000000000046}");
+ public static final ClassID WORD97 = new ClassID("{00020906-0000-0000-C000-000000000046}");
+ public static final ClassID WORD95 = new ClassID("{00020900-0000-0000-C000-000000000046}");
+ public static final ClassID POWERPOINT97 = new ClassID("{64818D10-4F9B-11CF-86EA-00AA00B929E8}");
+ public static final ClassID POWERPOINT95 = new ClassID("{EA7BAE70-FB3B-11CD-A903-00AA00510EA3}");
+ public static final ClassID EQUATION30 = new ClassID("{0002CE02-0000-0000-C000-000000000046}");
+
+
+ /**
+ *
The bytes making out the class ID in correct order,
+ * i.e. big-endian.
+ */
+ protected byte[] bytes;
+
+
+
+ /**
+ *
Creates a {@link ClassID} and reads its value from a byte
+ * array.
+ *
+ * @param src The byte array to read from.
+ * @param offset The offset of the first byte to read.
+ */
+ public ClassID(final byte[] src, final int offset)
+ {
+ read(src, offset);
+ }
+
+
+ /**
+ *
Creates a {@link ClassID} and initializes its value with
+ * 0x00 bytes.
+ */
+ public ClassID()
+ {
+ bytes = new byte[LENGTH];
+ for (int i = 0; i < LENGTH; i++)
+ bytes[i] = 0x00;
+ }
+
+
+ /**
+ *
Creates a {@link ClassID} from a human-readable representation of the Class ID in standard
+ * format "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}".
+ *
+ * @param externalForm representation of the Class ID represented by this object.
+ */
+ public ClassID(String externalForm) {
+ bytes = new byte[LENGTH];
+ String clsStr = externalForm.replaceAll("[{}-]", "");
+ for (int i=0; iThe number of bytes occupied by this object in the byte
+ * stream.
*/
+ public static final int LENGTH = 16;
+
+ /**
+ * @return The number of bytes occupied by this object in the byte
+ * stream.
+ */
+ public int length()
+ {
+ return LENGTH;
+ }
+
+
+
+ /**
+ *
Gets the bytes making out the class ID. They are returned in
+ * correct order, i.e. big-endian.
+ *
+ * @return the bytes making out the class ID.
+ */
+ public byte[] getBytes()
+ {
+ return bytes;
+ }
+
+
+
+ /**
+ *
Sets the bytes making out the class ID.
+ *
+ * @param bytes The bytes making out the class ID in big-endian format. They
+ * are copied without their order being changed.
+ */
+ public void setBytes(final byte[] bytes)
+ {
+ for (int i = 0; i < this.bytes.length; i++)
+ this.bytes[i] = bytes[i];
+ }
+
+
+
+ /**
+ *
Reads the class ID's value from a byte array by turning
+ * little-endian into big-endian.
+ *
+ * @param src The byte array to read from
+ *
+ * @param offset The offset within the src byte array
+ *
+ * @return A byte array containing the class ID.
+ */
+ public byte[] read(final byte[] src, final int offset)
+ {
+ bytes = new byte[16];
+
+ /* Read double word. */
+ bytes[0] = src[3 + offset];
+ bytes[1] = src[2 + offset];
+ bytes[2] = src[1 + offset];
+ bytes[3] = src[0 + offset];
+
+ /* Read first word. */
+ bytes[4] = src[5 + offset];
+ bytes[5] = src[4 + offset];
+
+ /* Read second word. */
+ bytes[6] = src[7 + offset];
+ bytes[7] = src[6 + offset];
+
+ /* Read 8 bytes. */
+ for (int i = 8; i < 16; i++)
+ bytes[i] = src[i + offset];
+
+ return bytes;
+ }
+
+
+
+ /**
+ *
Writes the class ID to a byte array in the
+ * little-endian format.
+ *
+ * @param dst The byte array to write to.
+ *
+ * @param offset The offset within the dst byte array.
+ *
+ * @exception ArrayStoreException if there is not enough room for the class
+ * ID 16 bytes in the byte array after the offset position.
+ */
+ public void write(final byte[] dst, final int offset)
+ throws ArrayStoreException
+ {
+ /* Check array size: */
+ if (dst.length < 16)
+ throw new ArrayStoreException
+ ("Destination byte[] must have room for at least 16 bytes, " +
+ "but has a length of only " + dst.length + ".");
+ /* Write double word. */
+ dst[0 + offset] = bytes[3];
+ dst[1 + offset] = bytes[2];
+ dst[2 + offset] = bytes[1];
+ dst[3 + offset] = bytes[0];
+
+ /* Write first word. */
+ dst[4 + offset] = bytes[5];
+ dst[5 + offset] = bytes[4];
+
+ /* Write second word. */
+ dst[6 + offset] = bytes[7];
+ dst[7 + offset] = bytes[6];
+
+ /* Write 8 bytes. */
+ for (int i = 8; i < 16; i++)
+ dst[i + offset] = bytes[i];
+ }
+
+
+
+ /**
+ *
Checks whether this ClassID is equal to another
+ * object.
+ *
+ * @param o the object to compare this PropertySet with
+ * @return true if the objects are equal, else
+ * false.
+ */
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (o == null || !(o instanceof ClassID))
+ return false;
+ final ClassID cid = (ClassID) o;
+ if (bytes.length != cid.bytes.length)
+ return false;
+ for (int i = 0; i < bytes.length; i++)
+ if (bytes[i] != cid.bytes[i])
+ return false;
+ return true;
+ }
+
+
+
+ /**
+ * @see Object#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ return new String(bytes, StringUtil.UTF8).hashCode();
+ }
+
+ /**
+ *
Returns a human-readable representation of the Class ID in standard
+ * format "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}".
+ *
+ * @return String representation of the Class ID represented by this object.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuffer sbClassId = new StringBuffer(38);
+ sbClassId.append('{');
+ for (int i = 0; i < 16; i++)
+ {
+ sbClassId.append(HexDump.toHex(bytes[i]));
+ if (i == 3 || i == 5 || i == 7 || i == 9)
+ sbClassId.append('-');
+ }
+ sbClassId.append('}');
+ return sbClassId.toString();
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/ClipboardData.java b/trunk/src/java/org/apache/poi/hpsf/ClipboardData.java
new file mode 100644
index 000000000..42ce724a9
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/ClipboardData.java
@@ -0,0 +1,84 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+@Internal
+class ClipboardData
+{
+ private static final POILogger logger = POILogFactory
+ .getLogger( ClipboardData.class );
+
+ private int _format;
+ private byte[] _value;
+
+ ClipboardData( byte[] data, int offset )
+ {
+ int size = LittleEndian.getInt( data, offset );
+
+ if ( size < 4 )
+ {
+ logger.log( POILogger.WARN, "ClipboardData at offset ",
+ Integer.valueOf( offset ), " size less than 4 bytes "
+ + "(doesn't even have format field!). "
+ + "Setting to format == 0 and hope for the best" );
+ _format = 0;
+ _value = new byte[0];
+ return;
+ }
+
+ _format = LittleEndian.getInt( data, offset + LittleEndian.INT_SIZE );
+ _value = LittleEndian.getByteArray( data, offset
+ + LittleEndian.INT_SIZE * 2, size - LittleEndian.INT_SIZE );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE * 2 + _value.length;
+ }
+
+ byte[] getValue()
+ {
+ return _value;
+ }
+
+ byte[] toByteArray()
+ {
+ byte[] result = new byte[getSize()];
+ LittleEndian.putInt( result, 0 * LittleEndian.INT_SIZE,
+ LittleEndian.INT_SIZE + _value.length );
+ LittleEndian.putInt( result, 1 * LittleEndian.INT_SIZE, _format );
+ System.arraycopy( _value, 0, result, LittleEndian.INT_SIZE
+ + LittleEndian.INT_SIZE, _value.length );
+ return result;
+ }
+
+ int write( OutputStream out ) throws IOException
+ {
+ LittleEndian.putInt( LittleEndian.INT_SIZE + _value.length, out );
+ LittleEndian.putInt( _format, out );
+ out.write( _value );
+ return 2 * LittleEndian.INT_SIZE + _value.length;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/CodePageString.java b/trunk/src/java/org/apache/poi/hpsf/CodePageString.java
new file mode 100644
index 000000000..8eb8987b3
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/CodePageString.java
@@ -0,0 +1,111 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.poi.util.CodePageUtil;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.StringUtil;
+
+@Internal
+class CodePageString
+{
+ private final static POILogger logger = POILogFactory
+ .getLogger( CodePageString.class );
+
+ private byte[] _value;
+
+ CodePageString( final byte[] data, final int startOffset )
+ {
+ int offset = startOffset;
+
+ int size = LittleEndian.getInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ _value = LittleEndian.getByteArray( data, offset, size );
+ if ( size != 0 && _value[size - 1] != 0 ) {
+ // TODO Some files, such as TestVisioWithCodepage.vsd, are currently
+ // triggering this for values that don't look like codepages
+ // See Bug #52258 for details
+ logger.log(POILogger.WARN, "CodePageString started at offset #" + offset
+ + " is not NULL-terminated" );
+// throw new IllegalPropertySetDataException(
+// "CodePageString started at offset #" + offset
+// + " is not NULL-terminated" );
+ }
+ }
+
+ CodePageString( String string, int codepage )
+ throws UnsupportedEncodingException
+ {
+ setJavaValue( string, codepage );
+ }
+
+ String getJavaValue( int codepage ) throws UnsupportedEncodingException
+ {
+ String result;
+ if ( codepage == -1 )
+ result = new String( _value, StringUtil.UTF8 );
+ else
+ result = CodePageUtil.getStringFromCodePage(_value, codepage);
+ final int terminator = result.indexOf( '\0' );
+ if ( terminator == -1 )
+ {
+ logger.log(
+ POILogger.WARN,
+ "String terminator (\\0) for CodePageString property value not found."
+ + "Continue without trimming and hope for the best." );
+ return result;
+ }
+ if ( terminator != result.length() - 1 )
+ {
+ logger.log(
+ POILogger.WARN,
+ "String terminator (\\0) for CodePageString property value occured before the end of string. "
+ + "Trimming and hope for the best." );
+ }
+ return result.substring( 0, terminator );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+
+ void setJavaValue( String string, int codepage )
+ throws UnsupportedEncodingException
+ {
+ String stringNT = string + "\0";
+ if ( codepage == -1 )
+ _value = stringNT.getBytes(StringUtil.UTF8);
+ else
+ _value = CodePageUtil.getBytesInCodePage(stringNT, codepage);
+ }
+
+ int write( OutputStream out ) throws IOException
+ {
+ LittleEndian.putInt( _value.length, out );
+ out.write( _value );
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Currency.java b/trunk/src/java/org/apache/poi/hpsf/Currency.java
new file mode 100644
index 000000000..8928a56a0
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Currency.java
@@ -0,0 +1,33 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Currency
+{
+ static final int SIZE = 8;
+
+ private byte[] _value;
+
+ Currency( byte[] data, int offset )
+ {
+ _value = LittleEndian.getByteArray( data, offset, SIZE );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/CustomProperties.java b/trunk/src/java/org/apache/poi/hpsf/CustomProperties.java
new file mode 100644
index 000000000..f305836fa
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/CustomProperties.java
@@ -0,0 +1,429 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+
+/**
+ *
Maintains the instances of {@link CustomProperty} that belong to a
+ * {@link DocumentSummaryInformation}. The class maintains the names of the
+ * custom properties in a dictionary. It implements the {@link Map} interface
+ * and by this provides a simplified view on custom properties: A property's
+ * name is the key that maps to a typed value. This implementation hides
+ * property IDs from the developer and regards the property names as keys to
+ * typed values.
+ *
+ *
While this class provides a simple API to custom properties, it ignores
+ * the fact that not names, but IDs are the real keys to properties. Under the
+ * hood this class maintains a 1:1 relationship between IDs and names. Therefore
+ * you should not use this class to process property sets with several IDs
+ * mapping to the same name or with properties without a name: the result will
+ * contain only a subset of the original properties. If you really need to deal
+ * such property sets, use HPSF's low-level access methods.
+ *
+ *
An application can call the {@link #isPure} method to check whether a
+ * property set parsed by {@link CustomProperties} is still pure (i.e.
+ * unmodified) or whether one or more properties have been dropped.
+ *
+ *
This class is not thread-safe; concurrent access to instances of this
+ * class must be synchronized.
+ *
+ *
While this class is roughly HashMap<Long,CustomProperty>, that's the
+ * internal representation. To external calls, it should appear as
+ * HashMap<String,Object> mapping between Names and Custom Property Values.
Puts a {@link CustomProperty} into this map. It is assumed that the
+ * {@link CustomProperty} already has a valid ID. Otherwise use
+ * {@link #put(CustomProperty)}.
+ *
+ * @param name the property name
+ * @param cp the property
+ *
+ * @return the previous property stored under this name
+ */
+ public CustomProperty put(final String name, final CustomProperty cp)
+ {
+ if (name == null)
+ {
+ /* Ignoring a property without a name. */
+ isPure = false;
+ return null;
+ }
+ if (!(name.equals(cp.getName())))
+ throw new IllegalArgumentException("Parameter \"name\" (" + name +
+ ") and custom property's name (" + cp.getName() +
+ ") do not match.");
+
+ /* Register name and ID in the dictionary. Mapping in both directions is possible. If there is already a */
+ final Long idKey = Long.valueOf(cp.getID());
+ final Long oldID = dictionaryNameToID.get(name);
+ dictionaryIDToName.remove(oldID);
+ dictionaryNameToID.put(name, idKey);
+ dictionaryIDToName.put(idKey, name);
+
+ /* Put the custom property into this map. */
+ final CustomProperty oldCp = super.remove(oldID);
+ super.put(idKey, cp);
+ return oldCp;
+ }
+
+
+
+ /**
+ *
Puts a {@link CustomProperty} that has not yet a valid ID into this
+ * map. The method will allocate a suitable ID for the custom property:
+ *
+ *
+ *
+ *
If there is already a property with the same name, take the ID
+ * of that property.
+ *
+ *
Otherwise find the highest ID and use its value plus one.
+ *
+ *
+ *
+ * @param customProperty
+ * @return If the was already a property with the same name, the
+ * @throws ClassCastException
+ */
+ private Object put(final CustomProperty customProperty) throws ClassCastException
+ {
+ final String name = customProperty.getName();
+
+ /* Check whether a property with this name is in the map already. */
+ final Long oldId = dictionaryNameToID.get(name);
+ if (oldId != null)
+ customProperty.setID(oldId.longValue());
+ else
+ {
+ long max = 1;
+ for (Long long1 : dictionaryIDToName.keySet()) {
+ final long id = long1.longValue();
+ if (id > max)
+ max = id;
+ }
+ customProperty.setID(max + 1);
+ }
+ return this.put(name, customProperty);
+ }
+
+
+
+ /**
+ *
Removes a custom property.
+ * @param name The name of the custom property to remove
+ * @return The removed property or null if the specified property was not found.
+ *
+ * @see java.util.HashSet#remove(java.lang.Object)
+ */
+ public Object remove(final String name)
+ {
+ final Long id = dictionaryNameToID.get(name);
+ if (id == null)
+ return null;
+ dictionaryIDToName.remove(id);
+ dictionaryNameToID.remove(name);
+ return super.remove(id);
+ }
+
+ /**
+ *
Adds a named string property.
+ *
+ * @param name The property's name.
+ * @param value The property's value.
+ * @return the property that was stored under the specified name before, or
+ * null if there was no such property before.
+ */
+ public Object put(final String name, final String value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(-1);
+ p.setType(Variant.VT_LPWSTR);
+ p.setValue(value);
+ final CustomProperty cp = new CustomProperty(p, name);
+ return put(cp);
+ }
+
+ /**
+ *
Adds a named long property.
+ *
+ * @param name The property's name.
+ * @param value The property's value.
+ * @return the property that was stored under the specified name before, or
+ * null if there was no such property before.
+ */
+ public Object put(final String name, final Long value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(-1);
+ p.setType(Variant.VT_I8);
+ p.setValue(value);
+ final CustomProperty cp = new CustomProperty(p, name);
+ return put(cp);
+ }
+
+ /**
+ *
Adds a named double property.
+ *
+ * @param name The property's name.
+ * @param value The property's value.
+ * @return the property that was stored under the specified name before, or
+ * null if there was no such property before.
+ */
+ public Object put(final String name, final Double value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(-1);
+ p.setType(Variant.VT_R8);
+ p.setValue(value);
+ final CustomProperty cp = new CustomProperty(p, name);
+ return put(cp);
+ }
+
+ /**
+ *
Adds a named integer property.
+ *
+ * @param name The property's name.
+ * @param value The property's value.
+ * @return the property that was stored under the specified name before, or
+ * null if there was no such property before.
+ */
+ public Object put(final String name, final Integer value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(-1);
+ p.setType(Variant.VT_I4);
+ p.setValue(value);
+ final CustomProperty cp = new CustomProperty(p, name);
+ return put(cp);
+ }
+
+ /**
+ *
Adds a named boolean property.
+ *
+ * @param name The property's name.
+ * @param value The property's value.
+ * @return the property that was stored under the specified name before, or
+ * null if there was no such property before.
+ */
+ public Object put(final String name, final Boolean value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(-1);
+ p.setType(Variant.VT_BOOL);
+ p.setValue(value);
+ final CustomProperty cp = new CustomProperty(p, name);
+ return put(cp);
+ }
+
+
+ /**
+ *
Gets a named value from the custom properties.
+ *
+ * @param name the name of the value to get
+ * @return the value or null if a value with the specified
+ * name is not found in the custom properties.
+ */
+ public Object get(final String name)
+ {
+ final Long id = dictionaryNameToID.get(name);
+ final CustomProperty cp = super.get(id);
+ return cp != null ? cp.getValue() : null;
+ }
+
+
+
+ /**
+ *
Adds a named date property.
+ *
+ * @param name The property's name.
+ * @param value The property's value.
+ * @return the property that was stored under the specified name before, or
+ * null if there was no such property before.
+ */
+ public Object put(final String name, final Date value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(-1);
+ p.setType(Variant.VT_FILETIME);
+ p.setValue(value);
+ final CustomProperty cp = new CustomProperty(p, name);
+ return put(cp);
+ }
+
+ /**
+ * Returns a set of all the names of our custom properties.
+ * Equivalent to {@link #nameSet()}
+ *
+ * @return a set of all the names of our custom properties
+ */
+ @Override
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public Set keySet() {
+ return dictionaryNameToID.keySet();
+ }
+
+ /**
+ * Returns a set of all the names of our custom properties
+ *
+ * @return a set of all the names of our custom properties
+ */
+ public Set nameSet() {
+ return dictionaryNameToID.keySet();
+ }
+
+ /**
+ * Returns a set of all the IDs of our custom properties
+ *
+ * @return a set of all the IDs of our custom properties
+ */
+ public Set idSet() {
+ return dictionaryNameToID.keySet();
+ }
+
+
+ /**
+ *
Sets the codepage.
+ *
+ * @param codepage the codepage
+ */
+ public void setCodepage(final int codepage)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(PropertyIDMap.PID_CODEPAGE);
+ p.setType(Variant.VT_I2);
+ p.setValue(Integer.valueOf(codepage));
+ put(new CustomProperty(p));
+ }
+
+
+
+ /**
+ *
Gets the dictionary which contains IDs and names of the named custom
+ * properties.
+ *
+ * @return the dictionary.
+ */
+ Map getDictionary()
+ {
+ return dictionaryIDToName;
+ }
+
+
+ /**
+ * Checks against both String Name and Long ID
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ if(key instanceof Long) {
+ return super.containsKey(key);
+ }
+ if(key instanceof String) {
+ return super.containsKey(dictionaryNameToID.get(key));
+ }
+ return false;
+ }
+
+ /**
+ * Checks against both the property, and its values.
+ */
+ @Override
+ public boolean containsValue(Object value) {
+ if(value instanceof CustomProperty) {
+ return super.containsValue(value);
+ } else {
+ for(CustomProperty cp : super.values()) {
+ if(cp.getValue() == value) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+
+ /**
+ *
Gets the codepage.
+ *
+ * @return the codepage or -1 if the codepage is undefined.
+ */
+ public int getCodepage()
+ {
+ int codepage = -1;
+ for (final Iterator i = this.values().iterator(); codepage == -1 && i.hasNext();)
+ {
+ final CustomProperty cp = i.next();
+ if (cp.getID() == PropertyIDMap.PID_CODEPAGE)
+ codepage = ((Integer) cp.getValue()).intValue();
+ }
+ return codepage;
+ }
+
+
+
+ /**
+ *
Tells whether this {@link CustomProperties} instance is pure or one or
+ * more properties of the underlying low-level property set has been
+ * dropped.
+ *
+ * @return true if the {@link CustomProperties} is pure, else
+ * false.
+ */
+ public boolean isPure()
+ {
+ return isPure;
+ }
+
+ /**
+ *
Sets the purity of the custom property set.
+ *
+ * @param isPure the purity
+ */
+ public void setPure(final boolean isPure)
+ {
+ this.isPure = isPure;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/CustomProperty.java b/trunk/src/java/org/apache/poi/hpsf/CustomProperty.java
new file mode 100644
index 000000000..a256d94d3
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/CustomProperty.java
@@ -0,0 +1,124 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This class represents custom properties in the document summary
+ * information stream. The difference to normal properties is that custom
+ * properties have an optional name. If the name is not null it
+ * will be maintained in the section's dictionary.
+ *
+ * @param name The name to set.
+ */
+ public void setName(final String name)
+ {
+ this.name = name;
+ }
+
+
+ /**
+ *
Compares two custom properties for equality. The method returns
+ * true if all attributes of the two custom properties are
+ * equal.
+ *
+ * @param o The custom property to compare with.
+ * @return true if both custom properties are equal, else
+ * false.
+ *
+ * @see java.util.AbstractSet#equals(java.lang.Object)
+ */
+ public boolean equalsContents(final Object o)
+ {
+ final CustomProperty c = (CustomProperty) o;
+ final String name1 = c.getName();
+ final String name2 = this.getName();
+ boolean equalNames = true;
+ if (name1 == null)
+ equalNames = name2 == null;
+ else
+ equalNames = name1.equals(name2);
+ return equalNames && c.getID() == this.getID()
+ && c.getType() == this.getType()
+ && c.getValue().equals(this.getValue());
+ }
+
+ /**
+ * @see java.util.AbstractSet#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ return (int) this.getID();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof CustomProperty) ? equalsContents(o) : false;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Date.java b/trunk/src/java/org/apache/poi/hpsf/Date.java
new file mode 100644
index 000000000..237420fcc
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Date.java
@@ -0,0 +1,33 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Date
+{
+ static final int SIZE = 8;
+
+ private byte[] _value;
+
+ Date( byte[] data, int offset )
+ {
+ _value = LittleEndian.getByteArray( data, offset, SIZE );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Decimal.java b/trunk/src/java/org/apache/poi/hpsf/Decimal.java
new file mode 100644
index 000000000..d8c404c31
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Decimal.java
@@ -0,0 +1,55 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Decimal
+{
+ static final int SIZE = 16;
+
+ /**
+ * Findbugs: UNR_UNREAD_FIELD
+ */
+ private short field_1_wReserved;
+ private byte field_2_scale;
+ private byte field_3_sign;
+ private int field_4_hi32;
+ private long field_5_lo64;
+
+ Decimal( final byte[] data, final int startOffset )
+ {
+ int offset = startOffset;
+
+ field_1_wReserved = LittleEndian.getShort( data, offset );
+ offset += LittleEndian.SHORT_SIZE;
+
+ field_2_scale = data[offset];
+ offset += LittleEndian.BYTE_SIZE;
+
+ field_3_sign = data[offset];
+ offset += LittleEndian.BYTE_SIZE;
+
+ field_4_hi32 = LittleEndian.getInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ field_5_lo64 = LittleEndian.getLong( data, offset );
+ offset += LittleEndian.LONG_SIZE;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java b/trunk/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java
new file mode 100644
index 000000000..0f8c629cf
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/DocumentSummaryInformation.java
@@ -0,0 +1,928 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.hpsf.wellknown.SectionIDMap;
+import org.apache.poi.util.CodePageUtil;
+
+/**
+ *
Convenience class representing a DocumentSummary Information stream in a
+ * Microsoft Office document.
The document name a document summary information stream
+ * usually has in a POIFS filesystem.
+ */
+ public static final String DEFAULT_STREAM_NAME =
+ "\005DocumentSummaryInformation";
+
+ @Override
+ public PropertyIDMap getPropertySetIDMap() {
+ return PropertyIDMap.getDocumentSummaryInformationProperties();
+ }
+
+
+ /**
+ *
Creates a {@link DocumentSummaryInformation} from a given
+ * {@link PropertySet}.
+ *
+ * @param ps A property set which should be created from a
+ * document summary information stream.
+ * @throws UnexpectedPropertySetTypeException if ps
+ * does not contain a document summary information stream.
+ */
+ public DocumentSummaryInformation(final PropertySet ps)
+ throws UnexpectedPropertySetTypeException
+ {
+ super(ps);
+ if (!isDocumentSummaryInformation())
+ throw new UnexpectedPropertySetTypeException
+ ("Not a " + getClass().getName());
+ }
+
+
+ /**
+ *
Returns the category (or {@code null}).
+ *
+ * @return The category value
+ */
+ public String getCategory()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_CATEGORY);
+ }
+
+ /**
+ *
Sets the category.
+ *
+ * @param category The category to set.
+ */
+ public void setCategory(final String category)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_CATEGORY, category);
+ }
+
+ /**
+ *
Removes the category.
+ */
+ public void removeCategory()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_CATEGORY);
+ }
+
+
+
+ /**
+ *
Returns the presentation format (or
+ * {@code null}).
+ *
+ * @return The presentation format value
+ */
+ public String getPresentationFormat()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_PRESFORMAT);
+ }
+
+ /**
+ *
Sets the presentation format.
+ *
+ * @param presentationFormat The presentation format to set.
+ */
+ public void setPresentationFormat(final String presentationFormat)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_PRESFORMAT, presentationFormat);
+ }
+
+ /**
+ *
Removes the presentation format.
+ */
+ public void removePresentationFormat()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_PRESFORMAT);
+ }
+
+
+
+ /**
+ *
Returns the byte count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a byte count.
+ *
+ * @return The byteCount value
+ */
+ public int getByteCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_BYTECOUNT);
+ }
+
+ /**
+ *
Sets the byte count.
+ *
+ * @param byteCount The byte count to set.
+ */
+ public void setByteCount(final int byteCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_BYTECOUNT, byteCount);
+ }
+
+ /**
+ *
Removes the byte count.
+ */
+ public void removeByteCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_BYTECOUNT);
+ }
+
+
+
+ /**
+ *
Returns the line count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a line count.
+ *
+ * @return The line count value
+ */
+ public int getLineCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_LINECOUNT);
+ }
+
+ /**
+ *
Sets the line count.
+ *
+ * @param lineCount The line count to set.
+ */
+ public void setLineCount(final int lineCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_LINECOUNT, lineCount);
+ }
+
+ /**
+ *
Removes the line count.
+ */
+ public void removeLineCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_LINECOUNT);
+ }
+
+
+
+ /**
+ *
Returns the par count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a par count.
+ *
+ * @return The par count value
+ */
+ public int getParCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_PARCOUNT);
+ }
+
+ /**
+ *
Sets the par count.
+ *
+ * @param parCount The par count to set.
+ */
+ public void setParCount(final int parCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_PARCOUNT, parCount);
+ }
+
+ /**
+ *
Removes the par count.
+ */
+ public void removeParCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_PARCOUNT);
+ }
+
+
+
+ /**
+ *
Returns the slide count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a slide count.
+ *
+ * @return The slide count value
+ */
+ public int getSlideCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_SLIDECOUNT);
+ }
+
+ /**
+ *
Sets the slideCount.
+ *
+ * @param slideCount The slide count to set.
+ */
+ public void setSlideCount(final int slideCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_SLIDECOUNT, slideCount);
+ }
+
+ /**
+ *
Removes the slide count.
+ */
+ public void removeSlideCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_SLIDECOUNT);
+ }
+
+
+
+ /**
+ *
Returns the note count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a note count.
+ *
+ * @return The note count value
+ */
+ public int getNoteCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_NOTECOUNT);
+ }
+
+ /**
+ *
Sets the note count.
+ *
+ * @param noteCount The note count to set.
+ */
+ public void setNoteCount(final int noteCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_NOTECOUNT, noteCount);
+ }
+
+ /**
+ *
Removes the noteCount.
+ */
+ public void removeNoteCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_NOTECOUNT);
+ }
+
+
+
+ /**
+ *
Returns the hidden count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a hidden
+ * count.
+ *
+ * @return The hidden count value
+ */
+ public int getHiddenCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_HIDDENCOUNT);
+ }
+
+ /**
+ *
Sets the hidden count.
+ *
+ * @param hiddenCount The hidden count to set.
+ */
+ public void setHiddenCount(final int hiddenCount)
+ {
+ final MutableSection s = (MutableSection) getSections().get(0);
+ s.setProperty(PropertyIDMap.PID_HIDDENCOUNT, hiddenCount);
+ }
+
+ /**
+ *
Removes the hidden count.
+ */
+ public void removeHiddenCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_HIDDENCOUNT);
+ }
+
+
+
+ /**
+ *
Returns the mmclip count or 0 if the {@link
+ * DocumentSummaryInformation} does not contain a mmclip
+ * count.
+ *
+ * @return The mmclip count value
+ */
+ public int getMMClipCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_MMCLIPCOUNT);
+ }
+
+ /**
+ *
Sets the mmclip count.
+ *
+ * @param mmClipCount The mmclip count to set.
+ */
+ public void setMMClipCount(final int mmClipCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_MMCLIPCOUNT, mmClipCount);
+ }
+
+ /**
+ *
Removes the mmclip count.
+ */
+ public void removeMMClipCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_MMCLIPCOUNT);
+ }
+
+
+
+ /**
+ *
Returns true when scaling of the thumbnail is
+ * desired, false if cropping is desired.
+ *
+ * @return The scale value
+ */
+ public boolean getScale()
+ {
+ return getPropertyBooleanValue(PropertyIDMap.PID_SCALE);
+ }
+
+ /**
+ *
Sets the scale.
+ *
+ * @param scale The scale to set.
+ */
+ public void setScale(final boolean scale)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_SCALE, scale);
+ }
+
+ /**
+ *
Removes the scale.
+ */
+ public void removeScale()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_SCALE);
+ }
+
+
+
+ /**
+ *
Returns the heading pair (or {@code null})
+ * when this method is implemented. Please note that the
+ * return type is likely to change!
+ *
+ * @return The heading pair value
+ */
+ public byte[] getHeadingPair()
+ {
+ notYetImplemented("Reading byte arrays ");
+ return (byte[]) getProperty(PropertyIDMap.PID_HEADINGPAIR);
+ }
+
+ /**
+ *
+ */
+ public void removeHeadingPair()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_HEADINGPAIR);
+ }
+
+
+
+ /**
+ *
Returns the doc parts (or {@code null})
+ * when this method is implemented. Please note that the
+ * return type is likely to change!
+ *
+ * @return The doc parts value
+ */
+ public byte[] getDocparts()
+ {
+ notYetImplemented("Reading byte arrays");
+ return (byte[]) getProperty(PropertyIDMap.PID_DOCPARTS);
+ }
+
+
+
+ /**
+ *
Sets the doc parts.
+ *
+ * @param docparts The doc parts to set.
+ */
+ public void setDocparts(final byte[] docparts)
+ {
+ notYetImplemented("Writing byte arrays");
+ }
+
+ /**
+ *
Removes the doc parts.
+ */
+ public void removeDocparts()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_DOCPARTS);
+ }
+
+
+
+ /**
+ *
Returns the manager (or {@code null}).
+ *
+ * @return The manager value
+ */
+ public String getManager()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_MANAGER);
+ }
+
+ /**
+ *
Sets the manager.
+ *
+ * @param manager The manager to set.
+ */
+ public void setManager(final String manager)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_MANAGER, manager);
+ }
+
+ /**
+ *
Removes the manager.
+ */
+ public void removeManager()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_MANAGER);
+ }
+
+
+
+ /**
+ *
Returns the company (or {@code null}).
+ *
+ * @return The company value
+ */
+ public String getCompany()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_COMPANY);
+ }
+
+ /**
+ *
Sets the company.
+ *
+ * @param company The company to set.
+ */
+ public void setCompany(final String company)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_COMPANY, company);
+ }
+
+ /**
+ *
Removes the company.
+ */
+ public void removeCompany()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_COMPANY);
+ }
+
+
+ /**
+ *
Returns true if the custom links are dirty.
+ *
+ * @return The links dirty value
+ */
+ public boolean getLinksDirty()
+ {
+ return getPropertyBooleanValue(PropertyIDMap.PID_LINKSDIRTY);
+ }
+
+ /**
+ *
Sets the linksDirty.
+ *
+ * @param linksDirty The links dirty value to set.
+ */
+ public void setLinksDirty(final boolean linksDirty)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_LINKSDIRTY, linksDirty);
+ }
+
+ /**
+ *
Removes the links dirty.
+ */
+ public void removeLinksDirty()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_LINKSDIRTY);
+ }
+
+
+ /**
+ *
Returns the character count including whitespace, or 0 if the
+ * {@link DocumentSummaryInformation} does not contain this char count.
+ *
This is the whitespace-including version of {@link SummaryInformation#getCharCount()}
+ *
+ * @return The character count or {@code null}
+ */
+ public int getCharCountWithSpaces()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_CCHWITHSPACES);
+ }
+
+ /**
+ * Sets the character count including whitespace
+ *
+ * @param count The character count to set.
+ */
+ public void setCharCountWithSpaces(int count)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_CCHWITHSPACES, count);
+ }
+
+ /**
+ * Removes the character count
+ */
+ public void removeCharCountWithSpaces()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_CCHWITHSPACES);
+ }
+
+
+ /**
+ * Get if the User Defined Property Set has been updated outside of the
+ * Application.
+ * If it has (true), the hyperlinks should be updated on document load.
+ *
+ * @return true, if the hyperlinks should be updated on document load
+ */
+ public boolean getHyperlinksChanged()
+ {
+ return getPropertyBooleanValue(PropertyIDMap.PID_HYPERLINKSCHANGED);
+ }
+
+ /**
+ * Set the flag for if the User Defined Property Set has been updated outside
+ * of the Application.
+ *
+ * @param changed true, if the User Defined Property Set has been updated
+ */
+ public void setHyperlinksChanged(boolean changed)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_HYPERLINKSCHANGED, changed);
+ }
+
+ /**
+ * Removes the flag for if the User Defined Property Set has been updated
+ * outside of the Application.
+ */
+ public void removeHyperlinksChanged()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_HYPERLINKSCHANGED);
+ }
+
+
+ /**
+ * Gets the version of the Application which wrote the
+ * Property set, stored with the two high order bytes having the major
+ * version number, and the two low order bytes the minor version number.
+ * This will be 0 if no version is set.
+ *
+ * @return the Application version
+ */
+ public int getApplicationVersion()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_VERSION);
+ }
+
+ /**
+ * Sets the Application version, which must be a 4 byte int with
+ * the two high order bytes having the major version number, and the
+ * two low order bytes the minor version number.
+ *
+ * @param version the Application version
+ */
+ public void setApplicationVersion(int version)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_VERSION, version);
+ }
+
+ /**
+ * Removes the Application Version
+ */
+ public void removeApplicationVersion()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_VERSION);
+ }
+
+
+ /**
+ * Returns the VBA digital signature for the VBA project
+ * embedded in the document (or {@code null}).
+ *
+ * @return the VBA digital signature
+ */
+ public byte[] getVBADigitalSignature()
+ {
+ Object value = getProperty(PropertyIDMap.PID_DIGSIG);
+ if (value != null && value instanceof byte[]) {
+ return (byte[])value;
+ }
+ return null;
+ }
+
+ /**
+ *
Sets the VBA digital signature for the VBA project
+ * embedded in the document.
+ *
+ * @param signature VBA Digital Signature for the project
+ */
+ public void setVBADigitalSignature(byte[] signature)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_DIGSIG, signature);
+ }
+
+ /**
+ * Removes the VBA Digital Signature
+ */
+ public void removeVBADigitalSignature()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_DIGSIG);
+ }
+
+
+ /**
+ * Gets the content type of the file (or {@code null}).
+ *
+ * @return the content type of the file
+ */
+ public String getContentType()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_CONTENTTYPE);
+ }
+
+ /**
+ * Sets the content type of the file
+ *
+ * @param type the content type of the file
+ */
+ public void setContentType(String type)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_CONTENTTYPE, type);
+ }
+
+ /**
+ * Removes the content type of the file
+ */
+ public void removeContentType()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_CONTENTTYPE);
+ }
+
+
+ /**
+ * Gets the content status of the file (or {@code null}).
+ *
+ * @return the content status of the file
+ */
+ public String getContentStatus()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_CONTENTSTATUS);
+ }
+
+ /**
+ * Sets the content status of the file
+ *
+ * @param status the content status of the file
+ */
+ public void setContentStatus(String status)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_CONTENTSTATUS, status);
+ }
+
+ /**
+ * Removes the content status of the file
+ */
+ public void removeContentStatus()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_CONTENTSTATUS);
+ }
+
+
+ /**
+ * Gets the document language, which is normally unset and empty (or {@code null}).
+ *
+ * @return the document language
+ */
+ public String getLanguage()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_LANGUAGE);
+ }
+
+ /**
+ * Set the document language
+ *
+ * @param language the document language
+ */
+ public void setLanguage(String language)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_LANGUAGE, language);
+ }
+
+ /**
+ * Removes the document language
+ */
+ public void removeLanguage()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_LANGUAGE);
+ }
+
+
+ /**
+ *
Gets the document version as a string, which is normally unset and empty
+ * (or {@code null}).
+ *
+ * @return the document verion
+ */
+ public String getDocumentVersion()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_DOCVERSION);
+ }
+
+ /**
+ * Sets the document version string
+ *
+ * @param version the document version string
+ */
+ public void setDocumentVersion(String version)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_DOCVERSION, version);
+ }
+
+ /**
+ * Removes the document version string
+ */
+ public void removeDocumentVersion()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_DOCVERSION);
+ }
+
+
+ /**
+ *
Gets the custom properties.
+ *
+ * @return The custom properties.
+ */
+ public CustomProperties getCustomProperties()
+ {
+ CustomProperties cps = null;
+ if (getSectionCount() >= 2)
+ {
+ cps = new CustomProperties();
+ final Section section = getSections().get(1);
+ final Map dictionary = section.getDictionary();
+ final Property[] properties = section.getProperties();
+ int propertyCount = 0;
+ for (int i = 0; i < properties.length; i++)
+ {
+ final Property p = properties[i];
+ final long id = p.getID();
+ if (id != 0 && id != 1)
+ {
+ propertyCount++;
+ final CustomProperty cp = new CustomProperty(p,
+ dictionary.get(Long.valueOf(id)));
+ cps.put(cp.getName(), cp);
+ }
+ }
+ if (cps.size() != propertyCount)
+ cps.setPure(false);
+ }
+ return cps;
+ }
+
+ /**
+ *
Sets the custom properties.
+ *
+ * @param customProperties The custom properties
+ */
+ public void setCustomProperties(final CustomProperties customProperties)
+ {
+ ensureSection2();
+ final MutableSection section = (MutableSection) getSections().get(1);
+ final Map dictionary = customProperties.getDictionary();
+ section.clear();
+
+ /* Set the codepage. If both custom properties and section have a
+ * codepage, the codepage from the custom properties wins, else take the
+ * one that is defined. If none is defined, take Unicode. */
+ int cpCodepage = customProperties.getCodepage();
+ if (cpCodepage < 0)
+ cpCodepage = section.getCodepage();
+ if (cpCodepage < 0)
+ cpCodepage = CodePageUtil.CP_UNICODE;
+ customProperties.setCodepage(cpCodepage);
+ section.setCodepage(cpCodepage);
+ section.setDictionary(dictionary);
+ for (final Iterator i = customProperties.values().iterator(); i.hasNext();)
+ {
+ final Property p = i.next();
+ section.setProperty(p);
+ }
+ }
+
+ /**
+ *
+ */
+ public void removeCustomProperties()
+ {
+ if (getSectionCount() >= 2)
+ getSections().remove(1);
+ else
+ throw new HPSFRuntimeException("Illegal internal format of Document SummaryInformation stream: second section is missing.");
+ }
+
+
+ /**
+ *
Throws an {@link UnsupportedOperationException} with a message text
+ * telling which functionality is not yet implemented.
+ *
+ * @param msg text telling was leaves to be implemented, e.g.
+ * "Reading byte arrays".
+ */
+ private void notYetImplemented(final String msg)
+ {
+ throw new UnsupportedOperationException(msg + " is not yet implemented.");
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Filetime.java b/trunk/src/java/org/apache/poi/hpsf/Filetime.java
new file mode 100644
index 000000000..84a58bb1d
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Filetime.java
@@ -0,0 +1,70 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.util.LittleEndian;
+
+class Filetime
+{
+ static final int SIZE = LittleEndian.INT_SIZE * 2;
+
+ private int _dwHighDateTime;
+ private int _dwLowDateTime;
+
+ Filetime( byte[] data, int offset )
+ {
+ _dwLowDateTime = LittleEndian.getInt( data, offset + 0
+ * LittleEndian.INT_SIZE );
+ _dwHighDateTime = LittleEndian.getInt( data, offset + 1
+ * LittleEndian.INT_SIZE );
+ }
+
+ Filetime( int low, int high )
+ {
+ _dwLowDateTime = low;
+ _dwHighDateTime = high;
+ }
+
+ long getHigh()
+ {
+ return _dwHighDateTime;
+ }
+
+ long getLow()
+ {
+ return _dwLowDateTime;
+ }
+
+ byte[] toByteArray()
+ {
+ byte[] result = new byte[SIZE];
+ LittleEndian.putInt( result, 0 * LittleEndian.INT_SIZE, _dwLowDateTime );
+ LittleEndian
+ .putInt( result, 1 * LittleEndian.INT_SIZE, _dwHighDateTime );
+ return result;
+ }
+
+ int write( OutputStream out ) throws IOException
+ {
+ LittleEndian.putInt( _dwLowDateTime, out );
+ LittleEndian.putInt( _dwHighDateTime, out );
+ return SIZE;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/GUID.java b/trunk/src/java/org/apache/poi/hpsf/GUID.java
new file mode 100644
index 000000000..4bbd7f341
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/GUID.java
@@ -0,0 +1,39 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class GUID
+{
+ static final int SIZE = 16;
+
+ private int _data1;
+ private short _data2;
+ private short _data3;
+ private long _data4;
+
+ GUID( byte[] data, int offset )
+ {
+ _data1 = LittleEndian.getInt( data, offset + 0 );
+ _data2 = LittleEndian.getShort( data, offset + 4 );
+ _data3 = LittleEndian.getShort( data, offset + 6 );
+ _data4 = LittleEndian.getLong( data, offset + 8 );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/HPSFException.java b/trunk/src/java/org/apache/poi/hpsf/HPSFException.java
new file mode 100644
index 000000000..abc3b8d9f
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/HPSFException.java
@@ -0,0 +1,100 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is the superclass of all other checked exceptions thrown
+ * in this package. It supports a nested "reason" throwable, i.e. an exception
+ * that caused this one to be thrown.
Creates a new {@link HPSFException} with a reason.
+ *
+ * @param reason The reason, i.e. a throwable that indirectly
+ * caused this exception.
+ */
+ public HPSFException(final Throwable reason)
+ {
+ super();
+ this.reason = reason;
+ }
+
+
+
+ /**
+ *
Creates an {@link HPSFException} with a message string and a
+ * reason.
+ *
+ * @param msg The message string.
+ * @param reason The reason, i.e. a throwable that indirectly
+ * caused this exception.
+ */
+ public HPSFException(final String msg, final Throwable reason)
+ {
+ super(msg);
+ this.reason = reason;
+ }
+
+
+
+ /**
+ *
Returns the {@link Throwable} that caused this exception to
+ * be thrown or null if there was no such {@link
+ * Throwable}.
+ *
+ * @return The reason
+ */
+ public Throwable getReason()
+ {
+ return reason;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/HPSFPropertiesOnlyDocument.java b/trunk/src/java/org/apache/poi/hpsf/HPSFPropertiesOnlyDocument.java
new file mode 100644
index 000000000..78ee5512b
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/HPSFPropertiesOnlyDocument.java
@@ -0,0 +1,95 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.POIDocument;
+import org.apache.poi.poifs.filesystem.EntryUtils;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+/**
+ * A version of {@link POIDocument} which allows access to the
+ * HPSF Properties, but no other document contents.
+ * Normally used when you want to read or alter the Document Properties,
+ * without affecting the rest of the file
+ */
+public class HPSFPropertiesOnlyDocument extends POIDocument {
+ public HPSFPropertiesOnlyDocument(NPOIFSFileSystem fs) {
+ super(fs.getRoot());
+ }
+ public HPSFPropertiesOnlyDocument(OPOIFSFileSystem fs) {
+ super(fs);
+ }
+ public HPSFPropertiesOnlyDocument(POIFSFileSystem fs) {
+ super(fs);
+ }
+
+ /**
+ * Write out to the currently open file the properties changes, but nothing else
+ */
+ public void write() throws IOException {
+ NPOIFSFileSystem fs = directory.getFileSystem();
+
+ validateInPlaceWritePossible();
+ writeProperties(fs, null);
+ fs.writeFilesystem();
+ }
+ /**
+ * Write out, with any properties changes, but nothing else
+ */
+ public void write(File newFile) throws IOException {
+ POIFSFileSystem fs = POIFSFileSystem.create(newFile);
+ try {
+ write(fs);
+ fs.writeFilesystem();
+ } finally {
+ fs.close();
+ }
+ }
+ /**
+ * Write out, with any properties changes, but nothing else
+ */
+ public void write(OutputStream out) throws IOException {
+ NPOIFSFileSystem fs = new NPOIFSFileSystem();
+ try {
+ write(fs);
+ fs.writeFilesystem(out);
+ } finally {
+ fs.close();
+ }
+ }
+
+ private void write(NPOIFSFileSystem fs) throws IOException {
+ // For tracking what we've written out, so far
+ List excepts = new ArrayList(2);
+
+ // Write out our HPFS properties, with any changes
+ writeProperties(fs, excepts);
+
+ // Copy over everything else unchanged
+ EntryUtils.copyNodes(directory, fs.getRoot(), excepts);
+
+ // Caller will save the resultant POIFSFileSystem to the stream/file
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java b/trunk/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java
new file mode 100644
index 000000000..984a316fc
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/HPSFRuntimeException.java
@@ -0,0 +1,100 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+
+/**
+ *
This exception is the superclass of all other unchecked
+ * exceptions thrown in this package. It supports a nested "reason"
+ * throwable, i.e. an exception that caused this one to be thrown.
+ */
+public class HPSFRuntimeException extends RuntimeException
+{
+ private static final long serialVersionUID = -7804271670232727159L;
+ /**
The underlying reason for this exception - may be
+ * null.
Creates a new {@link HPSFRuntimeException} with a
+ * reason.
+ *
+ * @param reason The reason, i.e. a throwable that indirectly
+ * caused this exception.
+ */
+ public HPSFRuntimeException(final Throwable reason)
+ {
+ super();
+ this.reason = reason;
+ }
+
+
+
+ /**
+ *
Creates a new {@link HPSFRuntimeException} with a message
+ * string and a reason.
+ *
+ * @param msg The message string.
+ * @param reason The reason, i.e. a throwable that indirectly
+ * caused this exception.
+ */
+ public HPSFRuntimeException(final String msg, final Throwable reason)
+ {
+ super(msg);
+ this.reason = reason;
+ }
+
+
+
+ /**
+ *
Returns the {@link Throwable} that caused this exception to
+ * be thrown or null if there was no such {@link
+ * Throwable}.
+ *
+ * @return The reason
+ */
+ public Throwable getReason()
+ {
+ return reason;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/IllegalPropertySetDataException.java b/trunk/src/java/org/apache/poi/hpsf/IllegalPropertySetDataException.java
new file mode 100644
index 000000000..3217af958
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/IllegalPropertySetDataException.java
@@ -0,0 +1,79 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown when there is an illegal value set in a
+ * {@link PropertySet}. For example, a {@link Variant#VT_BOOL} must
+ * have a value of -1 (true) or 0 (false).
+ * Any other value would trigger this exception. It supports a nested
+ * "reason" throwable, i.e. an exception that caused this one to be
+ * thrown.
+ *
+ * @param msg The exception's message string
+ * @param reason This exception's underlying reason
+ */
+ public IllegalPropertySetDataException(final String msg,
+ final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/IllegalVariantTypeException.java b/trunk/src/java/org/apache/poi/hpsf/IllegalVariantTypeException.java
new file mode 100644
index 000000000..b71ff4703
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/IllegalVariantTypeException.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.HexDump;
+
+/**
+ *
This exception is thrown if HPSF encounters a variant type that is illegal
+ * in the current context.
+ *
+ * @param variantType The unsupported variant type
+ * @param value The value
+ * @param msg A message string
+ */
+ public IllegalVariantTypeException(final long variantType,
+ final Object value, final String msg)
+ {
+ super(variantType, value, msg);
+ }
+
+ /**
+ *
Constructor
+ *
+ * @param variantType The unsupported variant type
+ * @param value The value
+ */
+ public IllegalVariantTypeException(final long variantType,
+ final Object value)
+ {
+ this(variantType, value, "The variant type " + variantType + " (" +
+ Variant.getVariantName(variantType) + ", " +
+ HexDump.toHex(variantType) + ") is illegal in this context.");
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/IndirectPropertyName.java b/trunk/src/java/org/apache/poi/hpsf/IndirectPropertyName.java
new file mode 100644
index 000000000..fc09ca6e8
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/IndirectPropertyName.java
@@ -0,0 +1,35 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class IndirectPropertyName
+{
+ private CodePageString _value;
+
+ IndirectPropertyName( byte[] data, int offset ) //NOSONAR
+ {
+ _value = new CodePageString( data, offset );
+ }
+
+ int getSize()
+ {
+ return _value.getSize();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java b/trunk/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java
new file mode 100644
index 000000000..450c892ab
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/MarkUnsupportedException.java
@@ -0,0 +1,69 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if an {@link java.io.InputStream} does
+ * not support the {@link java.io.InputStream#mark} operation.
+ *
+ * @param msg The exception's message string
+ * @param reason This exception's underlying reason
+ */
+ public MarkUnsupportedException(final String msg, final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/MissingSectionException.java b/trunk/src/java/org/apache/poi/hpsf/MissingSectionException.java
new file mode 100644
index 000000000..f3e81cad2
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/MissingSectionException.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if one of the {@link PropertySet}'s
+ * convenience methods does not find a required {@link Section}.
+ *
+ *
The constructors of this class are analogous to those of its
+ * superclass and documented there.
+ *
+ * @param msg The exception's message string
+ * @param reason This exception's underlying reason
+ */
+ public MissingSectionException(final String msg, final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/MutableProperty.java b/trunk/src/java/org/apache/poi/hpsf/MutableProperty.java
new file mode 100644
index 000000000..9d77c0dd0
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/MutableProperty.java
@@ -0,0 +1,119 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.util.CodePageUtil;
+
+/**
+ *
Adds writing capability to the {@link Property} class.
+ *
+ *
Please be aware that this class' functionality will be merged into the
+ * {@link Property} class at a later time, so the API will change.
Creates a MutableProperty as a copy of an existing
+ * Property.
+ *
+ * @param p The property to copy.
+ */
+ public MutableProperty(final Property p)
+ {
+ setID(p.getID());
+ setType(p.getType());
+ setValue(p.getValue());
+ }
+
+
+ /**
+ *
Sets the property's ID.
+ *
+ * @param id the ID
+ */
+ public void setID(final long id)
+ {
+ this.id = id;
+ }
+
+
+
+ /**
+ *
Sets the property's type.
+ *
+ * @param type the property's type
+ */
+ public void setType(final long type)
+ {
+ this.type = type;
+ }
+
+
+
+ /**
+ *
Sets the property's value.
+ *
+ * @param value the property's value
+ */
+ public void setValue(final Object value)
+ {
+ this.value = value;
+ }
+
+
+
+ /**
+ *
Writes the property to an output stream.
+ *
+ * @param out The output stream to write to.
+ * @param codepage The codepage to use for writing non-wide strings
+ * @return the number of bytes written to the stream
+ *
+ * @exception IOException if an I/O error occurs
+ * @exception WritingNotSupportedException if a variant type is to be
+ * written that is not yet supported
+ */
+ public int write(final OutputStream out, final int codepage)
+ throws IOException, WritingNotSupportedException
+ {
+ int length = 0;
+ long variantType = getType();
+
+ /* Ensure that wide strings are written if the codepage is Unicode. */
+ if (codepage == CodePageUtil.CP_UNICODE && variantType == Variant.VT_LPSTR)
+ variantType = Variant.VT_LPWSTR;
+
+ length += TypeWriter.writeUIntToStream(out, variantType);
+ length += VariantSupport.write(out, variantType, getValue(), codepage);
+ return length;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/MutablePropertySet.java b/trunk/src/java/org/apache/poi/hpsf/MutablePropertySet.java
new file mode 100644
index 000000000..5c7d38658
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/MutablePropertySet.java
@@ -0,0 +1,304 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.LinkedList;
+
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+
+/**
+ *
Adds writing support to the {@link PropertySet} class.
+ *
+ *
Please be aware that this class' functionality will be merged into the
+ * {@link PropertySet} class at a later time, so the API will change.
Constructs a MutablePropertySet instance. Its
+ * primary task is to initialize the immutable field with their proper
+ * values. It also sets fields that might change to reasonable defaults.
+ */
+ public MutablePropertySet()
+ {
+ /* Initialize the "byteOrder" field. */
+ byteOrder = LittleEndian.getUShort(BYTE_ORDER_ASSERTION);
+
+ /* Initialize the "format" field. */
+ format = LittleEndian.getUShort(FORMAT_ASSERTION);
+
+ /* Initialize "osVersion" field as if the property has been created on
+ * a Win32 platform, whether this is the case or not. */
+ osVersion = (OS_WIN32 << 16) | 0x0A04;
+
+ /* Initailize the "classID" field. */
+ classID = new ClassID();
+
+ /* Initialize the sections. Since property set must have at least
+ * one section it is added right here. */
+ sections = new LinkedList();
+ sections.add(new MutableSection());
+ }
+
+
+
+ /**
+ *
Constructs a MutablePropertySet by doing a deep copy of
+ * an existing PropertySet. All nested elements, i.e.
+ * Sections and Property instances, will be their
+ * mutable counterparts in the new MutablePropertySet.
+ *
+ * @param ps The property set to copy
+ */
+ public MutablePropertySet(final PropertySet ps)
+ {
+ byteOrder = ps.getByteOrder();
+ format = ps.getFormat();
+ osVersion = ps.getOSVersion();
+ setClassID(ps.getClassID());
+ clearSections();
+ if (sections == null)
+ sections = new LinkedList();
+ for (final Section section : ps.getSections())
+ {
+ final MutableSection s = new MutableSection(section);
+ addSection(s);
+ }
+ }
+
+
+
+ /**
+ *
The length of the property set stream header.
+ */
+ private final static int OFFSET_HEADER =
+ BYTE_ORDER_ASSERTION.length + /* Byte order */
+ FORMAT_ASSERTION.length + /* Format */
+ LittleEndianConsts.INT_SIZE + /* OS version */
+ ClassID.LENGTH + /* Class ID */
+ LittleEndianConsts.INT_SIZE; /* Section count */
+
+
+
+ /**
+ *
Sets the "byteOrder" property.
+ *
+ * @param byteOrder the byteOrder value to set
+ */
+ public void setByteOrder(final int byteOrder)
+ {
+ this.byteOrder = byteOrder;
+ }
+
+
+
+ /**
+ *
Sets the "format" property.
+ *
+ * @param format the format value to set
+ */
+ public void setFormat(final int format)
+ {
+ this.format = format;
+ }
+
+
+
+ /**
+ *
Sets the "osVersion" property.
+ *
+ * @param osVersion the osVersion value to set
+ */
+ public void setOSVersion(final int osVersion)
+ {
+ this.osVersion = osVersion;
+ }
+
+
+
+ /**
+ *
Sets the property set stream's low-level "class ID"
+ * field.
+ *
+ * @param section The {@link Section} to add. It will be appended
+ * after any sections that are already present in the property set
+ * and thus become the last section.
+ */
+ public void addSection(final Section section)
+ {
+ if (sections == null)
+ sections = new LinkedList();
+ sections.add(section);
+ }
+
+
+
+ /**
+ *
Writes the property set to an output stream.
+ *
+ * @param out the output stream to write the section to
+ * @exception IOException if an error when writing to the output stream
+ * occurs
+ * @exception WritingNotSupportedException if HPSF does not yet support
+ * writing a property's variant type.
+ */
+ public void write(final OutputStream out)
+ throws WritingNotSupportedException, IOException
+ {
+ /* Write the number of sections in this property set stream. */
+ final int nrSections = sections.size();
+
+ /* Write the property set's header. */
+ TypeWriter.writeToStream(out, (short) getByteOrder());
+ TypeWriter.writeToStream(out, (short) getFormat());
+ TypeWriter.writeToStream(out, getOSVersion());
+ TypeWriter.writeToStream(out, getClassID());
+ TypeWriter.writeToStream(out, nrSections);
+ int offset = OFFSET_HEADER;
+
+ /* Write the section list, i.e. the references to the sections. Each
+ * entry in the section list consist of the section's class ID and the
+ * section's offset relative to the beginning of the stream. */
+ offset += nrSections * (ClassID.LENGTH + LittleEndianConsts.INT_SIZE);
+ final int sectionsBegin = offset;
+ for (final Section section : sections)
+ {
+ final MutableSection s = (MutableSection)section;
+ final ClassID formatID = s.getFormatID();
+ if (formatID == null)
+ throw new NoFormatIDException();
+ TypeWriter.writeToStream(out, s.getFormatID());
+ TypeWriter.writeUIntToStream(out, offset);
+ try
+ {
+ offset += s.getSize();
+ }
+ catch (HPSFRuntimeException ex)
+ {
+ final Throwable cause = ex.getReason();
+ if (cause instanceof UnsupportedEncodingException) {
+ throw new IllegalPropertySetDataException(cause);
+ }
+ throw ex;
+ }
+ }
+
+ /* Write the sections themselves. */
+ offset = sectionsBegin;
+ for (final Section section : sections)
+ {
+ final MutableSection s = (MutableSection)section;
+ offset += s.write(out);
+ }
+
+ /* Indicate that we're done */
+ out.close();
+ }
+
+
+
+ /**
+ *
Returns the contents of this property set stream as an input stream.
+ * The latter can be used for example to write the property set into a POIFS
+ * document. The input stream represents a snapshot of the property set.
+ * If the latter is modified while the input stream is still being
+ * read, the modifications will not be reflected in the input stream but in
+ * the {@link MutablePropertySet} only.
+ *
+ * @return the contents of this property set stream
+ *
+ * @throws WritingNotSupportedException if HPSF does not yet support writing
+ * of a property's variant type.
+ * @throws IOException if an I/O exception occurs.
+ */
+ public InputStream toInputStream()
+ throws IOException, WritingNotSupportedException
+ {
+ final ByteArrayOutputStream psStream = new ByteArrayOutputStream();
+ try {
+ write(psStream);
+ } finally {
+ psStream.close();
+ }
+ final byte[] streamData = psStream.toByteArray();
+ return new ByteArrayInputStream(streamData);
+ }
+
+ /**
+ *
Writes a property set to a document in a POI filesystem directory.
+ *
+ * @param dir The directory in the POI filesystem to write the document to.
+ * @param name The document's name. If there is already a document with the
+ * same name in the directory the latter will be overwritten.
+ *
+ * @throws WritingNotSupportedException if the filesystem doesn't support writing
+ * @throws IOException if the old entry can't be deleted or the new entry be written
+ */
+ public void write(final DirectoryEntry dir, final String name)
+ throws WritingNotSupportedException, IOException
+ {
+ /* If there is already an entry with the same name, remove it. */
+ try
+ {
+ final Entry e = dir.getEntry(name);
+ e.delete();
+ }
+ catch (FileNotFoundException ex)
+ {
+ /* Entry not found, no need to remove it. */
+ }
+ /* Create the new entry. */
+ dir.createDocument(name, toInputStream());
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/MutableSection.java b/trunk/src/java/org/apache/poi/hpsf/MutableSection.java
new file mode 100644
index 000000000..b0d71acf0
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/MutableSection.java
@@ -0,0 +1,699 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.util.CodePageUtil;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ *
Adds writing capability to the {@link Section} class.
+ *
+ *
Please be aware that this class' functionality will be merged into the
+ * {@link Section} class at a later time, so the API will change.
List to assemble the properties. Unfortunately a wrong
+ * decision has been taken when specifying the "properties" field
+ * as an Property[]. It should have been a {@link java.util.List}.
+ */
+ private List preprops;
+
+
+
+ /**
+ *
Contains the bytes making out the section. This byte array is
+ * established when the section's size is calculated and can be reused
+ * later. It is valid only if the "dirty" flag is false.
Constructs a MutableSection by doing a deep copy of an
+ * existing Section. All nested Property
+ * instances, will be their mutable counterparts in the new
+ * MutableSection.
+ *
+ * @param s The section set to copy
+ */
+ public MutableSection(final Section s)
+ {
+ setFormatID(s.getFormatID());
+ final Property[] pa = s.getProperties();
+ final MutableProperty[] mpa = new MutableProperty[pa.length];
+ for (int i = 0; i < pa.length; i++)
+ mpa[i] = new MutableProperty(pa[i]);
+ setProperties(mpa);
+ setDictionary(s.getDictionary());
+ }
+
+
+
+ /**
+ *
+ *
+ * @param formatID The section's format ID as a byte array. It components
+ * are in big-endian format.
+ *
+ * @see #setFormatID(ClassID)
+ * @see Section#getFormatID
+ */
+ public void setFormatID(final byte[] formatID)
+ {
+ ClassID fid = getFormatID();
+ if (fid == null)
+ {
+ fid = new ClassID();
+ setFormatID(fid);
+ }
+ fid.setBytes(formatID);
+ }
+
+
+
+ /**
+ *
Sets this section's properties. Any former values are overwritten.
+ *
+ * @param properties This section's new properties.
+ */
+ public void setProperties(final Property[] properties)
+ {
+ this.properties = properties;
+ preprops = new LinkedList();
+ for (int i = 0; i < properties.length; i++)
+ preprops.add(properties[i]);
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets the string value of the property with the specified ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value. It will be written as a Unicode
+ * string.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ */
+ public void setProperty(final int id, final String value)
+ {
+ setProperty(id, Variant.VT_LPWSTR, value);
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets the int value of the property with the specified ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ */
+ public void setProperty(final int id, final int value)
+ {
+ setProperty(id, Variant.VT_I4, Integer.valueOf(value));
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets the long value of the property with the specified ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ */
+ public void setProperty(final int id, final long value)
+ {
+ setProperty(id, Variant.VT_I8, Long.valueOf(value));
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets the boolean value of the property with the specified ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ */
+ public void setProperty(final int id, final boolean value)
+ {
+ setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets the value and the variant type of the property with the
+ * specified ID. If a property with this ID is not yet present in
+ * the section, it will be added. An already present property with
+ * the specified ID will be overwritten. A default mapping will be
+ * used to choose the property's type.
+ *
+ * @param id The property's ID.
+ * @param variantType The property's variant type.
+ * @param value The property's value.
+ *
+ * @see #setProperty(int, String)
+ * @see #getProperty
+ * @see Variant
+ */
+ public void setProperty(final int id, final long variantType,
+ final Object value)
+ {
+ final MutableProperty p = new MutableProperty();
+ p.setID(id);
+ p.setType(variantType);
+ p.setValue(value);
+ setProperty(p);
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets a property.
+ *
+ * @param p The property to be set.
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ * @see Variant
+ */
+ public void setProperty(final Property p)
+ {
+ final long id = p.getID();
+ removeProperty(id);
+ preprops.add(p);
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Removes a property.
+ *
+ * @param id The ID of the property to be removed
+ */
+ public void removeProperty(final long id)
+ {
+ for (final Iterator i = preprops.iterator(); i.hasNext();)
+ if (i.next().getID() == id)
+ {
+ i.remove();
+ break;
+ }
+ dirty = true;
+ }
+
+
+
+ /**
+ *
Sets the value of the boolean property with the specified
+ * ID.
+ *
+ * @param id The property's ID
+ * @param value The property's value
+ *
+ * @see #setProperty(int, long, Object)
+ * @see #getProperty
+ * @see Variant
+ */
+ protected void setPropertyBooleanValue(final int id, final boolean value)
+ {
+ setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
+ }
+
+
+
+ /**
+ *
Calculates the section's size. It is the sum of the lengths of the
+ * section's header (8), the properties list (16 times the number of
+ * properties) and the properties themselves.
+ *
+ * @return the section's length in bytes.
+ * @throws WritingNotSupportedException
+ * @throws IOException
+ */
+ private int calcSize() throws WritingNotSupportedException, IOException
+ {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ write(out);
+ out.close();
+ /* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
+ * shows custom properties. */
+ sectionBytes = Util.pad4(out.toByteArray());
+ return sectionBytes.length;
+ }
+
+
+
+ /**
+ *
Writes this section into an output stream.
+ *
+ *
Internally this is done by writing into three byte array output
+ * streams: one for the properties, one for the property list and one for
+ * the section as such. The two former are appended to the latter when they
+ * have received all their data.
+ *
+ * @param out The stream to write into.
+ *
+ * @return The number of bytes written, i.e. the section's size.
+ * @exception IOException if an I/O error occurs
+ * @exception WritingNotSupportedException if HPSF does not yet support
+ * writing a property's variant type.
+ */
+ public int write(final OutputStream out)
+ throws WritingNotSupportedException, IOException
+ {
+ /* Check whether we have already generated the bytes making out the
+ * section. */
+ if (!dirty && sectionBytes != null)
+ {
+ out.write(sectionBytes);
+ return sectionBytes.length;
+ }
+
+ /* The properties are written to this stream. */
+ final ByteArrayOutputStream propertyStream =
+ new ByteArrayOutputStream();
+
+ /* The property list is established here. After each property that has
+ * been written to "propertyStream", a property list entry is written to
+ * "propertyListStream". */
+ final ByteArrayOutputStream propertyListStream =
+ new ByteArrayOutputStream();
+
+ /* Maintain the current position in the list. */
+ int position = 0;
+
+ /* Increase the position variable by the size of the property list so
+ * that it points behind the property list and to the beginning of the
+ * properties themselves. */
+ position += 2 * LittleEndian.INT_SIZE +
+ getPropertyCount() * 2 * LittleEndian.INT_SIZE;
+
+ /* Writing the section's dictionary it tricky. If there is a dictionary
+ * (property 0) the codepage property (property 1) must be set, too. */
+ int codepage = -1;
+ if (getProperty(PropertyIDMap.PID_DICTIONARY) != null)
+ {
+ final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
+ if (p1 != null)
+ {
+ if (!(p1 instanceof Integer))
+ throw new IllegalPropertySetDataException
+ ("The codepage property (ID = 1) must be an " +
+ "Integer object.");
+ }
+ else
+ /* Warning: The codepage property is not set although a
+ * dictionary is present. In order to cope with this problem we
+ * add the codepage property and set it to Unicode. */
+ setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
+ Integer.valueOf(CodePageUtil.CP_UNICODE));
+ codepage = getCodepage();
+ }
+
+ /* Sort the property list by their property IDs: */
+ Collections.sort(preprops, new Comparator()
+ {
+ public int compare(final Property p1, final Property p2)
+ {
+ if (p1.getID() < p2.getID())
+ return -1;
+ else if (p1.getID() == p2.getID())
+ return 0;
+ else
+ return 1;
+ }
+ });
+
+ /* Write the properties and the property list into their respective
+ * streams: */
+ for (final ListIterator i = preprops.listIterator(); i.hasNext();)
+ {
+ final MutableProperty p = (MutableProperty) i.next();
+ final long id = p.getID();
+
+ /* Write the property list entry. */
+ TypeWriter.writeUIntToStream(propertyListStream, p.getID());
+ TypeWriter.writeUIntToStream(propertyListStream, position);
+
+ /* If the property ID is not equal 0 we write the property and all
+ * is fine. However, if it equals 0 we have to write the section's
+ * dictionary which has an implicit type only and an explicit
+ * value. */
+ if (id != 0)
+ /* Write the property and update the position to the next
+ * property. */
+ position += p.write(propertyStream, getCodepage());
+ else
+ {
+ if (codepage == -1)
+ throw new IllegalPropertySetDataException
+ ("Codepage (property 1) is undefined.");
+ position += writeDictionary(propertyStream, dictionary,
+ codepage);
+ }
+ }
+ propertyStream.close();
+ propertyListStream.close();
+
+ /* Write the section: */
+ byte[] pb1 = propertyListStream.toByteArray();
+ byte[] pb2 = propertyStream.toByteArray();
+
+ /* Write the section's length: */
+ TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
+ pb1.length + pb2.length);
+
+ /* Write the section's number of properties: */
+ TypeWriter.writeToStream(out, getPropertyCount());
+
+ /* Write the property list: */
+ out.write(pb1);
+
+ /* Write the properties: */
+ out.write(pb2);
+
+ int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
+ return streamLength;
+ }
+
+
+
+ /**
+ *
Writes the section's dictionary.
+ *
+ * @param out The output stream to write to.
+ * @param dictionary The dictionary.
+ * @param codepage The codepage to be used to write the dictionary items.
+ * @return The number of bytes written
+ * @exception IOException if an I/O exception occurs.
+ */
+ private static int writeDictionary(final OutputStream out,
+ final Map dictionary, final int codepage)
+ throws IOException
+ {
+ int length = TypeWriter.writeUIntToStream(out, dictionary.size());
+ for (Map.Entry ls : dictionary.entrySet()) {
+ final Long key = ls.getKey();
+ final String value = ls.getValue();
+
+ if (codepage == CodePageUtil.CP_UNICODE)
+ {
+ /* Write the dictionary item in Unicode. */
+ int sLength = value.length() + 1;
+ if ((sLength & 1) == 1) {
+ sLength++;
+ }
+ length += TypeWriter.writeUIntToStream(out, key.longValue());
+ length += TypeWriter.writeUIntToStream(out, sLength);
+ final byte[] ca = CodePageUtil.getBytesInCodePage(value, codepage);
+ for (int j = 2; j < ca.length; j += 2)
+ {
+ out.write(ca[j+1]);
+ out.write(ca[j]);
+ length += 2;
+ }
+ sLength -= value.length();
+ while (sLength > 0)
+ {
+ out.write(0x00);
+ out.write(0x00);
+ length += 2;
+ sLength--;
+ }
+ }
+ else
+ {
+ /* Write the dictionary item in another codepage than
+ * Unicode. */
+ length += TypeWriter.writeUIntToStream(out, key.longValue());
+ length += TypeWriter.writeUIntToStream(out, value.length() + 1);
+ final byte[] ba = CodePageUtil.getBytesInCodePage(value, codepage);
+ for (int j = 0; j < ba.length; j++)
+ {
+ out.write(ba[j]);
+ length++;
+ }
+ out.write(0x00);
+ length++;
+ }
+ }
+ return length;
+ }
+
+
+
+ /**
+ *
Overwrites the super class' method to cope with a redundancy:
+ * the property count is maintained in a separate member variable, but
+ * shouldn't.
+ *
+ * @return The number of properties in this section
+ */
+ public int getPropertyCount()
+ {
+ return preprops.size();
+ }
+
+
+
+ /**
+ *
+ *
+ * @param id The ID of the property to get
+ * @return The property or null if there is no such property
+ */
+ public Object getProperty(final long id)
+ {
+ /* Calling getProperties() ensures that properties and preprops are in
+ * sync.
Sets the section's dictionary. All keys in the dictionary must be
+ * {@link java.lang.Long} instances, all values must be
+ * {@link java.lang.String}s. This method overwrites the properties with IDs
+ * 0 and 1 since they are reserved for the dictionary and the dictionary's
+ * codepage. Setting these properties explicitly might have surprising
+ * effects. An application should never do this but always use this
+ * method.
+ *
+ * @param dictionary The dictionary
+ *
+ * @exception IllegalPropertySetDataException if the dictionary's key and
+ * value types are not correct.
+ *
+ * @see Section#getDictionary()
+ */
+ public void setDictionary(final Map dictionary)
+ throws IllegalPropertySetDataException
+ {
+ if (dictionary != null)
+ {
+ this.dictionary = dictionary;
+
+ /* Set the dictionary property (ID 0). Please note that the second
+ * parameter in the method call below is unused because dictionaries
+ * don't have a type. */
+ setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
+
+ /* If the codepage property (ID 1) for the strings (keys and
+ * values) used in the dictionary is not yet defined, set it to
+ * Unicode. */
+ final Integer codepage =
+ (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
+ if (codepage == null)
+ setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
+ Integer.valueOf(CodePageUtil.CP_UNICODE));
+ }
+ else
+ /* Setting the dictionary to null means to remove property 0.
+ * However, it does not mean to remove property 1 (codepage). */
+ removeProperty(PropertyIDMap.PID_DICTIONARY);
+ }
+
+
+
+ /**
+ *
Sets a property.
+ *
+ * @param id The property ID.
+ * @param value The property's value. The value's class must be one of those
+ * supported by HPSF.
+ */
+ public void setProperty(final int id, final Object value)
+ {
+ if (value instanceof String)
+ setProperty(id, (String) value);
+ else if (value instanceof Long)
+ setProperty(id, ((Long) value).longValue());
+ else if (value instanceof Integer)
+ setProperty(id, ((Integer) value).intValue());
+ else if (value instanceof Short)
+ setProperty(id, ((Short) value).intValue());
+ else if (value instanceof Boolean)
+ setProperty(id, ((Boolean) value).booleanValue());
+ else if (value instanceof Date)
+ setProperty(id, Variant.VT_FILETIME, value);
+ else
+ throw new HPSFRuntimeException(
+ "HPSF does not support properties of type " +
+ value.getClass().getName() + ".");
+ }
+
+
+
+ /**
+ *
Removes all properties from the section including 0 (dictionary) and
+ * 1 (codepage).
+ */
+ public void clear()
+ {
+ final Property[] properties = getProperties();
+ for (int i = 0; i < properties.length; i++)
+ {
+ final Property p = properties[i];
+ removeProperty(p.getID());
+ }
+ }
+
+ /**
+ *
Sets the codepage.
+ *
+ * @param codepage the codepage
+ */
+ public void setCodepage(final int codepage)
+ {
+ setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
+ Integer.valueOf(codepage));
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java b/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java
new file mode 100644
index 000000000..f1a09438e
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/NoFormatIDException.java
@@ -0,0 +1,71 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if a {@link MutablePropertySet} is to be written
+ * but does not have a formatID set (see {@link
+ * MutableSection#setFormatID(ClassID)} or
+ * {@link org.apache.poi.hpsf.MutableSection#setFormatID(byte[])}.
+ */
+public class NoFormatIDException extends HPSFRuntimeException
+{
+
+ /**
+ *
+ *
+ * @param msg The exception's message string
+ * @param reason This exception's underlying reason
+ */
+ public NoFormatIDException(final String msg, final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java b/trunk/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java
new file mode 100644
index 000000000..62738d42b
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/NoPropertySetStreamException.java
@@ -0,0 +1,76 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if a format error in a property set stream is
+ * detected or when the input data do not constitute a property set stream.
+ *
+ *
The constructors of this class are analogous to those of its superclass
+ * and are documented there.
+ *
+ * @param msg The exception's message string
+ * @param reason This exception's underlying reason
+ */
+ public NoPropertySetStreamException(final String msg,
+ final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/NoSingleSectionException.java b/trunk/src/java/org/apache/poi/hpsf/NoSingleSectionException.java
new file mode 100644
index 000000000..2309fe561
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/NoSingleSectionException.java
@@ -0,0 +1,74 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if one of the {@link PropertySet}'s
+ * convenience methods that require a single {@link Section} is called
+ * and the {@link PropertySet} does not contain exactly one {@link
+ * Section}.
+ *
+ *
The constructors of this class are analogous to those of its
+ * superclass and documented there.
+ *
+ * @param msg The exception's message string
+ * @param reason This exception's underlying reason
+ */
+ public NoSingleSectionException(final String msg, final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Property.java b/trunk/src/java/org/apache/poi/hpsf/Property.java
new file mode 100644
index 000000000..14dbee027
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Property.java
@@ -0,0 +1,453 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.poi.util.CodePageUtil;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ *
A property in a {@link Section} of a {@link PropertySet}.
+ *
+ *
The property's ID gives the property a meaning
+ * in the context of its {@link Section}. Each {@link Section} spans
+ * its own name space of property IDs.
+ *
+ *
The property's type determines how its
+ * value is interpreted. For example, if the type is
+ * {@link Variant#VT_LPSTR} (byte string), the value consists of a
+ * DWord telling how many bytes the string contains. The bytes follow
+ * immediately, including any null bytes that terminate the
+ * string. The type {@link Variant#VT_I4} denotes a four-byte integer
+ * value, {@link Variant#VT_FILETIME} some date and time (of a
+ * file).
+ *
+ *
Please note that not all {@link Variant} types yet. This might change
+ * over time but largely depends on your feedback so that the POI team knows
+ * which variant types are really needed. So please feel free to submit error
+ * reports or patches for the types you need.
+ *
+ * @return The ID value
+ */
+ public long getID()
+ {
+ return id;
+ }
+
+
+
+ /**
The property's type.
*/
+ protected long type;
+
+
+ /**
+ *
Returns the property's type.
+ *
+ * @return The type value
+ */
+ public long getType()
+ {
+ return type;
+ }
+
+
+
+ /**
The property's value.
*/
+ protected Object value;
+
+
+ /**
+ *
Returns the property's value.
+ *
+ * @return The property's value
+ */
+ public Object getValue()
+ {
+ return value;
+ }
+
+
+
+ /**
+ *
Creates a property.
+ *
+ * @param id the property's ID.
+ * @param type the property's type, see {@link Variant}.
+ * @param value the property's value. Only certain types are allowed, see
+ * {@link Variant}.
+ */
+ public Property(final long id, final long type, final Object value)
+ {
+ this.id = id;
+ this.type = type;
+ this.value = value;
+ }
+
+
+
+ /**
+ *
Creates a {@link Property} instance by reading its bytes
+ * from the property set stream.
+ *
+ * @param id The property's ID.
+ * @param src The bytes the property set stream consists of.
+ * @param offset The property's type/value pair's offset in the
+ * section.
+ * @param length The property's type/value pair's length in bytes.
+ * @param codepage The section's and thus the property's
+ * codepage. It is needed only when reading string values.
+ * @exception UnsupportedEncodingException if the specified codepage is not
+ * supported.
+ */
+ public Property(final long id, final byte[] src, final long offset,
+ final int length, final int codepage)
+ throws UnsupportedEncodingException
+ {
+ this.id = id;
+
+ /*
+ * ID 0 is a special case since it specifies a dictionary of
+ * property IDs and property names.
+ */
+ if (id == 0)
+ {
+ value = readDictionary(src, offset, length, codepage);
+ return;
+ }
+
+ int o = (int) offset;
+ type = LittleEndian.getUInt(src, o);
+ o += LittleEndian.INT_SIZE;
+
+ try
+ {
+ value = VariantSupport.read(src, o, length, (int) type, codepage);
+ }
+ catch (UnsupportedVariantTypeException ex)
+ {
+ VariantSupport.writeUnsupportedTypeMessage(ex);
+ value = ex.getValue();
+ }
+ }
+
+
+
+ /**
+ *
Creates an empty property. It must be filled using the set method to
+ * be usable.
+ */
+ protected Property()
+ { }
+
+
+
+ /**
+ *
Reads a dictionary.
+ *
+ * @param src The byte array containing the bytes making out the dictionary.
+ * @param offset At this offset within src the dictionary
+ * starts.
+ * @param length The dictionary contains at most this many bytes.
+ * @param codepage The codepage of the string values.
+ * @return The dictonary
+ * @throws UnsupportedEncodingException if the dictionary's codepage is not
+ * (yet) supported.
+ */
+ protected Map, ?> readDictionary(final byte[] src, final long offset,
+ final int length, final int codepage)
+ throws UnsupportedEncodingException
+ {
+ /* Check whether "offset" points into the "src" array". */
+ if (offset < 0 || offset > src.length)
+ throw new HPSFRuntimeException
+ ("Illegal offset " + offset + " while HPSF stream contains " +
+ length + " bytes.");
+ int o = (int) offset;
+
+ /*
+ * Read the number of dictionary entries.
+ */
+ final long nrEntries = LittleEndian.getUInt(src, o);
+ o += LittleEndian.INT_SIZE;
+
+ final Map m = new LinkedHashMap(
+ (int) nrEntries, (float) 1.0 );
+
+ try
+ {
+ for (int i = 0; i < nrEntries; i++)
+ {
+ /* The key. */
+ final Long id = Long.valueOf(LittleEndian.getUInt(src, o));
+ o += LittleEndian.INT_SIZE;
+
+ /* The value (a string). The length is the either the
+ * number of (two-byte) characters if the character set is Unicode
+ * or the number of bytes if the character set is not Unicode.
+ * The length includes terminating 0x00 bytes which we have to strip
+ * off to create a Java string. */
+ long sLength = LittleEndian.getUInt(src, o);
+ o += LittleEndian.INT_SIZE;
+
+ /* Read the string. */
+ final StringBuffer b = new StringBuffer();
+ switch (codepage)
+ {
+ case -1:
+ {
+ /* Without a codepage the length is equal to the number of
+ * bytes. */
+ b.append(new String(src, o, (int) sLength, Charset.forName("ASCII")));
+ break;
+ }
+ case CodePageUtil.CP_UNICODE:
+ {
+ /* The length is the number of characters, i.e. the number
+ * of bytes is twice the number of the characters. */
+ final int nrBytes = (int) (sLength * 2);
+ final byte[] h = new byte[nrBytes];
+ for (int i2 = 0; i2 < nrBytes; i2 += 2)
+ {
+ h[i2] = src[o + i2 + 1];
+ h[i2 + 1] = src[o + i2];
+ }
+ b.append(new String(h, 0, nrBytes,
+ CodePageUtil.codepageToEncoding(codepage)));
+ break;
+ }
+ default:
+ {
+ /* For encodings other than Unicode the length is the number
+ * of bytes. */
+ b.append(new String(src, o, (int) sLength,
+ VariantSupport.codepageToEncoding(codepage)));
+ break;
+ }
+ }
+
+ /* Strip 0x00 characters from the end of the string: */
+ while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00)
+ b.setLength(b.length() - 1);
+ if (codepage == CodePageUtil.CP_UNICODE)
+ {
+ if (sLength % 2 == 1)
+ sLength++;
+ o += (sLength + sLength);
+ }
+ else
+ o += sLength;
+ m.put(id, b.toString());
+ }
+ }
+ catch (RuntimeException ex)
+ {
+ final POILogger l = POILogFactory.getLogger(getClass());
+ l.log(POILogger.WARN,
+ "The property set's dictionary contains bogus data. "
+ + "All dictionary entries starting with the one with ID "
+ + id + " will be ignored.", ex);
+ }
+ return m;
+ }
+
+
+
+ /**
+ *
Returns the property's size in bytes. This is always a multiple of
+ * 4.
+ *
+ * @return the property's size in bytes
+ *
+ * @exception WritingNotSupportedException if HPSF does not yet support the
+ * property's variant type.
+ */
+ protected int getSize() throws WritingNotSupportedException
+ {
+ int length = VariantSupport.getVariantLength(type);
+ if (length >= 0)
+ return length; /* Fixed length */
+ if (length == -2)
+ /* Unknown length */
+ throw new WritingNotSupportedException(type, null);
+
+ /* Variable length: */
+ final int PADDING = 4; /* Pad to multiples of 4. */
+ switch ((int) type)
+ {
+ case Variant.VT_LPSTR:
+ {
+ int l = ((String) value).length() + 1;
+ int r = l % PADDING;
+ if (r > 0)
+ l += PADDING - r;
+ length += l;
+ break;
+ }
+ case Variant.VT_EMPTY:
+ break;
+ default:
+ throw new WritingNotSupportedException(type, value);
+ }
+ return length;
+ }
+
+
+
+ /**
+ *
Compares two properties.
Please beware that a property with
+ * ID == 0 is a special case: It does not have a type, and its value is the
+ * section's dictionary. Another special case are strings: Two properties
+ * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;
+ *
+ * @see Object#equals(java.lang.Object)
+ */
+ public boolean equals(final Object o)
+ {
+ if (!(o instanceof Property)) {
+ return false;
+ }
+ final Property p = (Property) o;
+ final Object pValue = p.getValue();
+ final long pId = p.getID();
+ if (id != pId || (id != 0 && !typesAreEqual(type, p.getType())))
+ return false;
+ if (value == null && pValue == null)
+ return true;
+ if (value == null || pValue == null)
+ return false;
+
+ /* It's clear now that both values are non-null. */
+ final Class> valueClass = value.getClass();
+ final Class> pValueClass = pValue.getClass();
+ if (!(valueClass.isAssignableFrom(pValueClass)) &&
+ !(pValueClass.isAssignableFrom(valueClass)))
+ return false;
+
+ if (value instanceof byte[])
+ return Util.equal((byte[]) value, (byte[]) pValue);
+
+ return value.equals(pValue);
+ }
+
+
+
+ private boolean typesAreEqual(final long t1, final long t2)
+ {
+ if (t1 == t2 ||
+ (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) ||
+ (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR)) {
+ return true;
+ }
+ return false;
+ }
+
+
+
+ /**
+ * @see Object#hashCode()
+ */
+ public int hashCode()
+ {
+ long hashCode = 0;
+ hashCode += id;
+ hashCode += type;
+ if (value != null)
+ hashCode += value.hashCode();
+ final int returnHashCode = (int) (hashCode & 0x0ffffffffL );
+ return returnHashCode;
+
+ }
+
+
+
+ /**
+ * @see Object#toString()
+ */
+ public String toString()
+ {
+ final StringBuffer b = new StringBuffer();
+ b.append(getClass().getName());
+ b.append('[');
+ b.append("id: ");
+ b.append(getID());
+ b.append(", type: ");
+ b.append(getType());
+ final Object value = getValue();
+ b.append(", value: ");
+ if (value instanceof String)
+ {
+ b.append(value.toString());
+ final String s = (String) value;
+ final int l = s.length();
+ final byte[] bytes = new byte[l * 2];
+ for (int i = 0; i < l; i++)
+ {
+ final char c = s.charAt(i);
+ final byte high = (byte) ((c & 0x00ff00) >> 8);
+ final byte low = (byte) ((c & 0x0000ff) >> 0);
+ bytes[i * 2] = high;
+ bytes[i * 2 + 1] = low;
+ }
+ b.append(" [");
+ if(bytes.length > 0) {
+ final String hex = HexDump.dump(bytes, 0L, 0);
+ b.append(hex);
+ }
+ b.append("]");
+ }
+ else if (value instanceof byte[])
+ {
+ byte[] bytes = (byte[])value;
+ if(bytes.length > 0) {
+ String hex = HexDump.dump(bytes, 0L, 0);
+ b.append(hex);
+ }
+ }
+ else
+ {
+ b.append(value.toString());
+ }
+ b.append(']');
+ return b.toString();
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/PropertySet.java b/trunk/src/java/org/apache/poi/hpsf/PropertySet.java
new file mode 100644
index 000000000..9c9b1876a
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/PropertySet.java
@@ -0,0 +1,696 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hpsf.wellknown.SectionIDMap;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ *
Represents a property set in the Horrible Property Set Format
+ * (HPSF). These are usually metadata of a Microsoft Office
+ * document.
+ *
+ *
An application that wants to access these metadata should create
+ * an instance of this class or one of its subclasses by calling the
+ * factory method {@link PropertySetFactory#create} and then retrieve
+ * the information its needs by calling appropriate methods.
+ *
+ *
{@link PropertySetFactory#create} does its work by calling one
+ * of the constructors {@link PropertySet#PropertySet(InputStream)} or
+ * {@link PropertySet#PropertySet(byte[])}. If the constructor's
+ * argument is not in the Horrible Property Set Format, i.e. not a
+ * property set stream, or if any other error occurs, an appropriate
+ * exception is thrown.
+ *
+ *
A {@link PropertySet} has a list of {@link Section}s, and each
+ * {@link Section} has a {@link Property} array. Use {@link
+ * #getSections} to retrieve the {@link Section}s, then call {@link
+ * Section#getProperties} for each {@link Section} to get hold of the
+ * {@link Property} arrays.
Since the vast majority of {@link
+ * PropertySet}s contains only a single {@link Section}, the
+ * convenience method {@link #getProperties} returns the properties of
+ * a {@link PropertySet}'s {@link Section} (throwing a {@link
+ * NoSingleSectionException} if the {@link PropertySet} contains more
+ * (or less) than exactly one {@link Section}).
+ */
+public class PropertySet
+{
+
+ /**
+ *
Returns the number of {@link Section}s in the property
+ * set.
+ *
+ * @return The number of {@link Section}s in the property set.
+ */
+ public int getSectionCount()
+ {
+ return sections.size();
+ }
+
+
+
+ /**
+ *
The sections in this {@link PropertySet}.
+ */
+ protected List sections;
+
+ /**
+ *
Returns the {@link Section}s in the property set.
+ *
+ * @return The {@link Section}s in the property set.
+ */
+ public List getSections()
+ {
+ return sections;
+ }
+
+
+
+ /**
+ *
Creates an empty (uninitialized) {@link PropertySet}.
+ *
+ *
Please note: For the time being this
+ * constructor is protected since it is used for internal purposes
+ * only, but expect it to become public once the property set's
+ * writing functionality is implemented.
Creates a {@link PropertySet} instance from an {@link
+ * InputStream} in the Horrible Property Set Format.
+ *
+ *
The constructor reads the first few bytes from the stream
+ * and determines whether it is really a property set stream. If
+ * it is, it parses the rest of the stream. If it is not, it
+ * resets the stream to its beginning in order to let other
+ * components mess around with the data and throws an
+ * exception.
+ *
+ * @param stream Holds the data making out the property set
+ * stream.
+ * @throws MarkUnsupportedException if the stream does not support
+ * the {@link InputStream#markSupported} method.
+ * @throws IOException if the {@link InputStream} cannot be
+ * accessed as needed.
+ * @exception NoPropertySetStreamException if the input stream does not
+ * contain a property set.
+ * @exception UnsupportedEncodingException if a character encoding is not
+ * supported.
+ */
+ public PropertySet(final InputStream stream)
+ throws NoPropertySetStreamException, MarkUnsupportedException,
+ IOException, UnsupportedEncodingException
+ {
+ if (isPropertySetStream(stream))
+ {
+ final int avail = stream.available();
+ final byte[] buffer = new byte[avail];
+ IOUtils.readFully(stream, buffer);
+ init(buffer, 0, buffer.length);
+ }
+ else
+ throw new NoPropertySetStreamException();
+ }
+
+
+
+ /**
+ *
Creates a {@link PropertySet} instance from a byte array
+ * that represents a stream in the Horrible Property Set
+ * Format.
+ *
+ * @param stream The byte array holding the stream data.
+ * @param offset The offset in stream where the stream
+ * data begin. If the stream data begin with the first byte in the
+ * array, the offset is 0.
+ * @param length The length of the stream data.
+ * @throws NoPropertySetStreamException if the byte array is not a
+ * property set stream.
+ *
+ * @exception UnsupportedEncodingException if the codepage is not supported.
+ */
+ public PropertySet(final byte[] stream, final int offset, final int length)
+ throws NoPropertySetStreamException, UnsupportedEncodingException
+ {
+ if (isPropertySetStream(stream, offset, length))
+ init(stream, offset, length);
+ else
+ throw new NoPropertySetStreamException();
+ }
+
+
+
+ /**
+ *
Creates a {@link PropertySet} instance from a byte array
+ * that represents a stream in the Horrible Property Set
+ * Format.
+ *
+ * @param stream The byte array holding the stream data. The
+ * complete byte array contents is the stream data.
+ * @throws NoPropertySetStreamException if the byte array is not a
+ * property set stream.
+ *
+ * @exception UnsupportedEncodingException if the codepage is not supported.
+ */
+ public PropertySet(final byte[] stream)
+ throws NoPropertySetStreamException, UnsupportedEncodingException
+ {
+ this(stream, 0, stream.length);
+ }
+
+
+
+ /**
+ *
Checks whether an {@link InputStream} is in the Horrible
+ * Property Set Format.
+ *
+ * @param stream The {@link InputStream} to check. In order to
+ * perform the check, the method reads the first bytes from the
+ * stream. After reading, the stream is reset to the position it
+ * had before reading. The {@link InputStream} must support the
+ * {@link InputStream#mark} method.
+ * @return true if the stream is a property set
+ * stream, else false.
+ * @throws MarkUnsupportedException if the {@link InputStream}
+ * does not support the {@link InputStream#mark} method.
+ * @exception IOException if an I/O error occurs
+ */
+ public static boolean isPropertySetStream(final InputStream stream)
+ throws MarkUnsupportedException, IOException
+ {
+ /*
+ * Read at most this many bytes.
+ */
+ final int BUFFER_SIZE = 50;
+
+ /*
+ * Mark the current position in the stream so that we can
+ * reset to this position if the stream does not contain a
+ * property set.
+ */
+ if (!stream.markSupported())
+ throw new MarkUnsupportedException(stream.getClass().getName());
+ stream.mark(BUFFER_SIZE);
+
+ /*
+ * Read a couple of bytes from the stream.
+ */
+ final byte[] buffer = new byte[BUFFER_SIZE];
+ final int bytes =
+ stream.read(buffer, 0,
+ Math.min(buffer.length, stream.available()));
+ final boolean isPropertySetStream =
+ isPropertySetStream(buffer, 0, bytes);
+ stream.reset();
+ return isPropertySetStream;
+ }
+
+
+
+ /**
+ *
Checks whether a byte array is in the Horrible Property Set
+ * Format.
+ *
+ * @param src The byte array to check.
+ * @param offset The offset in the byte array.
+ * @param length The significant number of bytes in the byte
+ * array. Only this number of bytes will be checked.
+ * @return true if the byte array is a property set
+ * stream, false if not.
+ */
+ public static boolean isPropertySetStream(final byte[] src,
+ final int offset,
+ final int length)
+ {
+ /* FIXME (3): Ensure that at most "length" bytes are read. */
+
+ /*
+ * Read the header fields of the stream. They must always be
+ * there.
+ */
+ int o = offset;
+ final int byteOrder = LittleEndian.getUShort(src, o);
+ o += LittleEndian.SHORT_SIZE;
+ byte[] temp = new byte[LittleEndian.SHORT_SIZE];
+ LittleEndian.putShort(temp, 0, (short) byteOrder);
+ if (!Util.equal(temp, BYTE_ORDER_ASSERTION))
+ return false;
+ final int format = LittleEndian.getUShort(src, o);
+ o += LittleEndian.SHORT_SIZE;
+ temp = new byte[LittleEndian.SHORT_SIZE];
+ LittleEndian.putShort(temp, 0, (short) format);
+ if (!Util.equal(temp, FORMAT_ASSERTION))
+ return false;
+ // final long osVersion = LittleEndian.getUInt(src, offset);
+ o += LittleEndian.INT_SIZE;
+ // final ClassID classID = new ClassID(src, offset);
+ o += ClassID.LENGTH;
+ final long sectionCount = LittleEndian.getUInt(src, o);
+ o += LittleEndian.INT_SIZE;
+ if (sectionCount < 0)
+ return false;
+ return true;
+ }
+
+
+
+ /**
+ *
Initializes this {@link PropertySet} instance from a byte
+ * array. The method assumes that it has been checked already that
+ * the byte array indeed represents a property set stream. It does
+ * no more checks on its own.
+ *
+ * @param src Byte array containing the property set stream
+ * @param offset The property set stream starts at this offset
+ * from the beginning of src
+ * @param length Length of the property set stream.
+ * @throws UnsupportedEncodingException if HPSF does not (yet) support the
+ * property set's character encoding.
+ */
+ private void init(final byte[] src, final int offset, final int length)
+ throws UnsupportedEncodingException
+ {
+ /* FIXME (3): Ensure that at most "length" bytes are read. */
+
+ /*
+ * Read the stream's header fields.
+ */
+ int o = offset;
+ byteOrder = LittleEndian.getUShort(src, o);
+ o += LittleEndian.SHORT_SIZE;
+ format = LittleEndian.getUShort(src, o);
+ o += LittleEndian.SHORT_SIZE;
+ osVersion = (int) LittleEndian.getUInt(src, o);
+ o += LittleEndian.INT_SIZE;
+ classID = new ClassID(src, o);
+ o += ClassID.LENGTH;
+ final int sectionCount = LittleEndian.getInt(src, o);
+ o += LittleEndian.INT_SIZE;
+ if (sectionCount < 0)
+ throw new HPSFRuntimeException("Section count " + sectionCount +
+ " is negative.");
+
+ /*
+ * Read the sections, which are following the header. They
+ * start with an array of section descriptions. Each one
+ * consists of a format ID telling what the section contains
+ * and an offset telling how many bytes from the start of the
+ * stream the section begins.
+ */
+ /*
+ * Most property sets have only one section. The Document
+ * Summary Information stream has 2. Everything else is a rare
+ * exception and is no longer fostered by Microsoft.
+ */
+ sections = new ArrayList( sectionCount );
+
+ /*
+ * Loop over the section descriptor array. Each descriptor
+ * consists of a ClassID and a DWord, and we have to increment
+ * "offset" accordingly.
+ */
+ for (int i = 0; i < sectionCount; i++)
+ {
+ final Section s = new Section(src, o);
+ o += ClassID.LENGTH + LittleEndian.INT_SIZE;
+ sections.add(s);
+ }
+ }
+
+
+
+ /**
+ *
Checks whether this {@link PropertySet} represents a Summary
+ * Information.
+ *
+ * @return true if this {@link PropertySet}
+ * represents a Summary Information, else false.
+ */
+ public boolean isSummaryInformation()
+ {
+ if (sections.size() <= 0)
+ return false;
+ return Util.equal(sections.get(0).getFormatID().getBytes(),
+ SectionIDMap.SUMMARY_INFORMATION_ID);
+ }
+
+
+
+ /**
+ *
Checks whether this {@link PropertySet} is a Document
+ * Summary Information.
+ *
+ * @return true if this {@link PropertySet}
+ * represents a Document Summary Information, else false.
+ */
+ public boolean isDocumentSummaryInformation()
+ {
+ if (sections.size() <= 0)
+ return false;
+ return Util.equal(sections.get(0).getFormatID().getBytes(),
+ SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
+ }
+
+
+
+ /**
+ *
Convenience method returning the {@link Property} array
+ * contained in this property set. It is a shortcut for getting
+ * the {@link PropertySet}'s {@link Section}s list and then
+ * getting the {@link Property} array from the first {@link
+ * Section}.
+ *
+ * @return The properties of the only {@link Section} of this
+ * {@link PropertySet}.
+ * @throws NoSingleSectionException if the {@link PropertySet} has
+ * more or less than one {@link Section}.
+ */
+ public Property[] getProperties()
+ throws NoSingleSectionException
+ {
+ return getFirstSection().getProperties();
+ }
+
+
+
+ /**
+ *
Convenience method returning the value of the property with
+ * the specified ID. If the property is not available,
+ * null is returned and a subsequent call to {@link
+ * #wasNull} will return true .
+ *
+ * @param id The property ID
+ * @return The property value
+ * @throws NoSingleSectionException if the {@link PropertySet} has
+ * more or less than one {@link Section}.
+ */
+ protected Object getProperty(final int id) throws NoSingleSectionException
+ {
+ return getFirstSection().getProperty(id);
+ }
+
+
+
+ /**
+ *
Convenience method returning the value of a boolean property
+ * with the specified ID. If the property is not available,
+ * false is returned. A subsequent call to {@link
+ * #wasNull} will return true to let the caller
+ * distinguish that case from a real property value of
+ * false.
+ *
+ * @param id The property ID
+ * @return The property value
+ * @throws NoSingleSectionException if the {@link PropertySet} has
+ * more or less than one {@link Section}.
+ */
+ protected boolean getPropertyBooleanValue(final int id)
+ throws NoSingleSectionException
+ {
+ return getFirstSection().getPropertyBooleanValue(id);
+ }
+
+
+
+ /**
+ *
Convenience method returning the value of the numeric
+ * property with the specified ID. If the property is not
+ * available, 0 is returned. A subsequent call to {@link #wasNull}
+ * will return true to let the caller distinguish
+ * that case from a real property value of 0.
+ *
+ * @param id The property ID
+ * @return The propertyIntValue value
+ * @throws NoSingleSectionException if the {@link PropertySet} has
+ * more or less than one {@link Section}.
+ */
+ protected int getPropertyIntValue(final int id)
+ throws NoSingleSectionException
+ {
+ return getFirstSection().getPropertyIntValue(id);
+ }
+
+
+
+ /**
+ *
Checks whether the property which the last call to {@link
+ * #getPropertyIntValue} or {@link #getProperty} tried to access
+ * was available or not. This information might be important for
+ * callers of {@link #getPropertyIntValue} since the latter
+ * returns 0 if the property does not exist. Using {@link
+ * #wasNull}, the caller can distiguish this case from a
+ * property's real value of 0.
+ *
+ * @return true if the last call to {@link
+ * #getPropertyIntValue} or {@link #getProperty} tried to access a
+ * property that was not available, else false.
+ * @throws NoSingleSectionException if the {@link PropertySet} has
+ * more than one {@link Section}.
+ */
+ public boolean wasNull() throws NoSingleSectionException
+ {
+ return getFirstSection().wasNull();
+ }
+
+
+
+ /**
+ *
Gets the {@link PropertySet}'s first section.
+ *
+ * @return The {@link PropertySet}'s first section.
+ */
+ public Section getFirstSection()
+ {
+ if (getSectionCount() < 1)
+ throw new MissingSectionException("Property set does not contain any sections.");
+ return sections.get(0);
+ }
+
+
+
+ /**
+ *
If the {@link PropertySet} has only a single section this
+ * method returns it.
+ *
+ * @return The singleSection value
+ */
+ public Section getSingleSection()
+ {
+ final int sectionCount = getSectionCount();
+ if (sectionCount != 1)
+ throw new NoSingleSectionException
+ ("Property set contains " + sectionCount + " sections.");
+ return sections.get(0);
+ }
+
+
+
+ /**
+ *
Returns true if the PropertySet is equal
+ * to the specified parameter, else false.
+ *
+ * @param o the object to compare this PropertySet with
+ *
+ * @return true if the objects are equal, false
+ * if not
+ */
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (o == null || !(o instanceof PropertySet))
+ return false;
+ final PropertySet ps = (PropertySet) o;
+ int byteOrder1 = ps.getByteOrder();
+ int byteOrder2 = getByteOrder();
+ ClassID classID1 = ps.getClassID();
+ ClassID classID2 = getClassID();
+ int format1 = ps.getFormat();
+ int format2 = getFormat();
+ int osVersion1 = ps.getOSVersion();
+ int osVersion2 = getOSVersion();
+ int sectionCount1 = ps.getSectionCount();
+ int sectionCount2 = getSectionCount();
+ if (byteOrder1 != byteOrder2 ||
+ !classID1.equals(classID2) ||
+ format1 != format2 ||
+ osVersion1 != osVersion2 ||
+ sectionCount1 != sectionCount2)
+ return false;
+
+ /* Compare the sections: */
+ return Util.equals(getSections(), ps.getSections());
+ }
+
+
+
+ /**
+ * @see Object#hashCode()
+ */
+ public int hashCode()
+ {
+ throw new UnsupportedOperationException("FIXME: Not yet implemented.");
+ }
+
+
+
+ /**
+ * @see Object#toString()
+ */
+ public String toString()
+ {
+ final StringBuilder b = new StringBuilder();
+ final int sectionCount = getSectionCount();
+ b.append(getClass().getName());
+ b.append('[');
+ b.append("byteOrder: ");
+ b.append(getByteOrder());
+ b.append(", classID: ");
+ b.append(getClassID());
+ b.append(", format: ");
+ b.append(getFormat());
+ b.append(", OSVersion: ");
+ b.append(getOSVersion());
+ b.append(", sectionCount: ");
+ b.append(sectionCount);
+ b.append(", sections: [\n");
+ for (Section section: getSections())
+ b.append(section);
+ b.append(']');
+ b.append(']');
+ return b.toString();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/PropertySetFactory.java b/trunk/src/java/org/apache/poi/hpsf/PropertySetFactory.java
new file mode 100644
index 000000000..4d4b4e638
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/PropertySetFactory.java
@@ -0,0 +1,151 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.poi.hpsf.wellknown.SectionIDMap;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.poifs.filesystem.DocumentEntry;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+
+/**
+ *
Factory class to create instances of {@link SummaryInformation},
+ * {@link DocumentSummaryInformation} and {@link PropertySet}.
+ */
+public class PropertySetFactory
+{
+ /**
+ *
Creates the most specific {@link PropertySet} from an entry
+ * in the specified POIFS Directory. This is preferrably a {@link
+ * DocumentSummaryInformation} or a {@link SummaryInformation}. If
+ * the specified entry does not contain a property set stream, an
+ * exception is thrown. If no entry is found with the given name,
+ * an exception is thrown.
+ *
+ * @param dir The directory to find the PropertySet in
+ * @param name The name of the entry containing the PropertySet
+ * @return The created {@link PropertySet}.
+ * @throws FileNotFoundException if there is no entry with that name
+ * @throws NoPropertySetStreamException if the stream does not
+ * contain a property set.
+ * @throws IOException if some I/O problem occurs.
+ * @exception UnsupportedEncodingException if the specified codepage is not
+ * supported.
+ */
+ public static PropertySet create(final DirectoryEntry dir, final String name)
+ throws FileNotFoundException, NoPropertySetStreamException,
+ IOException, UnsupportedEncodingException
+ {
+ InputStream inp = null;
+ try {
+ DocumentEntry entry = (DocumentEntry)dir.getEntry(name);
+ inp = new DocumentInputStream(entry);
+ try {
+ return create(inp);
+ } catch (MarkUnsupportedException e) { return null; }
+ } finally {
+ if (inp != null) inp.close();
+ }
+ }
+
+ /**
+ *
Creates the most specific {@link PropertySet} from an {@link
+ * InputStream}. This is preferrably a {@link
+ * DocumentSummaryInformation} or a {@link SummaryInformation}. If
+ * the specified {@link InputStream} does not contain a property
+ * set stream, an exception is thrown and the {@link InputStream}
+ * is repositioned at its beginning.
+ *
+ * @param stream Contains the property set stream's data.
+ * @return The created {@link PropertySet}.
+ * @throws NoPropertySetStreamException if the stream does not
+ * contain a property set.
+ * @throws MarkUnsupportedException if the stream does not support
+ * the mark operation.
+ * @throws IOException if some I/O problem occurs.
+ * @exception UnsupportedEncodingException if the specified codepage is not
+ * supported.
+ */
+ public static PropertySet create(final InputStream stream)
+ throws NoPropertySetStreamException, MarkUnsupportedException,
+ UnsupportedEncodingException, IOException
+ {
+ final PropertySet ps = new PropertySet(stream);
+ try
+ {
+ if (ps.isSummaryInformation())
+ return new SummaryInformation(ps);
+ else if (ps.isDocumentSummaryInformation())
+ return new DocumentSummaryInformation(ps);
+ else
+ return ps;
+ }
+ catch (UnexpectedPropertySetTypeException ex)
+ {
+ /* This exception will never be throws because we already checked
+ * explicitly for this case above. */
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ /**
+ *
Creates a new summary information.
+ *
+ * @return the new summary information.
+ */
+ public static SummaryInformation newSummaryInformation()
+ {
+ final MutablePropertySet ps = new MutablePropertySet();
+ final MutableSection s = (MutableSection) ps.getFirstSection();
+ s.setFormatID(SectionIDMap.SUMMARY_INFORMATION_ID);
+ try
+ {
+ return new SummaryInformation(ps);
+ }
+ catch (UnexpectedPropertySetTypeException ex)
+ {
+ /* This should never happen. */
+ throw new HPSFRuntimeException(ex);
+ }
+ }
+
+ /**
+ *
Creates a new document summary information.
+ *
+ * @return the new document summary information.
+ */
+ public static DocumentSummaryInformation newDocumentSummaryInformation()
+ {
+ final MutablePropertySet ps = new MutablePropertySet();
+ final MutableSection s = (MutableSection) ps.getFirstSection();
+ s.setFormatID(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
+ try
+ {
+ return new DocumentSummaryInformation(ps);
+ }
+ catch (UnexpectedPropertySetTypeException ex)
+ {
+ /* This should never happen. */
+ throw new HPSFRuntimeException(ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java b/trunk/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java
new file mode 100644
index 000000000..cb16058a7
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java
@@ -0,0 +1,43 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown when HPSF tries to read a (yet) unsupported
+ * variant type.
+ *
+ * @param variantType The unsupported variant type.
+ * @param value The value.
+ */
+ public ReadingNotSupportedException(final long variantType,
+ final Object value)
+ {
+ super(variantType, value);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Section.java b/trunk/src/java/org/apache/poi/hpsf/Section.java
new file mode 100644
index 000000000..c54fe51ab
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Section.java
@@ -0,0 +1,688 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.hpsf.wellknown.SectionIDMap;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ *
Represents a section in a {@link PropertySet}.
+ */
+public class Section
+{
+
+ /**
+ *
Maps property IDs to section-private PID strings. These
+ * strings can be found in the property with ID 0.
+ */
+ protected Map dictionary;
+
+ /**
+ *
The section's format ID, {@link #getFormatID}.
+ */
+ protected ClassID formatID;
+
+
+ /**
+ *
Returns the format ID. The format ID is the "type" of the
+ * section. For example, if the format ID of the first {@link
+ * Section} contains the bytes specified by
+ * org.apache.poi.hpsf.wellknown.SectionIDMap.SUMMARY_INFORMATION_ID
+ * the section (and thus the property set) is a SummaryInformation.
+ *
+ * @return The format ID
+ */
+ public ClassID getFormatID()
+ {
+ return formatID;
+ }
+
+
+
+ /**
+ * @see #getOffset
+ */
+ protected long offset;
+
+
+ /**
+ *
Returns the offset of the section in the stream.
+ *
+ * @return The offset of the section in the stream.
+ */
+ public long getOffset()
+ {
+ return offset;
+ }
+
+
+
+ /**
+ * @see #getSize
+ */
+ protected int size;
+
+
+ /**
+ *
Returns the section's size in bytes.
+ *
+ * @return The section's size in bytes.
+ */
+ public int getSize()
+ {
+ return size;
+ }
+
+
+
+ /**
+ *
Returns the number of properties in this section.
+ *
+ * @return The number of properties in this section.
+ */
+ public int getPropertyCount()
+ {
+ return properties.length;
+ }
+
+
+
+ /**
+ * @see #getProperties
+ */
+ protected Property[] properties;
+
+
+ /**
+ *
Creates a {@link Section} instance from a byte array.
+ *
+ * @param src Contains the complete property set stream.
+ * @param offset The position in the stream that points to the
+ * section's format ID.
+ *
+ * @exception UnsupportedEncodingException if the section's codepage is not
+ * supported.
+ */
+ @SuppressWarnings("unchecked")
+ public Section(final byte[] src, final int offset)
+ throws UnsupportedEncodingException
+ {
+ int o1 = offset;
+
+ /*
+ * Read the format ID.
+ */
+ formatID = new ClassID(src, o1);
+ o1 += ClassID.LENGTH;
+
+ /*
+ * Read the offset from the stream's start and positions to
+ * the section header.
+ */
+ this.offset = LittleEndian.getUInt(src, o1);
+ o1 = (int) this.offset;
+
+ /*
+ * Read the section length.
+ */
+ size = (int) LittleEndian.getUInt(src, o1);
+ o1 += LittleEndian.INT_SIZE;
+
+ /*
+ * Read the number of properties.
+ */
+ final int propertyCount = (int) LittleEndian.getUInt(src, o1);
+ o1 += LittleEndian.INT_SIZE;
+
+ /*
+ * Read the properties. The offset is positioned at the first
+ * entry of the property list. There are two problems:
+ *
+ * 1. For each property we have to find out its length. In the
+ * property list we find each property's ID and its offset relative
+ * to the section's beginning. Unfortunately the properties in the
+ * property list need not to be in ascending order, so it is not
+ * possible to calculate the length as
+ * (offset of property(i+1) - offset of property(i)). Before we can
+ * that we first have to sort the property list by ascending offsets.
+ *
+ * 2. We have to read the property with ID 1 before we read other
+ * properties, at least before other properties containing strings.
+ * The reason is that property 1 specifies the codepage. If it is
+ * 1200, all strings are in Unicode. In other words: Before we can
+ * read any strings we have to know whether they are in Unicode or
+ * not. Unfortunately property 1 is not guaranteed to be the first in
+ * a section.
+ *
+ * The algorithm below reads the properties in two passes: The first
+ * one looks for property ID 1 and extracts the codepage number. The
+ * seconds pass reads the other properties.
+ */
+ properties = new Property[propertyCount];
+
+ /* Pass 1: Read the property list. */
+ int pass1Offset = o1;
+ final List propertyList = new ArrayList(propertyCount);
+ PropertyListEntry ple;
+ for (int i = 0; i < properties.length; i++)
+ {
+ ple = new PropertyListEntry();
+
+ /* Read the property ID. */
+ ple.id = (int) LittleEndian.getUInt(src, pass1Offset);
+ pass1Offset += LittleEndian.INT_SIZE;
+
+ /* Offset from the section's start. */
+ ple.offset = (int) LittleEndian.getUInt(src, pass1Offset);
+ pass1Offset += LittleEndian.INT_SIZE;
+
+ /* Add the entry to the property list. */
+ propertyList.add(ple);
+ }
+
+ /* Sort the property list by ascending offsets: */
+ Collections.sort(propertyList);
+
+ /* Calculate the properties' lengths. */
+ for (int i = 0; i < propertyCount - 1; i++)
+ {
+ PropertyListEntry ple1 = propertyList.get(i);
+ PropertyListEntry ple2 = propertyList.get(i + 1);
+ ple1.length = ple2.offset - ple1.offset;
+ }
+ if (propertyCount > 0)
+ {
+ ple = propertyList.get(propertyCount - 1);
+ ple.length = size - ple.offset;
+ }
+
+ /* Look for the codepage. */
+ int codepage = -1;
+ for (final Iterator i = propertyList.iterator();
+ codepage == -1 && i.hasNext();)
+ {
+ ple = i.next();
+
+ /* Read the codepage if the property ID is 1. */
+ if (ple.id == PropertyIDMap.PID_CODEPAGE)
+ {
+ /* Read the property's value type. It must be
+ * VT_I2. */
+ int o = (int) (this.offset + ple.offset);
+ final long type = LittleEndian.getUInt(src, o);
+ o += LittleEndian.INT_SIZE;
+
+ if (type != Variant.VT_I2)
+ throw new HPSFRuntimeException
+ ("Value type of property ID 1 is not VT_I2 but " +
+ type + ".");
+
+ /* Read the codepage number. */
+ codepage = LittleEndian.getUShort(src, o);
+ }
+ }
+
+ /* Pass 2: Read all properties - including the codepage property,
+ * if available. */
+ int i1 = 0;
+ for (final Iterator i = propertyList.iterator(); i.hasNext();)
+ {
+ ple = i.next();
+ Property p = new Property(ple.id, src,
+ this.offset + ple.offset,
+ ple.length, codepage);
+ if (p.getID() == PropertyIDMap.PID_CODEPAGE)
+ p = new Property(p.getID(), p.getType(), Integer.valueOf(codepage));
+ properties[i1++] = p;
+ }
+
+ /*
+ * Extract the dictionary (if available).
+ */
+ dictionary = (Map) getProperty(0);
+ }
+
+
+
+ /**
+ *
Represents an entry in the property list and holds a property's ID and
+ * its offset from the section's beginning.
+ */
+ static class PropertyListEntry implements Comparable
+ {
+ int id;
+ int offset;
+ int length;
+
+ /**
+ *
Compares this {@link PropertyListEntry} with another one by their
+ * offsets. A {@link PropertyListEntry} is "smaller" than another one if
+ * its offset from the section's begin is smaller.
+ *
+ * @see Comparable#compareTo(java.lang.Object)
+ */
+ public int compareTo(final PropertyListEntry o)
+ {
+ final int otherOffset = o.offset;
+ if (offset < otherOffset)
+ return -1;
+ else if (offset == otherOffset)
+ return 0;
+ else
+ return 1;
+ }
+
+
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id;
+ result = prime * result + length;
+ result = prime * result + offset;
+ return result;
+ }
+
+
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PropertyListEntry other = (PropertyListEntry) obj;
+ if (id != other.id) {
+ return false;
+ }
+ if (length != other.length) {
+ return false;
+ }
+ if (offset != other.offset) {
+ return false;
+ }
+ return true;
+ }
+
+
+
+ public String toString()
+ {
+ final StringBuffer b = new StringBuffer();
+ b.append(getClass().getName());
+ b.append("[id=");
+ b.append(id);
+ b.append(", offset=");
+ b.append(offset);
+ b.append(", length=");
+ b.append(length);
+ b.append(']');
+ return b.toString();
+ }
+ }
+
+
+
+ /**
+ *
Returns the value of the property with the specified ID. If
+ * the property is not available, null is returned
+ * and a subsequent call to {@link #wasNull} will return
+ * true.
+ *
+ * @param id The property's ID
+ *
+ * @return The property's value
+ */
+ public Object getProperty(final long id)
+ {
+ wasNull = false;
+ for (int i = 0; i < properties.length; i++)
+ if (id == properties[i].getID())
+ return properties[i].getValue();
+ wasNull = true;
+ return null;
+ }
+
+
+
+ /**
+ *
Returns the value of the numeric property with the specified
+ * ID. If the property is not available, 0 is returned. A
+ * subsequent call to {@link #wasNull} will return
+ * true to let the caller distinguish that case from
+ * a real property value of 0.
+ *
+ * @param id The property's ID
+ *
+ * @return The property's value
+ */
+ protected int getPropertyIntValue(final long id)
+ {
+ final Number i;
+ final Object o = getProperty(id);
+ if (o == null)
+ return 0;
+ if (!(o instanceof Long || o instanceof Integer))
+ throw new HPSFRuntimeException
+ ("This property is not an integer type, but " +
+ o.getClass().getName() + ".");
+ i = (Number) o;
+ return i.intValue();
+ }
+
+
+
+ /**
+ *
Returns the value of the boolean property with the specified
+ * ID. If the property is not available, false is
+ * returned. A subsequent call to {@link #wasNull} will return
+ * true to let the caller distinguish that case from
+ * a real property value of false.
+ *
+ * @param id The property's ID
+ *
+ * @return The property's value
+ */
+ protected boolean getPropertyBooleanValue(final int id)
+ {
+ final Boolean b = (Boolean) getProperty(id);
+ if (b == null) {
+ return false;
+ }
+ return b.booleanValue();
+ }
+
+
+
+ /**
+ *
This member is true if the last call to {@link
+ * #getPropertyIntValue} or {@link #getProperty} tried to access a
+ * property that was not available, else false.
+ */
+ private boolean wasNull;
+
+
+ /**
+ *
Checks whether the property which the last call to {@link
+ * #getPropertyIntValue} or {@link #getProperty} tried to access
+ * was available or not. This information might be important for
+ * callers of {@link #getPropertyIntValue} since the latter
+ * returns 0 if the property does not exist. Using {@link
+ * #wasNull} the caller can distiguish this case from a property's
+ * real value of 0.
+ *
+ * @return true if the last call to {@link
+ * #getPropertyIntValue} or {@link #getProperty} tried to access a
+ * property that was not available, else false.
+ */
+ public boolean wasNull()
+ {
+ return wasNull;
+ }
+
+
+
+ /**
+ *
Returns the PID string associated with a property ID. The ID
+ * is first looked up in the {@link Section}'s private
+ * dictionary. If it is not found there, the method calls {@link
+ * SectionIDMap#getPIDString}.
+ *
+ * @param pid The property ID
+ *
+ * @return The property ID's string value
+ */
+ public String getPIDString(final long pid)
+ {
+ String s = null;
+ if (dictionary != null) {
+ s = dictionary.get(Long.valueOf(pid));
+ }
+ if (s == null) {
+ s = SectionIDMap.getPIDString(getFormatID().getBytes(), pid);
+ }
+ return s;
+ }
+
+
+
+ /**
+ *
Checks whether this section is equal to another object. The result is
+ * false if one of the the following conditions holds:
+ *
+ *
+ *
+ *
The other object is not a {@link Section}.
+ *
+ *
The format IDs of the two sections are not equal.
+ *
+ *
The sections have a different number of properties. However,
+ * properties with ID 1 (codepage) are not counted.
+ *
+ *
The other object is not a {@link Section}.
+ *
+ *
The properties have different values. The order of the properties
+ * is irrelevant.
+ *
+ *
+ *
+ * @param o The object to compare this section with
+ * @return true if the objects are equal, false if
+ * not
+ */
+ public boolean equals(final Object o)
+ {
+ if (o == null || !(o instanceof Section))
+ return false;
+ final Section s = (Section) o;
+ if (!s.getFormatID().equals(getFormatID()))
+ return false;
+
+ /* Compare all properties except 0 and 1 as they must be handled
+ * specially. */
+ Property[] pa1 = new Property[getProperties().length];
+ Property[] pa2 = new Property[s.getProperties().length];
+ System.arraycopy(getProperties(), 0, pa1, 0, pa1.length);
+ System.arraycopy(s.getProperties(), 0, pa2, 0, pa2.length);
+
+ /* Extract properties 0 and 1 and remove them from the copy of the
+ * arrays. */
+ Property p10 = null;
+ Property p20 = null;
+ for (int i = 0; i < pa1.length; i++)
+ {
+ final long id = pa1[i].getID();
+ if (id == 0)
+ {
+ p10 = pa1[i];
+ pa1 = remove(pa1, i);
+ i--;
+ }
+ if (id == 1)
+ {
+ // p11 = pa1[i];
+ pa1 = remove(pa1, i);
+ i--;
+ }
+ }
+ for (int i = 0; i < pa2.length; i++)
+ {
+ final long id = pa2[i].getID();
+ if (id == 0)
+ {
+ p20 = pa2[i];
+ pa2 = remove(pa2, i);
+ i--;
+ }
+ if (id == 1)
+ {
+ // p21 = pa2[i];
+ pa2 = remove(pa2, i);
+ i--;
+ }
+ }
+
+ /* If the number of properties (not counting property 1) is unequal the
+ * sections are unequal. */
+ if (pa1.length != pa2.length)
+ return false;
+
+ /* If the dictionaries are unequal the sections are unequal. */
+ boolean dictionaryEqual = true;
+ if (p10 != null && p20 != null)
+ dictionaryEqual = p10.getValue().equals(p20.getValue());
+ else if (p10 != null || p20 != null)
+ dictionaryEqual = false;
+ if (dictionaryEqual) {
+ return Util.equals(pa1, pa2);
+ }
+ return false;
+ }
+
+
+
+ /**
+ *
Removes a field from a property array. The resulting array is
+ * compactified and returned.
+ *
+ * @param pa The property array.
+ * @param i The index of the field to be removed.
+ * @return the compactified array.
+ */
+ private Property[] remove(final Property[] pa, final int i)
+ {
+ final Property[] h = new Property[pa.length - 1];
+ if (i > 0)
+ System.arraycopy(pa, 0, h, 0, i);
+ System.arraycopy(pa, i + 1, h, i, h.length - i);
+ return h;
+ }
+
+
+
+ /**
+ * @see Object#hashCode()
+ */
+ public int hashCode()
+ {
+ long hashCode = 0;
+ hashCode += getFormatID().hashCode();
+ final Property[] pa = getProperties();
+ for (int i = 0; i < pa.length; i++)
+ hashCode += pa[i].hashCode();
+ final int returnHashCode = (int) (hashCode & 0x0ffffffffL);
+ return returnHashCode;
+ }
+
+
+
+ /**
+ * @see Object#toString()
+ */
+ public String toString()
+ {
+ final StringBuffer b = new StringBuffer();
+ final Property[] pa = getProperties();
+ b.append(getClass().getName());
+ b.append('[');
+ b.append("formatID: ");
+ b.append(getFormatID());
+ b.append(", offset: ");
+ b.append(getOffset());
+ b.append(", propertyCount: ");
+ b.append(getPropertyCount());
+ b.append(", size: ");
+ b.append(getSize());
+ b.append(", properties: [\n");
+ for (int i = 0; i < pa.length; i++)
+ {
+ b.append(pa[i].toString());
+ b.append(",\n");
+ }
+ b.append(']');
+ b.append(']');
+ return b.toString();
+ }
+
+
+
+ /**
+ *
Gets the section's dictionary. A dictionary allows an application to
+ * use human-readable property names instead of numeric property IDs. It
+ * contains mappings from property IDs to their associated string
+ * values. The dictionary is stored as the property with ID 0. The codepage
+ * for the strings in the dictionary is defined by property with ID 1.
+ *
+ * @return the dictionary or null if the section does not have
+ * a dictionary.
+ */
+ public Map getDictionary()
+ {
+ return dictionary;
+ }
+
+
+
+ /**
+ *
Gets the section's codepage, if any.
+ *
+ * @return The section's codepage if one is defined, else -1.
+ */
+ public int getCodepage()
+ {
+ final Integer codepage =
+ (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
+ if (codepage == null)
+ return -1;
+ int cp = codepage.intValue();
+ return cp;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/SpecialPropertySet.java b/trunk/src/java/org/apache/poi/hpsf/SpecialPropertySet.java
new file mode 100644
index 000000000..aa8f8051d
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/SpecialPropertySet.java
@@ -0,0 +1,413 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.List;
+
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.poifs.filesystem.DirectoryEntry;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ *
Abstract superclass for the convenience classes {@link
+ * SummaryInformation} and {@link DocumentSummaryInformation}.
+ *
+ *
The motivation behind this class is quite nasty if you look
+ * behind the scenes, but it serves the application programmer well by
+ * providing him with the easy-to-use {@link SummaryInformation} and
+ * {@link DocumentSummaryInformation} classes. When parsing the data a
+ * property set stream consists of (possibly coming from an {@link
+ * java.io.InputStream}) we want to read and process each byte only
+ * once. Since we don't know in advance which kind of property set we
+ * have, we can expect only the most general {@link
+ * PropertySet}. Creating a special subclass should be as easy as
+ * calling the special subclass' constructor and pass the general
+ * {@link PropertySet} in. To make things easy internally, the special
+ * class just holds a reference to the general {@link PropertySet} and
+ * delegates all method calls to it.
+ *
+ *
A cleaner implementation would have been like this: The {@link
+ * PropertySetFactory} parses the stream data into some internal
+ * object first. Then it finds out whether the stream is a {@link
+ * SummaryInformation}, a {@link DocumentSummaryInformation} or a
+ * general {@link PropertySet}. However, the current implementation
+ * went the other way round historically: the convenience classes came
+ * only late to my mind.
+ */
+public abstract class SpecialPropertySet extends MutablePropertySet
+{
+ /**
+ * The id to name mapping of the properties in this set.
+ *
+ * @return the id to name mapping of the properties in this set
+ */
+ public abstract PropertyIDMap getPropertySetIDMap();
+
+ /**
+ *
The "real" property set SpecialPropertySet
+ * delegates to.
Creates a SpecialPropertySet.
+ *
+ * @param ps The property set to be encapsulated by the
+ * SpecialPropertySet
+ */
+ public SpecialPropertySet(final PropertySet ps)
+ {
+ delegate = new MutablePropertySet(ps);
+ }
+
+
+
+ /**
+ *
Creates a SpecialPropertySet.
+ *
+ * @param ps The mutable property set to be encapsulated by the
+ * SpecialPropertySet
+ */
+ public SpecialPropertySet(final MutablePropertySet ps)
+ {
+ delegate = ps;
+ }
+
+
+
+ /**
+ * @see PropertySet#getByteOrder
+ */
+ @Override
+ public int getByteOrder()
+ {
+ return delegate.getByteOrder();
+ }
+
+
+
+ /**
+ * @see PropertySet#getFormat
+ */
+ @Override
+ public int getFormat()
+ {
+ return delegate.getFormat();
+ }
+
+
+
+ /**
+ * @see PropertySet#getOSVersion
+ */
+ @Override
+ public int getOSVersion()
+ {
+ return delegate.getOSVersion();
+ }
+
+
+
+ /**
+ * @see PropertySet#getClassID
+ */
+ @Override
+ public ClassID getClassID()
+ {
+ return delegate.getClassID();
+ }
+
+
+
+ /**
+ * @see PropertySet#getSectionCount
+ */
+ @Override
+ public int getSectionCount()
+ {
+ return delegate.getSectionCount();
+ }
+
+
+
+ /**
+ * @see PropertySet#getSections
+ */
+ @Override
+ public List getSections()
+ {
+ return delegate.getSections();
+ }
+
+
+
+ /**
+ * @see PropertySet#isSummaryInformation
+ */
+ @Override
+ public boolean isSummaryInformation()
+ {
+ return delegate.isSummaryInformation();
+ }
+
+
+
+ /**
+ * @see PropertySet#isDocumentSummaryInformation
+ */
+ @Override
+ public boolean isDocumentSummaryInformation()
+ {
+ return delegate.isDocumentSummaryInformation();
+ }
+
+
+
+ /**
+ * @see PropertySet#getSingleSection
+ */
+ @Override
+ public Section getFirstSection()
+ {
+ return delegate.getFirstSection();
+ }
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#addSection(org.apache.poi.hpsf.Section)
+ */
+ @Override
+ public void addSection(final Section section)
+ {
+ delegate.addSection(section);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#clearSections()
+ */
+ @Override
+ public void clearSections()
+ {
+ delegate.clearSections();
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#setByteOrder(int)
+ */
+ @Override
+ public void setByteOrder(final int byteOrder)
+ {
+ delegate.setByteOrder(byteOrder);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#setClassID(org.apache.poi.hpsf.ClassID)
+ */
+ @Override
+ public void setClassID(final ClassID classID)
+ {
+ delegate.setClassID(classID);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#setFormat(int)
+ */
+ @Override
+ public void setFormat(final int format)
+ {
+ delegate.setFormat(format);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#setOSVersion(int)
+ */
+ @Override
+ public void setOSVersion(final int osVersion)
+ {
+ delegate.setOSVersion(osVersion);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#toInputStream()
+ */
+ @Override
+ public InputStream toInputStream() throws IOException, WritingNotSupportedException
+ {
+ return delegate.toInputStream();
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#write(org.apache.poi.poifs.filesystem.DirectoryEntry, java.lang.String)
+ */
+ @Override
+ public void write(final DirectoryEntry dir, final String name) throws WritingNotSupportedException, IOException
+ {
+ delegate.write(dir, name);
+ }
+
+ /**
+ * @see org.apache.poi.hpsf.MutablePropertySet#write(java.io.OutputStream)
+ */
+ @Override
+ public void write(final OutputStream out) throws WritingNotSupportedException, IOException
+ {
+ delegate.write(out);
+ }
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(final Object o)
+ {
+ return delegate.equals(o);
+ }
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#getProperties()
+ */
+ @Override
+ public Property[] getProperties() throws NoSingleSectionException
+ {
+ return delegate.getProperties();
+ }
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#getProperty(int)
+ */
+ @Override
+ protected Object getProperty(final int id) throws NoSingleSectionException
+ {
+ return delegate.getProperty(id);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#getPropertyBooleanValue(int)
+ */
+ @Override
+ protected boolean getPropertyBooleanValue(final int id) throws NoSingleSectionException
+ {
+ return delegate.getPropertyBooleanValue(id);
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#getPropertyIntValue(int)
+ */
+ @Override
+ protected int getPropertyIntValue(final int id) throws NoSingleSectionException
+ {
+ return delegate.getPropertyIntValue(id);
+ }
+
+
+
+ /**
+ * Fetches the property with the given ID, then does its
+ * best to return it as a String
+ *
+ * @param propertyId the property id
+ *
+ * @return The property as a String, or null if unavailable
+ */
+ protected String getPropertyStringValue(final int propertyId) {
+ Object propertyValue = getProperty(propertyId);
+ return getPropertyStringValue(propertyValue);
+ }
+ protected static String getPropertyStringValue(final Object propertyValue) {
+ // Normal cases
+ if (propertyValue == null) return null;
+ if (propertyValue instanceof String) return (String)propertyValue;
+
+ // Do our best with some edge cases
+ if (propertyValue instanceof byte[]) {
+ byte[] b = (byte[])propertyValue;
+ if (b.length == 0) {
+ return "";
+ }
+ if (b.length == 1) {
+ return Byte.toString(b[0]);
+ }
+ if (b.length == 2) {
+ return Integer.toString( LittleEndian.getUShort(b) );
+ }
+ if (b.length == 4) {
+ return Long.toString( LittleEndian.getUInt(b) );
+ }
+ // Maybe it's a string? who knows!
+ return new String(b, Charset.forName("ASCII"));
+ }
+ return propertyValue.toString();
+ }
+
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#hashCode()
+ */
+ @Override
+ public int hashCode()
+ {
+ return delegate.hashCode();
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#toString()
+ */
+ @Override
+ public String toString()
+ {
+ return delegate.toString();
+ }
+
+
+
+ /**
+ * @see org.apache.poi.hpsf.PropertySet#wasNull()
+ */
+ @Override
+ public boolean wasNull() throws NoSingleSectionException
+ {
+ return delegate.wasNull();
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/SummaryInformation.java b/trunk/src/java/org/apache/poi/hpsf/SummaryInformation.java
new file mode 100644
index 000000000..5c260187c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/SummaryInformation.java
@@ -0,0 +1,762 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.util.Date;
+
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+
+/**
+ *
Convenience class representing a Summary Information stream in a
+ * Microsoft Office document.
+ *
+ * @see DocumentSummaryInformation
+ */
+public final class SummaryInformation extends SpecialPropertySet {
+
+ /**
+ *
The document name a summary information stream usually has in a POIFS
+ * filesystem.
+ */
+ public static final String DEFAULT_STREAM_NAME = "\005SummaryInformation";
+
+ public PropertyIDMap getPropertySetIDMap() {
+ return PropertyIDMap.getSummaryInformationProperties();
+ }
+
+
+ /**
+ *
Creates a {@link SummaryInformation} from a given {@link
+ * PropertySet}.
+ *
+ * @param ps A property set which should be created from a summary
+ * information stream.
+ * @throws UnexpectedPropertySetTypeException if ps does not
+ * contain a summary information stream.
+ */
+ public SummaryInformation(final PropertySet ps)
+ throws UnexpectedPropertySetTypeException
+ {
+ super(ps);
+ if (!isSummaryInformation())
+ throw new UnexpectedPropertySetTypeException("Not a "
+ + getClass().getName());
+ }
+
+
+
+ /**
+ *
Returns the title (or null).
+ *
+ * @return The title or null
+ */
+ public String getTitle()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_TITLE);
+ }
+
+
+
+ /**
+ *
Sets the title.
+ *
+ * @param title The title to set.
+ */
+ public void setTitle(final String title)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_TITLE, title);
+ }
+
+
+
+ /**
+ *
Removes the title.
+ */
+ public void removeTitle()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_TITLE);
+ }
+
+
+
+ /**
+ *
Returns the subject (or null).
+ *
+ * @return The subject or null
+ */
+ public String getSubject()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_SUBJECT);
+ }
+
+
+
+ /**
+ *
Sets the subject.
+ *
+ * @param subject The subject to set.
+ */
+ public void setSubject(final String subject)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_SUBJECT, subject);
+ }
+
+
+
+ /**
+ *
Removes the subject.
+ */
+ public void removeSubject()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_SUBJECT);
+ }
+
+
+
+ /**
+ *
Returns the author (or null).
+ *
+ * @return The author or null
+ */
+ public String getAuthor()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_AUTHOR);
+ }
+
+
+
+ /**
+ *
Sets the author.
+ *
+ * @param author The author to set.
+ */
+ public void setAuthor(final String author)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_AUTHOR, author);
+ }
+
+
+
+ /**
+ *
Removes the author.
+ */
+ public void removeAuthor()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_AUTHOR);
+ }
+
+
+
+ /**
+ *
Returns the keywords (or null).
+ *
+ * @return The keywords or null
+ */
+ public String getKeywords()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_KEYWORDS);
+ }
+
+
+
+ /**
+ *
Sets the keywords.
+ *
+ * @param keywords The keywords to set.
+ */
+ public void setKeywords(final String keywords)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_KEYWORDS, keywords);
+ }
+
+
+
+ /**
+ *
Removes the keywords.
+ */
+ public void removeKeywords()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_KEYWORDS);
+ }
+
+
+
+ /**
+ *
Returns the comments (or null).
+ *
+ * @return The comments or null
+ */
+ public String getComments()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_COMMENTS);
+ }
+
+
+
+ /**
+ *
Sets the comments.
+ *
+ * @param comments The comments to set.
+ */
+ public void setComments(final String comments)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_COMMENTS, comments);
+ }
+
+
+
+ /**
+ *
Removes the comments.
+ */
+ public void removeComments()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_COMMENTS);
+ }
+
+
+
+ /**
+ *
Returns the template (or null).
+ *
+ * @return The template or null
+ */
+ public String getTemplate()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_TEMPLATE);
+ }
+
+
+
+ /**
+ *
Sets the template.
+ *
+ * @param template The template to set.
+ */
+ public void setTemplate(final String template)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_TEMPLATE, template);
+ }
+
+
+
+ /**
+ *
Removes the template.
+ */
+ public void removeTemplate()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_TEMPLATE);
+ }
+
+
+
+ /**
+ *
Returns the last author (or null).
+ *
+ * @return The last author or null
+ */
+ public String getLastAuthor()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_LASTAUTHOR);
+ }
+
+
+
+ /**
+ *
Sets the last author.
+ *
+ * @param lastAuthor The last author to set.
+ */
+ public void setLastAuthor(final String lastAuthor)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_LASTAUTHOR, lastAuthor);
+ }
+
+
+
+ /**
+ *
Removes the last author.
+ */
+ public void removeLastAuthor()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_LASTAUTHOR);
+ }
+
+
+
+ /**
+ *
Returns the revision number (or null).
+ *
+ * @return The revision number or null
+ */
+ public String getRevNumber()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_REVNUMBER);
+ }
+
+
+
+ /**
+ *
Sets the revision number.
+ *
+ * @param revNumber The revision number to set.
+ */
+ public void setRevNumber(final String revNumber)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_REVNUMBER, revNumber);
+ }
+
+
+
+ /**
+ *
Removes the revision number.
+ */
+ public void removeRevNumber()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_REVNUMBER);
+ }
+
+
+
+ /**
+ *
Returns the total time spent in editing the document (or
+ * 0).
+ *
+ * @return The total time spent in editing the document or 0 if the {@link
+ * SummaryInformation} does not contain this information.
+ */
+ public long getEditTime()
+ {
+ final Date d = (Date) getProperty(PropertyIDMap.PID_EDITTIME);
+ if (d == null) {
+ return 0;
+ }
+ return Util.dateToFileTime(d);
+ }
+
+
+
+ /**
+ *
Sets the total time spent in editing the document.
+ *
+ * @param time The time to set.
+ */
+ public void setEditTime(final long time)
+ {
+ final Date d = Util.filetimeToDate(time);
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_EDITTIME, Variant.VT_FILETIME, d);
+ }
+
+
+
+ /**
+ *
Remove the total time spent in editing the document.
+ */
+ public void removeEditTime()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_EDITTIME);
+ }
+
+
+
+ /**
+ *
Returns the last printed time (or null).
+ *
+ * @return The last printed time or null
+ */
+ public Date getLastPrinted()
+ {
+ return (Date) getProperty(PropertyIDMap.PID_LASTPRINTED);
+ }
+
+
+
+ /**
+ *
Sets the lastPrinted.
+ *
+ * @param lastPrinted The lastPrinted to set.
+ */
+ public void setLastPrinted(final Date lastPrinted)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_LASTPRINTED, Variant.VT_FILETIME,
+ lastPrinted);
+ }
+
+
+
+ /**
+ *
Removes the lastPrinted.
+ */
+ public void removeLastPrinted()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_LASTPRINTED);
+ }
+
+
+
+ /**
+ *
Returns the creation time (or null).
+ *
+ * @return The creation time or null
+ */
+ public Date getCreateDateTime()
+ {
+ return (Date) getProperty(PropertyIDMap.PID_CREATE_DTM);
+ }
+
+
+
+ /**
+ *
Sets the creation time.
+ *
+ * @param createDateTime The creation time to set.
+ */
+ public void setCreateDateTime(final Date createDateTime)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_CREATE_DTM, Variant.VT_FILETIME,
+ createDateTime);
+ }
+
+
+
+ /**
+ *
Removes the creation time.
+ */
+ public void removeCreateDateTime()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_CREATE_DTM);
+ }
+
+
+
+ /**
+ *
Returns the last save time (or null).
+ *
+ * @return The last save time or null
+ */
+ public Date getLastSaveDateTime()
+ {
+ return (Date) getProperty(PropertyIDMap.PID_LASTSAVE_DTM);
+ }
+
+
+
+ /**
+ *
Sets the total time spent in editing the document.
+ *
+ * @param time The time to set.
+ */
+ public void setLastSaveDateTime(final Date time)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s
+ .setProperty(PropertyIDMap.PID_LASTSAVE_DTM,
+ Variant.VT_FILETIME, time);
+ }
+
+
+
+ /**
+ *
Remove the total time spent in editing the document.
+ */
+ public void removeLastSaveDateTime()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_LASTSAVE_DTM);
+ }
+
+
+
+ /**
+ *
Returns the page count or 0 if the {@link SummaryInformation} does
+ * not contain a page count.
+ *
+ * @return The page count or 0 if the {@link SummaryInformation} does not
+ * contain a page count.
+ */
+ public int getPageCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_PAGECOUNT);
+ }
+
+
+
+ /**
+ *
Sets the page count.
+ *
+ * @param pageCount The page count to set.
+ */
+ public void setPageCount(final int pageCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_PAGECOUNT, pageCount);
+ }
+
+
+
+ /**
+ *
Removes the page count.
+ */
+ public void removePageCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_PAGECOUNT);
+ }
+
+
+
+ /**
+ *
Returns the word count or 0 if the {@link SummaryInformation} does
+ * not contain a word count.
+ *
+ * @return The word count or null
+ */
+ public int getWordCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_WORDCOUNT);
+ }
+
+
+
+ /**
+ *
Sets the word count.
+ *
+ * @param wordCount The word count to set.
+ */
+ public void setWordCount(final int wordCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_WORDCOUNT, wordCount);
+ }
+
+
+
+ /**
+ *
Removes the word count.
+ */
+ public void removeWordCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_WORDCOUNT);
+ }
+
+
+
+ /**
+ *
Returns the character count or 0 if the {@link SummaryInformation}
+ * does not contain a char count.
+ *
+ * @return The character count or null
+ */
+ public int getCharCount()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_CHARCOUNT);
+ }
+
+
+
+ /**
+ *
Sets the character count.
+ *
+ * @param charCount The character count to set.
+ */
+ public void setCharCount(final int charCount)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_CHARCOUNT, charCount);
+ }
+
+
+
+ /**
+ *
Removes the character count.
+ */
+ public void removeCharCount()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_CHARCOUNT);
+ }
+
+
+
+ /**
+ *
Returns the thumbnail (or null) when this
+ * method is implemented. Please note that the return type is likely to
+ * change!
+ *
+ *
To process this data, you may wish to make use of the
+ * {@link Thumbnail} class. The raw data is generally
+ * an image in WMF or Clipboard (BMP?) format
+ *
+ * @return The thumbnail or null
+ */
+ public byte[] getThumbnail()
+ {
+ return (byte[]) getProperty(PropertyIDMap.PID_THUMBNAIL);
+ }
+
+ /**
+ *
Returns the thumbnail (or null), processed
+ * as an object which is (largely) able to unpack the thumbnail
+ * image data.
+ *
+ * @return The thumbnail or null
+ */
+ public Thumbnail getThumbnailThumbnail()
+ {
+ byte[] data = getThumbnail();
+ if (data == null) return null;
+ return new Thumbnail(data);
+ }
+
+
+
+ /**
+ *
Sets the thumbnail.
+ *
+ * @param thumbnail The thumbnail to set.
+ */
+ public void setThumbnail(final byte[] thumbnail)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_THUMBNAIL, /* FIXME: */
+ Variant.VT_LPSTR, thumbnail);
+ }
+
+
+
+ /**
+ *
Removes the thumbnail.
+ */
+ public void removeThumbnail()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_THUMBNAIL);
+ }
+
+
+
+ /**
+ *
Returns the application name (or null).
+ *
+ * @return The application name or null
+ */
+ public String getApplicationName()
+ {
+ return getPropertyStringValue(PropertyIDMap.PID_APPNAME);
+ }
+
+
+
+ /**
+ *
Sets the application name.
+ *
+ * @param applicationName The application name to set.
+ */
+ public void setApplicationName(final String applicationName)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_APPNAME, applicationName);
+ }
+
+
+
+ /**
+ *
Removes the application name.
+ */
+ public void removeApplicationName()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_APPNAME);
+ }
+
+
+
+ /**
+ *
Returns a security code which is one of the following values:
+ *
+ *
+ *
+ *
0 if the {@link SummaryInformation} does not contain a
+ * security field or if there is no security on the document. Use
+ * {@link PropertySet#wasNull()} to distinguish between the two
+ * cases!
+ *
+ *
1 if the document is password protected
+ *
+ *
2 if the document is read-only recommended
+ *
+ *
4 if the document is read-only enforced
+ *
+ *
8 if the document is locked for annotations
+ *
+ *
+ *
+ * @return The security code or null
+ */
+ public int getSecurity()
+ {
+ return getPropertyIntValue(PropertyIDMap.PID_SECURITY);
+ }
+
+
+
+ /**
+ *
Sets the security code.
+ *
+ * @param security The security code to set.
+ */
+ public void setSecurity(final int security)
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.setProperty(PropertyIDMap.PID_SECURITY, security);
+ }
+
+
+
+ /**
+ *
Removes the security code.
+ */
+ public void removeSecurity()
+ {
+ final MutableSection s = (MutableSection) getFirstSection();
+ s.removeProperty(PropertyIDMap.PID_SECURITY);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Thumbnail.java b/trunk/src/java/org/apache/poi/hpsf/Thumbnail.java
new file mode 100644
index 000000000..a1faf3d08
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Thumbnail.java
@@ -0,0 +1,273 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+/**
+ *
Class to manipulate data in the Clipboard Variant ({@link
+ * Variant#VT_CF VT_CF}) format.
Offset in bytes where the Clipboard Format Tag starts in the
+ * byte[] returned by {@link
+ * SummaryInformation#getThumbnail()}
+ */
+ public static final int OFFSET_CFTAG = 4;
+
+ /**
+ *
Offset in bytes where the Clipboard Format starts in the
+ * byte[] returned by {@link
+ * SummaryInformation#getThumbnail()}
+ *
+ *
This is only valid if the Clipboard Format Tag is {@link
+ * #CFTAG_WINDOWS}
+ */
+ public static final int OFFSET_CF = 8;
+
+ /**
+ *
Offset in bytes where the Windows Metafile (WMF) image data
+ * starts in the byte[] returned by {@link
+ * SummaryInformation#getThumbnail()}
+ *
+ *
There is only WMF data at this point in the
+ * byte[] if the Clipboard Format Tag is {@link
+ * #CFTAG_WINDOWS} and the Clipboard Format is {@link
+ * #CF_METAFILEPICT}.
+ *
+ *
Note: The byte[] that starts at
+ * OFFSET_WMFDATA and ends at
+ * getThumbnail().length - 1 forms a complete WMF
+ * image. It can be saved to disk with a .wmf file
+ * type and read using a WMF-capable image viewer.
+ */
+ public static final int OFFSET_WMFDATA = 20;
+
+ /**
+ *
Clipboard Format Tag - Windows clipboard format
+ *
+ *
A DWORD indicating a built-in Windows clipboard
+ * format value
+ */
+ public static final int CFTAG_WINDOWS = -1;
+
+ /**
+ *
Clipboard Format Tag - Macintosh clipboard format
+ *
+ *
A DWORD indicating a Macintosh clipboard format
+ * value
+ */
+ public static final int CFTAG_MACINTOSH = -2;
+
+ /**
+ *
Clipboard Format Tag - Format ID
+ *
+ *
A GUID containing a format identifier (FMTID). This is
+ * rarely used.
+ */
+ public static final int CFTAG_FMTID = -3;
+
+ /**
+ *
Clipboard Format Tag - No Data
+ *
+ *
A DWORD indicating No data. This is rarely
+ * used.
+ */
+ public static final int CFTAG_NODATA = 0;
+
+ /**
+ *
Clipboard Format - Windows metafile format. This is the
+ * recommended way to store thumbnails in Property Streams.
+ *
+ *
Note: This is not the same format used in
+ * regular WMF images. The clipboard version of this format has an
+ * extra clipboard-specific header.
+ */
+ public static final int CF_METAFILEPICT = 3;
+
+ /**
+ *
Clipboard Format - Device Independent Bitmap
+ */
+ public static final int CF_DIB = 8;
+
+ /**
+ *
Clipboard Format - Enhanced Windows metafile format
+ */
+ public static final int CF_ENHMETAFILE = 14;
+
+ /**
+ *
Clipboard Format - Bitmap
+ */
+ public static final int CF_BITMAP = 2;
+
+ /**
+ *
A byte[] to hold a thumbnail image in ({@link
+ * Variant#VT_CF VT_CF}) format.
Default Constructor. If you use it then one you'll have to add
+ * the thumbnail byte[] from {@link
+ * SummaryInformation#getThumbnail()} to do any useful
+ * manipulations, otherwise you'll get a
+ * NullPointerException.
Sets the Thumbnail's underlying byte[] in
+ * {@link Variant#VT_CF VT_CF} format.
+ *
+ * @param thumbnail The new thumbnail value
+ * @see SummaryInformation#getThumbnail()
+ */
+ public void setThumbnail(final byte[] thumbnail)
+ {
+ this._thumbnailData = thumbnail;
+ }
+
+
+
+ /**
+ *
Returns an int representing the Clipboard
+ * Format Tag
+ *
+ *
Possible return values are:
+ *
+ *
{@link #CFTAG_WINDOWS CFTAG_WINDOWS}
+ *
{@link #CFTAG_MACINTOSH CFTAG_MACINTOSH}
+ *
{@link #CFTAG_FMTID CFTAG_FMTID}
+ *
{@link #CFTAG_NODATA CFTAG_NODATA}
+ *
+ *
+ * @return A flag indicating the Clipboard Format Tag
+ */
+ public long getClipboardFormatTag()
+ {
+ long clipboardFormatTag = LittleEndian.getInt(getThumbnail(),
+ OFFSET_CFTAG);
+ return clipboardFormatTag;
+ }
+
+
+
+ /**
+ *
Returns an int representing the Clipboard
+ * Format
+ *
+ *
Will throw an exception if the Thumbnail's Clipboard Format
+ * Tag is not {@link Thumbnail#CFTAG_WINDOWS CFTAG_WINDOWS}.
+ *
+ *
Possible return values are:
+ *
+ *
+ *
{@link #CF_METAFILEPICT CF_METAFILEPICT}
+ *
{@link #CF_DIB CF_DIB}
+ *
{@link #CF_ENHMETAFILE CF_ENHMETAFILE}
+ *
{@link #CF_BITMAP CF_BITMAP}
+ *
+ *
+ * @return a flag indicating the Clipboard Format
+ * @throws HPSFException if the Thumbnail isn't CFTAG_WINDOWS
+ */
+ public long getClipboardFormat() throws HPSFException
+ {
+ if (!(getClipboardFormatTag() == CFTAG_WINDOWS))
+ throw new HPSFException("Clipboard Format Tag of Thumbnail must " +
+ "be CFTAG_WINDOWS.");
+
+ return LittleEndian.getInt(getThumbnail(), OFFSET_CF);
+ }
+
+
+
+ /**
+ *
Returns the Thumbnail as a byte[] of WMF data
+ * if the Thumbnail's Clipboard Format Tag is {@link
+ * #CFTAG_WINDOWS CFTAG_WINDOWS} and its Clipboard Format is
+ * {@link #CF_METAFILEPICT CF_METAFILEPICT}
This
+ * byte[] is in the traditional WMF file, not the
+ * clipboard-specific version with special headers.
+ *
+ * @return A WMF image of the Thumbnail
+ * @throws HPSFException if the Thumbnail isn't CFTAG_WINDOWS and
+ * CF_METAFILEPICT
+ */
+ public byte[] getThumbnailAsWMF() throws HPSFException
+ {
+ if (!(getClipboardFormatTag() == CFTAG_WINDOWS))
+ throw new HPSFException("Clipboard Format Tag of Thumbnail must " +
+ "be CFTAG_WINDOWS.");
+ if (!(getClipboardFormat() == CF_METAFILEPICT)) {
+ throw new HPSFException("Clipboard Format of Thumbnail must " +
+ "be CF_METAFILEPICT.");
+ }
+ byte[] thumbnail = getThumbnail();
+ int wmfImageLength = thumbnail.length - OFFSET_WMFDATA;
+ byte[] wmfImage = new byte[wmfImageLength];
+ System.arraycopy(thumbnail,
+ OFFSET_WMFDATA,
+ wmfImage,
+ 0,
+ wmfImageLength);
+ return wmfImage;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/TypeWriter.java b/trunk/src/java/org/apache/poi/hpsf/TypeWriter.java
new file mode 100644
index 000000000..16270eb1d
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/TypeWriter.java
@@ -0,0 +1,187 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.util.LittleEndian;
+
+/**
+ *
Class for writing little-endian data and more.
+ */
+public class TypeWriter
+{
+
+ /**
+ *
Writes a two-byte value (short) to an output stream.
+ *
+ * @param out The stream to write to.
+ * @param n The value to write.
+ * @return The number of bytes that have been written.
+ * @exception IOException if an I/O error occurs
+ */
+ public static int writeToStream( final OutputStream out, final short n )
+ throws IOException
+ {
+ LittleEndian.putShort( out, n ); // FIXME: unsigned
+ return LittleEndian.SHORT_SIZE;
+ }
+
+ /**
+ *
Writes a four-byte value to an output stream.
+ *
+ * @param out The stream to write to.
+ * @param n The value to write.
+ * @exception IOException if an I/O error occurs
+ * @return The number of bytes written to the output stream.
+ */
+ public static int writeToStream( final OutputStream out, final int n )
+ throws IOException
+ {
+ LittleEndian.putInt( n, out );
+ return LittleEndian.INT_SIZE;
+ }
+
+ /**
+ *
Writes a eight-byte value to an output stream.
+ *
+ * @param out The stream to write to.
+ * @param n The value to write.
+ * @exception IOException if an I/O error occurs
+ * @return The number of bytes written to the output stream.
+ */
+ public static int writeToStream( final OutputStream out, final long n )
+ throws IOException
+ {
+ LittleEndian.putLong( n, out );
+ return LittleEndian.LONG_SIZE;
+ }
+
+ /**
+ *
Writes an unsigned two-byte value to an output stream.
+ *
+ * @param out The stream to write to
+ * @param n The value to write
+ * @exception IOException if an I/O error occurs
+ */
+ public static void writeUShortToStream( final OutputStream out, final int n )
+ throws IOException
+ {
+ int high = n & 0xFFFF0000;
+ if ( high != 0 )
+ throw new IllegalPropertySetDataException( "Value " + n
+ + " cannot be represented by 2 bytes." );
+ LittleEndian.putUShort( n, out );
+ }
+
+ /**
+ *
Writes an unsigned four-byte value to an output stream.
+ *
+ * @param out The stream to write to.
+ * @param n The value to write.
+ * @return The number of bytes that have been written to the output stream.
+ * @exception IOException if an I/O error occurs
+ */
+ public static int writeUIntToStream( final OutputStream out, final long n )
+ throws IOException
+ {
+ long high = n & 0xFFFFFFFF00000000L;
+ if ( high != 0 && high != 0xFFFFFFFF00000000L )
+ throw new IllegalPropertySetDataException( "Value " + n
+ + " cannot be represented by 4 bytes." );
+ LittleEndian.putUInt( n, out );
+ return LittleEndian.INT_SIZE;
+ }
+
+ /**
+ *
Writes a 16-byte {@link ClassID} to an output stream.
+ *
+ * @param out The stream to write to
+ * @param n The value to write
+ * @return The number of bytes written
+ * @exception IOException if an I/O error occurs
+ */
+ public static int writeToStream(final OutputStream out, final ClassID n)
+ throws IOException
+ {
+ byte[] b = new byte[16];
+ n.write(b, 0);
+ out.write(b, 0, b.length);
+ return b.length;
+ }
+
+
+
+ /**
+ *
Writes an array of {@link Property} instances to an output stream
+ * according to the Horrible Property Stream Format.
+ *
+ * @param out The stream to write to
+ * @param properties The array to write to the stream
+ * @param codepage The codepage number to use for writing strings
+ * @exception IOException if an I/O error occurs
+ * @throws UnsupportedVariantTypeException if HPSF does not support some
+ * variant type.
+ */
+ public static void writeToStream(final OutputStream out,
+ final Property[] properties,
+ final int codepage)
+ throws IOException, UnsupportedVariantTypeException
+ {
+ /* If there are no properties don't write anything. */
+ if (properties == null)
+ return;
+
+ /* Write the property list. This is a list containing pairs of property
+ * ID and offset into the stream. */
+ for (int i = 0; i < properties.length; i++)
+ {
+ final Property p = properties[i];
+ writeUIntToStream(out, p.getID());
+ writeUIntToStream(out, p.getSize());
+ }
+
+ /* Write the properties themselves. */
+ for (int i = 0; i < properties.length; i++)
+ {
+ final Property p = properties[i];
+ long type = p.getType();
+ writeUIntToStream(out, type);
+ VariantSupport.write(out, (int) type, p.getValue(), codepage);
+ }
+ }
+
+
+
+ /**
+ *
Writes a double value value to an output stream.
+ *
+ * @param out The stream to write to.
+ * @param n The value to write.
+ * @exception IOException if an I/O error occurs
+ * @return The number of bytes written to the output stream.
+ */
+ public static int writeToStream( final OutputStream out, final double n )
+ throws IOException
+ {
+ LittleEndian.putDouble( n, out );
+ return LittleEndian.DOUBLE_SIZE;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/TypedPropertyValue.java b/trunk/src/java/org/apache/poi/hpsf/TypedPropertyValue.java
new file mode 100644
index 000000000..3093a10af
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/TypedPropertyValue.java
@@ -0,0 +1,241 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+@Internal
+class TypedPropertyValue
+{
+ private static final POILogger logger = POILogFactory
+ .getLogger( TypedPropertyValue.class );
+
+ private int _type;
+
+ private Object _value;
+
+ TypedPropertyValue()
+ {
+ }
+
+ TypedPropertyValue( byte[] data, int startOffset )
+ {
+ read( data, startOffset );
+ }
+
+ TypedPropertyValue( int type, Object value )
+ {
+ _type = type;
+ _value = value;
+ }
+
+ Object getValue()
+ {
+ return _value;
+ }
+
+ int read( byte[] data, int startOffset )
+ {
+ int offset = startOffset;
+
+ _type = LittleEndian.getShort( data, offset );
+ offset += LittleEndian.SHORT_SIZE;
+
+ short padding = LittleEndian.getShort( data, offset );
+ offset += LittleEndian.SHORT_SIZE;
+ if ( padding != 0 )
+ {
+ logger.log( POILogger.WARN, "TypedPropertyValue padding at offset "
+ + offset + " MUST be 0, but it's value is " + padding );
+ }
+
+ offset += readValue( data, offset );
+
+ return offset - startOffset;
+ }
+
+ int readValue( byte[] data, int offset ) // NOSONAR
+ {
+ switch ( _type )
+ {
+ case Variant.VT_EMPTY:
+ case Variant.VT_NULL:
+ _value = null;
+ return 0;
+
+ case Variant.VT_R4:
+ case Variant.VT_I2:
+ _value = Short.valueOf( LittleEndian.getShort( data, offset ) );
+ return 4;
+
+ case Variant.VT_INT:
+ case Variant.VT_I4:
+ _value = Integer.valueOf( LittleEndian.getInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_R8:
+ _value = Double.valueOf( LittleEndian.getDouble( data, offset ) );
+ return 8;
+
+ case Variant.VT_CY:
+ _value = new Currency( data, offset );
+ return Currency.SIZE;
+
+ case Variant.VT_DATE:
+ _value = new Date( data, offset );
+ return Date.SIZE;
+
+ case Variant.VT_BSTR:
+ _value = new CodePageString( data, offset );
+ return ( (CodePageString) _value ).getSize();
+
+ case Variant.VT_BOOL:
+ _value = new VariantBool( data, offset );
+ return VariantBool.SIZE;
+
+ case Variant.VT_DECIMAL:
+ _value = new Decimal( data, offset );
+ return Decimal.SIZE;
+
+ case Variant.VT_I1:
+ _value = Byte.valueOf( data[offset] );
+ return 1;
+
+ case Variant.VT_UI1:
+ _value = Short.valueOf( LittleEndian.getUByte( data, offset ) );
+ return 2;
+
+ case Variant.VT_UI2:
+ _value = Integer.valueOf( LittleEndian.getUShort( data, offset ) );
+ return 4;
+
+ case Variant.VT_UINT:
+ case Variant.VT_UI4:
+ case Variant.VT_ERROR:
+ _value = Long.valueOf( LittleEndian.getUInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_I8:
+ _value = Long.valueOf( LittleEndian.getLong( data, offset ) );
+ return 8;
+
+ case Variant.VT_UI8:
+ _value = LittleEndian.getByteArray( data, offset, 8 );
+ return 8;
+
+ case Variant.VT_LPSTR:
+ _value = new CodePageString( data, offset );
+ return ( (CodePageString) _value ).getSize();
+
+ case Variant.VT_LPWSTR:
+ _value = new UnicodeString( data, offset );
+ return ( (UnicodeString) _value ).getSize();
+
+ case Variant.VT_FILETIME:
+ _value = new Filetime( data, offset );
+ return Filetime.SIZE;
+
+ case Variant.VT_BLOB:
+ _value = new Blob( data, offset );
+ return ( (Blob) _value ).getSize();
+
+ case Variant.VT_STREAM:
+ case Variant.VT_STORAGE:
+ case Variant.VT_STREAMED_OBJECT:
+ case Variant.VT_STORED_OBJECT:
+ _value = new IndirectPropertyName( data, offset );
+ return ( (IndirectPropertyName) _value ).getSize();
+
+ case Variant.VT_BLOB_OBJECT:
+ _value = new Blob( data, offset );
+ return ( (Blob) _value ).getSize();
+
+ case Variant.VT_CF:
+ _value = new ClipboardData( data, offset );
+ return ( (ClipboardData) _value ).getSize();
+
+ case Variant.VT_CLSID:
+ _value = new GUID( data, offset );
+ return GUID.SIZE;
+
+ case Variant.VT_VERSIONED_STREAM:
+ _value = new VersionedStream( data, offset );
+ return ( (VersionedStream) _value ).getSize();
+
+ case Variant.VT_VECTOR | Variant.VT_I2:
+ case Variant.VT_VECTOR | Variant.VT_I4:
+ case Variant.VT_VECTOR | Variant.VT_R4:
+ case Variant.VT_VECTOR | Variant.VT_R8:
+ case Variant.VT_VECTOR | Variant.VT_CY:
+ case Variant.VT_VECTOR | Variant.VT_DATE:
+ case Variant.VT_VECTOR | Variant.VT_BSTR:
+ case Variant.VT_VECTOR | Variant.VT_ERROR:
+ case Variant.VT_VECTOR | Variant.VT_BOOL:
+ case Variant.VT_VECTOR | Variant.VT_VARIANT:
+ case Variant.VT_VECTOR | Variant.VT_I1:
+ case Variant.VT_VECTOR | Variant.VT_UI1:
+ case Variant.VT_VECTOR | Variant.VT_UI2:
+ case Variant.VT_VECTOR | Variant.VT_UI4:
+ case Variant.VT_VECTOR | Variant.VT_I8:
+ case Variant.VT_VECTOR | Variant.VT_UI8:
+ case Variant.VT_VECTOR | Variant.VT_LPSTR:
+ case Variant.VT_VECTOR | Variant.VT_LPWSTR:
+ case Variant.VT_VECTOR | Variant.VT_FILETIME:
+ case Variant.VT_VECTOR | Variant.VT_CF:
+ case Variant.VT_VECTOR | Variant.VT_CLSID:
+ _value = new Vector( (short) ( _type & 0x0FFF ) );
+ return ( (Vector) _value ).read( data, offset );
+
+ case Variant.VT_ARRAY | Variant.VT_I2:
+ case Variant.VT_ARRAY | Variant.VT_I4:
+ case Variant.VT_ARRAY | Variant.VT_R4:
+ case Variant.VT_ARRAY | Variant.VT_R8:
+ case Variant.VT_ARRAY | Variant.VT_CY:
+ case Variant.VT_ARRAY | Variant.VT_DATE:
+ case Variant.VT_ARRAY | Variant.VT_BSTR:
+ case Variant.VT_ARRAY | Variant.VT_ERROR:
+ case Variant.VT_ARRAY | Variant.VT_BOOL:
+ case Variant.VT_ARRAY | Variant.VT_VARIANT:
+ case Variant.VT_ARRAY | Variant.VT_DECIMAL:
+ case Variant.VT_ARRAY | Variant.VT_I1:
+ case Variant.VT_ARRAY | Variant.VT_UI1:
+ case Variant.VT_ARRAY | Variant.VT_UI2:
+ case Variant.VT_ARRAY | Variant.VT_UI4:
+ case Variant.VT_ARRAY | Variant.VT_INT:
+ case Variant.VT_ARRAY | Variant.VT_UINT:
+ _value = new Array();
+ return ( (Array) _value ).read( data, offset );
+
+ default:
+ throw new UnsupportedOperationException(
+ "Unknown (possibly, incorrect) TypedPropertyValue type: "
+ + _type );
+ }
+ }
+
+ int readValuePadded( byte[] data, int offset )
+ {
+ int nonPadded = readValue( data, offset );
+ return ( nonPadded & 0x03 ) == 0 ? nonPadded : nonPadded
+ + ( 4 - ( nonPadded & 0x03 ) );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java b/trunk/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java
new file mode 100644
index 000000000..54efe0f44
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/UnexpectedPropertySetTypeException.java
@@ -0,0 +1,79 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if a certain type of property set is
+ * expected (e.g. a Document Summary Information) but the provided
+ * property set is not of that type.
+ *
+ *
The constructors of this class are analogous to those of its
+ * superclass and documented there.
Creates a new {@link UnexpectedPropertySetTypeException} with a
+ * reason.
+ *
+ * @param reason The reason, i.e. a throwable that indirectly
+ * caused this exception.
+ */
+ public UnexpectedPropertySetTypeException(final Throwable reason)
+ {
+ super(reason);
+ }
+
+
+ /**
+ *
Creates an {@link UnexpectedPropertySetTypeException} with a message
+ * string and a reason.
+ *
+ * @param msg The message string.
+ * @param reason The reason, i.e. a throwable that indirectly
+ * caused this exception.
+ */
+ public UnexpectedPropertySetTypeException(final String msg,
+ final Throwable reason)
+ {
+ super(msg, reason);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/UnicodeString.java b/trunk/src/java/org/apache/poi/hpsf/UnicodeString.java
new file mode 100644
index 000000000..0d3cdbf29
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/UnicodeString.java
@@ -0,0 +1,125 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.StringUtil;
+
+@Internal
+class UnicodeString {
+ private final static POILogger logger =
+ POILogFactory.getLogger( UnicodeString.class );
+
+ private byte[] _value;
+
+ UnicodeString(byte[] data, int offset) {
+ int length = LittleEndian.getInt( data, offset );
+ int dataOffset = offset + LittleEndian.INT_SIZE;
+
+ if (! validLength(length, data, dataOffset)) {
+ // If the length looks wrong, this might be because the offset is sometimes expected
+ // to be on a 4 byte boundary. Try checking with that if so, rather than blowing up with
+ // and ArrayIndexOutOfBoundsException below
+ boolean valid = false;
+ int past4byte = offset % 4;
+ if (past4byte != 0) {
+ offset = offset + past4byte;
+ length = LittleEndian.getInt( data, offset );
+ dataOffset = offset + LittleEndian.INT_SIZE;
+
+ valid = validLength(length, data, dataOffset);
+ }
+
+ if (!valid) {
+ throw new IllegalPropertySetDataException(
+ "UnicodeString started at offset #" + offset +
+ " is not NULL-terminated" );
+ }
+ }
+
+ if ( length == 0 )
+ {
+ _value = new byte[0];
+ return;
+ }
+
+ _value = LittleEndian.getByteArray( data, dataOffset, length * 2 );
+ }
+
+ /**
+ * Checks to see if the specified length seems valid,
+ * given the amount of data available still to read,
+ * and the requirement that the string be NULL-terminated
+ */
+ boolean validLength(int length, byte[] data, int offset) {
+ if (length == 0) {
+ return true;
+ }
+
+ int endOffset = offset + (length * 2);
+ if (endOffset <= data.length) {
+ // Data Length is OK, ensure it's null terminated too
+ if (data[endOffset-1] == 0 && data[endOffset-2] == 0) {
+ // Length looks plausible
+ return true;
+ }
+ }
+
+ // Something's up/invalid with that length for the given data+offset
+ return false;
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+
+ byte[] getValue()
+ {
+ return _value;
+ }
+
+ String toJavaString()
+ {
+ if ( _value.length == 0 )
+ return null;
+
+ String result = StringUtil.getFromUnicodeLE( _value, 0,
+ _value.length >> 1 );
+
+ final int terminator = result.indexOf( '\0' );
+ if ( terminator == -1 )
+ {
+ logger.log(
+ POILogger.WARN,
+ "String terminator (\\0) for UnicodeString property value not found."
+ + "Continue without trimming and hope for the best." );
+ return result;
+ }
+ if ( terminator != result.length() - 1 )
+ {
+ logger.log(
+ POILogger.WARN,
+ "String terminator (\\0) for UnicodeString property value occured before the end of string. "
+ + "Trimming and hope for the best." );
+ }
+ return result.substring( 0, terminator );
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java b/trunk/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java
new file mode 100644
index 000000000..12afdcb4c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java
@@ -0,0 +1,53 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.HexDump;
+
+/**
+ *
This exception is thrown if HPSF encounters a variant type that isn't
+ * supported yet. Although a variant type is unsupported the value can still be
+ * retrieved using the {@link VariantTypeException#getValue} method.
+ *
+ * @param variantType The unsupported variant type
+ * @param value The value who's variant type is not yet supported
+ */
+ public UnsupportedVariantTypeException(final long variantType,
+ final Object value)
+ {
+ super(variantType, value,
+ "HPSF does not yet support the variant type " + variantType +
+ " (" + Variant.getVariantName(variantType) + ", " +
+ HexDump.toHex(variantType) + "). If you want support for " +
+ "this variant type in one of the next POI releases please " +
+ "submit a request for enhancement (RFE) to " +
+ "! Thank you!");
+ }
+
+
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Util.java b/trunk/src/java/org/apache/poi/hpsf/Util.java
new file mode 100644
index 000000000..5d75cb986
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Util.java
@@ -0,0 +1,357 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.poi.util.SuppressForbidden;
+
+/**
+ *
Provides various static utility methods.
+ */
+public class Util
+{
+
+ /**
+ *
Checks whether two byte arrays a and b
+ * are equal. They are equal
+ *
+ *
+ *
+ *
if they have the same length and
+ *
+ *
if for each i with
+ * i >= 0 and
+ * i < a.length holds
+ * a[i] == b[i].
+ *
+ *
+ *
+ * @param a The first byte array
+ * @param b The first byte array
+ * @return true if the byte arrays are equal, else
+ * false
+ */
+ public static boolean equal(final byte[] a, final byte[] b)
+ {
+ if (a.length != b.length)
+ return false;
+ for (int i = 0; i < a.length; i++)
+ if (a[i] != b[i])
+ return false;
+ return true;
+ }
+
+
+
+ /**
+ *
Copies a part of a byte array into another byte array.
+ *
+ * @param src The source byte array.
+ * @param srcOffset Offset in the source byte array.
+ * @param length The number of bytes to copy.
+ * @param dst The destination byte array.
+ * @param dstOffset Offset in the destination byte array.
+ */
+ public static void copy(final byte[] src, final int srcOffset,
+ final int length, final byte[] dst,
+ final int dstOffset)
+ {
+ for (int i = 0; i < length; i++)
+ dst[dstOffset + i] = src[srcOffset + i];
+ }
+
+
+
+ /**
+ *
Concatenates the contents of several byte arrays into a
+ * single one.
+ *
+ * @param byteArrays The byte arrays to be concatened.
+ * @return A new byte array containing the concatenated byte
+ * arrays.
+ */
+ public static byte[] cat(final byte[][] byteArrays)
+ {
+ int capacity = 0;
+ for (int i = 0; i < byteArrays.length; i++)
+ capacity += byteArrays[i].length;
+ final byte[] result = new byte[capacity];
+ int r = 0;
+ for (int i = 0; i < byteArrays.length; i++)
+ for (int j = 0; j < byteArrays[i].length; j++)
+ result[r++] = byteArrays[i][j];
+ return result;
+ }
+
+
+
+ /**
+ *
Copies bytes from a source byte array into a new byte
+ * array.
+ *
+ * @param src Copy from this byte array.
+ * @param offset Start copying here.
+ * @param length Copy this many bytes.
+ * @return The new byte array. Its length is number of copied bytes.
+ */
+ public static byte[] copy(final byte[] src, final int offset,
+ final int length)
+ {
+ final byte[] result = new byte[length];
+ copy(src, offset, length, result, 0);
+ return result;
+ }
+
+
+
+ /**
+ *
The difference between the Windows epoch (1601-01-01
+ * 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in
+ * milliseconds: 11644473600000L. (Use your favorite spreadsheet
+ * program to verify the correctness of this value. By the way,
+ * did you notice that you can tell from the epochs which
+ * operating system is the modern one? :-))
+ */
+ public static final long EPOCH_DIFF = 11644473600000L;
+
+
+ /**
+ *
Converts a Windows FILETIME into a {@link Date}. The Windows
+ * FILETIME structure holds a date and time associated with a
+ * file. The structure identifies a 64-bit integer specifying the
+ * number of 100-nanosecond intervals which have passed since
+ * January 1, 1601. This 64-bit value is split into the two double
+ * words stored in the structure.
+ *
+ * @param high The higher double word of the FILETIME structure.
+ * @param low The lower double word of the FILETIME structure.
+ * @return The Windows FILETIME as a {@link Date}.
+ */
+ public static Date filetimeToDate(final int high, final int low)
+ {
+ final long filetime = ((long) high) << 32 | (low & 0xffffffffL);
+ return filetimeToDate(filetime);
+ }
+
+ /**
+ *
Converts a Windows FILETIME into a {@link Date}. The Windows
+ * FILETIME structure holds a date and time associated with a
+ * file. The structure identifies a 64-bit integer specifying the
+ * number of 100-nanosecond intervals which have passed since
+ * January 1, 1601.
+ *
+ * @param filetime The filetime to convert.
+ * @return The Windows FILETIME as a {@link Date}.
+ */
+ public static Date filetimeToDate(final long filetime)
+ {
+ final long ms_since_16010101 = filetime / (1000 * 10);
+ final long ms_since_19700101 = ms_since_16010101 - EPOCH_DIFF;
+ return new Date(ms_since_19700101);
+ }
+
+
+
+ /**
+ *
Converts a {@link Date} into a filetime.
+ *
+ * @param date The date to be converted
+ * @return The filetime
+ *
+ * @see #filetimeToDate(long)
+ * @see #filetimeToDate(int, int)
+ */
+ public static long dateToFileTime(final Date date)
+ {
+ long ms_since_19700101 = date.getTime();
+ long ms_since_16010101 = ms_since_19700101 + EPOCH_DIFF;
+ return ms_since_16010101 * (1000 * 10);
+ }
+
+
+ /**
+ *
Checks whether two collections are equal. Two collections
+ * C1 and C2 are equal, if the following conditions
+ * are true:
+ *
+ *
+ *
+ *
For each c1i (element of C1) there
+ * is a c2j (element of C2), and
+ * c1i equals c2j.
+ *
+ *
For each c2i (element of C2) there
+ * is a c1j (element of C1) and
+ * c2i equals c1j.
+ *
+ *
+ *
+ * @param c1 the first collection
+ * @param c2 the second collection
+ * @return true if the collections are equal, else
+ * false.
+ */
+ public static boolean equals(Collection> c1, Collection> c2)
+ {
+ Object[] o1 = c1.toArray();
+ Object[] o2 = c2.toArray();
+ return internalEquals(o1, o2);
+ }
+
+
+
+ /**
+ *
Compares to object arrays with regarding the objects' order. For
+ * example, [1, 2, 3] and [2, 1, 3] are equal.
+ *
+ * @param c1 The first object array.
+ * @param c2 The second object array.
+ * @return true if the object arrays are equal,
+ * false if they are not.
+ */
+ public static boolean equals(Object[] c1, Object[] c2)
+ {
+ final Object[] o1 = c1.clone();
+ final Object[] o2 = c2.clone();
+ return internalEquals(o1, o2);
+ }
+
+ private static boolean internalEquals(Object[] o1, Object[] o2)
+ {
+ for (int i1 = 0; i1 < o1.length; i1++)
+ {
+ final Object obj1 = o1[i1];
+ boolean matchFound = false;
+ for (int i2 = 0; !matchFound && i2 < o1.length; i2++)
+ {
+ final Object obj2 = o2[i2];
+ if (obj1.equals(obj2))
+ {
+ matchFound = true;
+ o2[i2] = null;
+ }
+ }
+ if (!matchFound)
+ return false;
+ }
+ return true;
+ }
+
+
+
+ /**
+ *
Pads a byte array with 0x00 bytes so that its length is a multiple of
+ * 4.
+ *
+ * @param ba The byte array to pad.
+ * @return The padded byte array.
+ */
+ public static byte[] pad4(final byte[] ba)
+ {
+ final int PAD = 4;
+ final byte[] result;
+ int l = ba.length % PAD;
+ if (l == 0)
+ result = ba;
+ else
+ {
+ l = PAD - l;
+ result = new byte[ba.length + l];
+ System.arraycopy(ba, 0, result, 0, ba.length);
+ }
+ return result;
+ }
+
+
+
+ /**
+ *
Pads a character array with 0x0000 characters so that its length is a
+ * multiple of 4.
+ *
+ * @param ca The character array to pad.
+ * @return The padded character array.
+ */
+ public static char[] pad4(final char[] ca)
+ {
+ final int PAD = 4;
+ final char[] result;
+ int l = ca.length % PAD;
+ if (l == 0)
+ result = ca;
+ else
+ {
+ l = PAD - l;
+ result = new char[ca.length + l];
+ System.arraycopy(ca, 0, result, 0, ca.length);
+ }
+ return result;
+ }
+
+
+
+ /**
+ *
Pads a string with 0x0000 characters so that its length is a
+ * multiple of 4.
+ *
+ * @param s The string to pad.
+ * @return The padded string as a character array.
+ */
+ public static char[] pad4(final String s)
+ {
+ return pad4(s.toCharArray());
+ }
+
+
+
+ /**
+ *
Returns a textual representation of a {@link Throwable}, including a
+ * stacktrace.
+ *
+ * @param t The {@link Throwable}
+ *
+ * @return a string containing the output of a call to
+ * t.printStacktrace().
+ */
+ @SuppressForbidden("uses printStackTrace")
+ public static String toString(final Throwable t)
+ {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+ t.printStackTrace(pw);
+ pw.close();
+ try
+ {
+ sw.close();
+ return sw.toString();
+ }
+ catch (IOException e)
+ {
+ final StringBuffer b = new StringBuffer(t.getMessage());
+ b.append("\n");
+ b.append("Could not create a stacktrace. Reason: ");
+ b.append(e.getMessage());
+ return b.toString();
+ }
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Variant.java b/trunk/src/java/org/apache/poi/hpsf/Variant.java
new file mode 100644
index 000000000..3f606043a
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Variant.java
@@ -0,0 +1,517 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
In the variant types descriptions the following shortcuts are
+ * used: [V] - may appear in a VARIANT,
+ * [T] - may appear in a TYPEDESC,
+ * [P] - may appear in an OLE property set,
+ * [S] - may appear in a Safe Array.
+ */
+public class Variant
+{
+
+ /**
+ *
[V][P] Nothing, i.e. not a single byte of data.
+ */
+ public static final int VT_EMPTY = 0;
+
+ /**
+ *
[V][P] SQL style Null.
+ */
+ public static final int VT_NULL = 1;
+
+ /**
+ *
[V][T][P][S] 2 byte signed int.
+ */
+ public static final int VT_I2 = 2;
+
+ /**
+ *
[V][T][P][S] 4 byte signed int.
+ */
+ public static final int VT_I4 = 3;
+
+ /**
+ *
[V][T][P][S] 4 byte real.
+ */
+ public static final int VT_R4 = 4;
+
+ /**
+ *
[V][T][P][S] 8 byte real.
+ */
+ public static final int VT_R8 = 5;
+
+ /**
+ *
[V][T][P][S] currency. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_CY = 6;
+
+ /**
+ *
[V][T][P][S] date. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_DATE = 7;
+
+ /**
+ *
[V][T][P][S] OLE Automation string. How long is this? How is it
+ * to be interpreted?
+ */
+ public static final int VT_BSTR = 8;
+
+ /**
+ *
[V][T][P][S] IDispatch *. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_DISPATCH = 9;
+
+ /**
+ *
[V][T][S] SCODE. How
+ * long is this? How is it to be interpreted?
+ */
+ public static final int VT_ERROR = 10;
+
+ /**
+ *
[V][T][P][S] True=-1, False=0.
+ */
+ public static final int VT_BOOL = 11;
+
+ /**
+ *
[V][T][P][S] VARIANT *. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_VARIANT = 12;
+
+ /**
+ *
[V][T][S] IUnknown *. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_UNKNOWN = 13;
+
+ /**
+ *
[V][T][S] 16 byte fixed point.
+ */
+ public static final int VT_DECIMAL = 14;
+
+ /**
+ *
[T] signed char.
+ */
+ public static final int VT_I1 = 16;
+
+ /**
+ *
[V][T][P][S] unsigned char.
+ */
+ public static final int VT_UI1 = 17;
+
+ /**
+ *
[T][P] unsigned short.
+ */
+ public static final int VT_UI2 = 18;
+
+ /**
+ *
[T][P] unsigned int.
+ */
+ public static final int VT_UI4 = 19;
+
+ /**
+ *
[T][P] signed 64-bit int.
+ */
+ public static final int VT_I8 = 20;
+
+ /**
+ *
[T][P] unsigned 64-bit int.
+ */
+ public static final int VT_UI8 = 21;
+
+ /**
+ *
[T] signed machine int.
+ */
+ public static final int VT_INT = 22;
+
+ /**
+ *
[T] unsigned machine int.
+ */
+ public static final int VT_UINT = 23;
+
+ /**
+ *
[T] C style void.
+ */
+ public static final int VT_VOID = 24;
+
+ /**
+ *
[T] Standard return type. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_HRESULT = 25;
+
+ /**
+ *
[T] pointer type. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_PTR = 26;
+
+ /**
+ *
[T] (use VT_ARRAY in VARIANT).
+ */
+ public static final int VT_SAFEARRAY = 27;
+
+ /**
+ *
[T] C style array. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_CARRAY = 28;
+
+ /**
+ *
[T] user defined type. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_USERDEFINED = 29;
+
+ /**
+ *
[T][P] null terminated string.
+ */
+ public static final int VT_LPSTR = 30;
+
+ /**
+ *
[T][P] wide (Unicode) null terminated string.
+ */
+ public static final int VT_LPWSTR = 31;
+
+ /**
+ *
[P] FILETIME. The FILETIME structure holds a date and time
+ * associated with a file. The structure identifies a 64-bit
+ * integer specifying the number of 100-nanosecond intervals which
+ * have passed since January 1, 1601. This 64-bit value is split
+ * into the two dwords stored in the structure.
+ */
+ public static final int VT_FILETIME = 64;
+
+ /**
+ *
[P] Length prefixed bytes.
+ */
+ public static final int VT_BLOB = 65;
+
+ /**
+ *
[P] Name of the stream follows.
+ */
+ public static final int VT_STREAM = 66;
+
+ /**
+ *
[P] Name of the storage follows.
+ */
+ public static final int VT_STORAGE = 67;
+
+ /**
+ *
[P] Stream contains an object. How long is this? How is it
+ * to be interpreted?
+ */
+ public static final int VT_STREAMED_OBJECT = 68;
+
+ /**
+ *
[P] Storage contains an object. How long is this? How is it
+ * to be interpreted?
+ */
+ public static final int VT_STORED_OBJECT = 69;
+
+ /**
+ *
[P] Blob contains an object. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_BLOB_OBJECT = 70;
+
+ /**
+ *
[P] Clipboard format. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_CF = 71;
+
+ /**
+ *
[P] A Class ID.
+ *
+ *
It consists of a 32 bit unsigned integer indicating the size
+ * of the structure, a 32 bit signed integer indicating (Clipboard
+ * Format Tag) indicating the type of data that it contains, and
+ * then a byte array containing the data.
+ *
+ *
The valid Clipboard Format Tags are:
+ *
+ *
+ *
{@link Thumbnail#CFTAG_WINDOWS}
+ *
{@link Thumbnail#CFTAG_MACINTOSH}
+ *
{@link Thumbnail#CFTAG_NODATA}
+ *
{@link Thumbnail#CFTAG_FMTID}
+ *
+ *
+ *
typedef struct tagCLIPDATA {
+ * // cbSize is the size of the buffer pointed to
+ * // by pClipData, plus sizeof(ulClipFmt)
+ * ULONG cbSize;
+ * long ulClipFmt;
+ * BYTE* pClipData;
+ * } CLIPDATA;
+ */
+ public static final int VT_CLSID = 72;
+
+ /**
+ * "MUST be a VersionedStream. The storage representing the (non-simple)
+ * property set MUST have a stream element with the name in the StreamName
+ * field." -- [MS-OLEPS] -- v20110920; Object Linking and Embedding (OLE)
+ * Property Set Data Structures; page 24 / 63
+ */
+ public static final int VT_VERSIONED_STREAM = 0x0049;
+
+ /**
+ *
[P] simple counted array. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_VECTOR = 0x1000;
+
+ /**
+ *
[V] SAFEARRAY*. How
+ * long is this? How is it to be interpreted?
+ */
+ public static final int VT_ARRAY = 0x2000;
+
+ /**
+ *
[V] void* for local use. How long is this? How is it to be
+ * interpreted?
+ */
+ public static final int VT_BYREF = 0x4000;
+
+ /**
+ *
FIXME (3): Document this!
+ */
+ public static final int VT_RESERVED = 0x8000;
+
+ /**
+ *
FIXME (3): Document this!
+ */
+ public static final int VT_ILLEGAL = 0xFFFF;
+
+ /**
+ *
FIXME (3): Document this!
+ */
+ public static final int VT_ILLEGALMASKED = 0xFFF;
+
+ /**
+ *
FIXME (3): Document this!
+ */
+ public static final int VT_TYPEMASK = 0xFFF;
+
+
+
+ /**
+ *
Maps the numbers denoting the variant types to their corresponding
+ * variant type names.
Returns the variant type name associated with a variant type
+ * number.
+ *
+ * @param variantType The variant type number
+ * @return The variant type name or the string "unknown variant type"
+ */
+ public static String getVariantName(final long variantType)
+ {
+ final String name = numberToName.get(Long.valueOf(variantType));
+ return name != null ? name : "unknown variant type";
+ }
+
+ /**
+ *
Returns a variant type's length.
+ *
+ * @param variantType The variant type number
+ * @return The length of the variant type's data in bytes. If the length is
+ * variable, i.e. the length of a string, -1 is returned. If HPSF does not
+ * know the length, -2 is returned. The latter usually indicates an
+ * unsupported variant type.
+ */
+ public static int getVariantLength(final long variantType)
+ {
+ final Long key = Long.valueOf((int) variantType);
+ final Integer length = numberToLength.get(key);
+ if (length == null)
+ return -2;
+ return length.intValue();
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/VariantBool.java b/trunk/src/java/org/apache/poi/hpsf/VariantBool.java
new file mode 100644
index 000000000..962674de7
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/VariantBool.java
@@ -0,0 +1,55 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+@Internal
+class VariantBool {
+ private final static POILogger logger = POILogFactory.getLogger( VariantBool.class );
+
+ static final int SIZE = 2;
+
+ private boolean _value;
+
+ VariantBool( byte[] data, int offset ) {
+ short value = LittleEndian.getShort( data, offset );
+ switch (value) {
+ case 0:
+ _value = false;
+ break;
+ case -1:
+ _value = true;
+ break;
+ default:
+ logger.log( POILogger.WARN, "VARIANT_BOOL value '"+value+"' is incorrect" );
+ _value = true;
+ break;
+ }
+ }
+
+ boolean getValue() {
+ return _value;
+ }
+
+ void setValue( boolean value ) {
+ this._value = value;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/VariantSupport.java b/trunk/src/java/org/apache/poi/hpsf/VariantSupport.java
new file mode 100644
index 000000000..b4816eb91
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/VariantSupport.java
@@ -0,0 +1,427 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.poi.util.CodePageUtil;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ *
Supports reading and writing of variant data.
+ *
+ *
FIXME (3): Reading and writing should be made more
+ * uniform than it is now. The following items should be resolved:
+ *
+ *
+ *
+ *
Reading requires a length parameter that is 4 byte greater than the
+ * actual data, because the variant type field is included.
+ *
+ *
Reading reads from a byte array while writing writes to an byte array
+ * output stream.
Writes a warning to System.err that a variant type is
+ * unsupported by HPSF. Such a warning is written only once for each variant
+ * type. Log messages can be turned on or off by
+ *
+ * @param ex The exception to log
+ */
+ protected static void writeUnsupportedTypeMessage
+ (final UnsupportedVariantTypeException ex)
+ {
+ if (isLogUnsupportedTypes())
+ {
+ if (unsupportedMessage == null)
+ unsupportedMessage = new LinkedList();
+ Long vt = Long.valueOf(ex.getVariantType());
+ if (!unsupportedMessage.contains(vt))
+ {
+ logger.log( POILogger.ERROR, ex.getMessage());
+ unsupportedMessage.add(vt);
+ }
+ }
+ }
+
+
+ /**
+ *
Checks whether HPSF supports the specified variant type. Unsupported
+ * types should be implemented included in the {@link #SUPPORTED_TYPES}
+ * array.
+ *
+ * @see Variant
+ * @param variantType the variant type to check
+ * @return true if HPFS supports this type, else
+ * false
+ */
+ public boolean isSupportedType(final int variantType)
+ {
+ for (int i = 0; i < SUPPORTED_TYPES.length; i++)
+ if (variantType == SUPPORTED_TYPES[i])
+ return true;
+ return false;
+ }
+
+
+
+ /**
+ *
Reads a variant type from a byte array.
+ *
+ * @param src The byte array
+ * @param offset The offset in the byte array where the variant starts
+ * @param length The length of the variant including the variant type field
+ * @param type The variant type to read
+ * @param codepage The codepage to use for non-wide strings
+ * @return A Java object that corresponds best to the variant field. For
+ * example, a VT_I4 is returned as a {@link Long}, a VT_LPSTR as a
+ * {@link String}.
+ * @exception ReadingNotSupportedException if a property is to be written
+ * who's variant type HPSF does not yet support
+ * @exception UnsupportedEncodingException if the specified codepage is not
+ * supported.
+ * @see Variant
+ */
+ public static Object read( final byte[] src, final int offset,
+ final int length, final long type, final int codepage )
+ throws ReadingNotSupportedException, UnsupportedEncodingException
+ {
+ TypedPropertyValue typedPropertyValue = new TypedPropertyValue(
+ (int) type, null );
+ int unpadded;
+ try
+ {
+ unpadded = typedPropertyValue.readValue( src, offset );
+ }
+ catch ( UnsupportedOperationException exc )
+ {
+ int propLength = Math.min( length, src.length - offset );
+ final byte[] v = new byte[propLength];
+ System.arraycopy( src, offset, v, 0, propLength );
+ throw new ReadingNotSupportedException( type, v );
+ }
+
+ switch ( (int) type )
+ {
+ case Variant.VT_EMPTY:
+ case Variant.VT_I4:
+ case Variant.VT_I8:
+ case Variant.VT_R8:
+ /*
+ * we have more property types that can be converted into Java
+ * objects, but current API need to be preserved, and it returns
+ * other types as byte arrays. In future major versions it shall be
+ * changed -- sergey
+ */
+ return typedPropertyValue.getValue();
+
+ case Variant.VT_I2:
+ {
+ /*
+ * also for backward-compatibility with prev. versions of POI
+ * --sergey
+ */
+ return Integer.valueOf( ( (Short) typedPropertyValue.getValue() )
+ .intValue() );
+ }
+ case Variant.VT_FILETIME:
+ {
+ Filetime filetime = (Filetime) typedPropertyValue.getValue();
+ return Util.filetimeToDate( (int) filetime.getHigh(),
+ (int) filetime.getLow() );
+ }
+ case Variant.VT_LPSTR:
+ {
+ CodePageString string = (CodePageString) typedPropertyValue
+ .getValue();
+ return string.getJavaValue( codepage );
+ }
+ case Variant.VT_LPWSTR:
+ {
+ UnicodeString string = (UnicodeString) typedPropertyValue
+ .getValue();
+ return string.toJavaString();
+ }
+ case Variant.VT_CF:
+ {
+ // if(l1 < 0) {
+ /**
+ * YK: reading the ClipboardData packet (VT_CF) is not quite
+ * correct. The size of the data is determined by the first four
+ * bytes of the packet while the current implementation calculates
+ * it in the Section constructor. Test files in Bugzilla 42726 and
+ * 45583 clearly show that this approach does not always work. The
+ * workaround below attempts to gracefully handle such cases instead
+ * of throwing exceptions.
+ *
+ * August 20, 2009
+ */
+ // l1 = LittleEndian.getInt(src, o1); o1 += LittleEndian.INT_SIZE;
+ // }
+ // final byte[] v = new byte[l1];
+ // System.arraycopy(src, o1, v, 0, v.length);
+ // value = v;
+ // break;
+ ClipboardData clipboardData = (ClipboardData) typedPropertyValue
+ .getValue();
+ return clipboardData.toByteArray();
+ }
+
+ case Variant.VT_BOOL:
+ {
+ VariantBool bool = (VariantBool) typedPropertyValue.getValue();
+ return Boolean.valueOf( bool.getValue() );
+ }
+
+ default:
+ {
+ /*
+ * it is not very good, but what can do without breaking current
+ * API? --sergey
+ */
+ final byte[] v = new byte[unpadded];
+ System.arraycopy( src, offset, v, 0, unpadded );
+ throw new ReadingNotSupportedException( type, v );
+ }
+ }
+ }
+
+ /**
+ *
Turns a codepage number into the equivalent character encoding's
+ * name.
+ *
+ * @param codepage The codepage number
+ *
+ * @return The character encoding's name. If the codepage number is 65001,
+ * the encoding name is "UTF-8". All other positive numbers are mapped to
+ * "cp" followed by the number, e.g. if the codepage number is 1252 the
+ * returned character encoding name will be "cp1252".
+ *
+ * @exception UnsupportedEncodingException if the specified codepage is
+ * less than zero.
+ */
+ public static String codepageToEncoding(final int codepage)
+ throws UnsupportedEncodingException
+ {
+ return CodePageUtil.codepageToEncoding(codepage);
+ }
+
+
+ /**
+ *
Writes a variant value to an output stream. This method ensures that
+ * always a multiple of 4 bytes is written.
+ *
+ *
If the codepage is UTF-16, which is encouraged, strings
+ * must always be written as {@link Variant#VT_LPWSTR}
+ * strings, not as {@link Variant#VT_LPSTR} strings. This method ensure this
+ * by converting strings appropriately, if needed.
+ *
+ * @param out The stream to write the value to.
+ * @param type The variant's type.
+ * @param value The variant's value.
+ * @param codepage The codepage to use to write non-wide strings
+ * @return The number of entities that have been written. In many cases an
+ * "entity" is a byte but this is not always the case.
+ * @exception IOException if an I/O exceptions occurs
+ * @exception WritingNotSupportedException if a property is to be written
+ * who's variant type HPSF does not yet support
+ */
+ public static int write(final OutputStream out, final long type,
+ final Object value, final int codepage)
+ throws IOException, WritingNotSupportedException
+ {
+ int length = 0;
+ switch ((int) type)
+ {
+ case Variant.VT_BOOL:
+ {
+ if ( ( (Boolean) value ).booleanValue() )
+ {
+ out.write( 0xff );
+ out.write( 0xff );
+ }
+ else
+ {
+ out.write( 0x00 );
+ out.write( 0x00 );
+ }
+ length += 2;
+ break;
+ }
+ case Variant.VT_LPSTR:
+ {
+ CodePageString codePageString = new CodePageString( (String) value,
+ codepage );
+ length += codePageString.write( out );
+ break;
+ }
+ case Variant.VT_LPWSTR:
+ {
+ final int nrOfChars = ( (String) value ).length() + 1;
+ length += TypeWriter.writeUIntToStream( out, nrOfChars );
+ char[] s = ( (String) value ).toCharArray();
+ for ( int i = 0; i < s.length; i++ )
+ {
+ final int high = ( ( s[i] & 0x0000ff00 ) >> 8 );
+ final int low = ( s[i] & 0x000000ff );
+ final byte highb = (byte) high;
+ final byte lowb = (byte) low;
+ out.write( lowb );
+ out.write( highb );
+ length += 2;
+ }
+ // NullTerminator
+ out.write( 0x00 );
+ out.write( 0x00 );
+ length += 2;
+ break;
+ }
+ case Variant.VT_CF:
+ {
+ final byte[] b = (byte[]) value;
+ out.write(b);
+ length = b.length;
+ break;
+ }
+ case Variant.VT_EMPTY:
+ {
+ length += TypeWriter.writeUIntToStream( out, Variant.VT_EMPTY );
+ break;
+ }
+ case Variant.VT_I2:
+ {
+ length += TypeWriter.writeToStream( out,
+ ( (Integer) value ).shortValue() );
+ break;
+ }
+ case Variant.VT_I4:
+ {
+ if (!(value instanceof Integer))
+ {
+ throw new ClassCastException("Could not cast an object to "
+ + Integer.class.toString() + ": "
+ + value.getClass().toString() + ", "
+ + value.toString());
+ }
+ length += TypeWriter.writeToStream(out,
+ ((Integer) value).intValue());
+ break;
+ }
+ case Variant.VT_I8:
+ {
+ length += TypeWriter.writeToStream(out, ((Long) value).longValue());
+ break;
+ }
+ case Variant.VT_R8:
+ {
+ length += TypeWriter.writeToStream(out,
+ ((Double) value).doubleValue());
+ break;
+ }
+ case Variant.VT_FILETIME:
+ {
+ long filetime = Util.dateToFileTime((Date) value);
+ int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
+ int low = (int) (filetime & 0x00000000FFFFFFFFL);
+ Filetime filetimeValue = new Filetime( low, high);
+ length += filetimeValue.write( out );
+ break;
+ }
+ default:
+ {
+ /* The variant type is not supported yet. However, if the value
+ * is a byte array we can write it nevertheless. */
+ if (value instanceof byte[])
+ {
+ final byte[] b = (byte[]) value;
+ out.write(b);
+ length = b.length;
+ writeUnsupportedTypeMessage
+ (new WritingNotSupportedException(type, value));
+ }
+ else
+ throw new WritingNotSupportedException(type, value);
+ break;
+ }
+ }
+
+ /* pad values to 4-bytes */
+ while ( ( length & 0x3 ) != 0 )
+ {
+ out.write( 0x00 );
+ length++;
+ }
+
+ return length;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/VariantTypeException.java b/trunk/src/java/org/apache/poi/hpsf/VariantTypeException.java
new file mode 100644
index 000000000..c2d2e8f66
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/VariantTypeException.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown if HPSF encounters a problem with a variant type.
+ * Concrete subclasses specifiy the problem further.
+ *
+ * @param variantType The variant type causing the problem
+ * @param value The value who's variant type causes the problem
+ * @param msg A message text describing the problem
+ */
+ public VariantTypeException(final long variantType, final Object value,
+ final String msg)
+ {
+ super(msg);
+ this.variantType = variantType;
+ this.value = value;
+ }
+
+
+
+ /**
+ *
Returns the offending variant type.
+ *
+ * @return the offending variant type.
+ */
+ public long getVariantType()
+ {
+ return variantType;
+ }
+
+
+
+ /**
+ *
Returns the value who's variant type caused the problem.
+ *
+ * @return the value who's variant type caused the problem
+ */
+ public Object getValue()
+ {
+ return value;
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/Vector.java b/trunk/src/java/org/apache/poi/hpsf/Vector.java
new file mode 100644
index 000000000..a1ddb0812
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/Vector.java
@@ -0,0 +1,84 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Holder for vector-type properties
+ *
+ * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
+ */
+@Internal
+class Vector
+{
+ private final short _type;
+
+ private TypedPropertyValue[] _values;
+
+ Vector( byte[] data, int startOffset, short type )
+ {
+ this._type = type;
+ read( data, startOffset );
+ }
+
+ Vector( short type )
+ {
+ this._type = type;
+ }
+
+ int read( byte[] data, int startOffset )
+ {
+ int offset = startOffset;
+
+ final long longLength = LittleEndian.getUInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ if ( longLength > Integer.MAX_VALUE )
+ throw new UnsupportedOperationException( "Vector is too long -- "
+ + longLength );
+ final int length = (int) longLength;
+
+ _values = new TypedPropertyValue[length];
+
+ if ( _type == Variant.VT_VARIANT )
+ {
+ for ( int i = 0; i < length; i++ )
+ {
+ TypedPropertyValue value = new TypedPropertyValue();
+ offset += value.read( data, offset );
+ _values[i] = value;
+ }
+ }
+ else
+ {
+ for ( int i = 0; i < length; i++ )
+ {
+ TypedPropertyValue value = new TypedPropertyValue( _type, null );
+ // be aware: not padded here
+ offset += value.readValue( data, offset );
+ _values[i] = value;
+ }
+ }
+ return offset - startOffset;
+ }
+
+ TypedPropertyValue[] getValues(){
+ return _values;
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/VersionedStream.java b/trunk/src/java/org/apache/poi/hpsf/VersionedStream.java
new file mode 100644
index 000000000..c3c066e4f
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/VersionedStream.java
@@ -0,0 +1,39 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class VersionedStream
+{
+ private GUID _versionGuid;
+ private IndirectPropertyName _streamName;
+
+ VersionedStream( byte[] data, int offset )
+ {
+ _versionGuid = new GUID( data, offset );
+ _streamName = new IndirectPropertyName( data, offset + GUID.SIZE );
+ }
+
+ int getSize()
+ {
+ return GUID.SIZE + _streamName.getSize();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java b/trunk/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java
new file mode 100644
index 000000000..d409999f7
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java
@@ -0,0 +1,43 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf;
+
+/**
+ *
This exception is thrown when trying to write a (yet) unsupported variant
+ * type.
+ *
+ * @param variantType The unsupported variant type.
+ * @param value The value.
+ */
+ public WritingNotSupportedException(final long variantType,
+ final Object value)
+ {
+ super(variantType, value);
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java b/trunk/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java
new file mode 100644
index 000000000..fc526ee61
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/extractor/HPSFPropertiesExtractor.java
@@ -0,0 +1,161 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.extractor;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.poi.POIDocument;
+import org.apache.poi.POIOLE2TextExtractor;
+import org.apache.poi.POITextExtractor;
+import org.apache.poi.hpsf.CustomProperties;
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.HPSFPropertiesOnlyDocument;
+import org.apache.poi.hpsf.Property;
+import org.apache.poi.hpsf.SpecialPropertySet;
+import org.apache.poi.hpsf.SummaryInformation;
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+/**
+ * Extracts all of the HPSF properties, both
+ * build in and custom, returning them in
+ * textual form.
+ */
+public class HPSFPropertiesExtractor extends POIOLE2TextExtractor {
+ public HPSFPropertiesExtractor(POIOLE2TextExtractor mainExtractor) {
+ super(mainExtractor);
+ }
+ public HPSFPropertiesExtractor(POIDocument doc) {
+ super(doc);
+ }
+ public HPSFPropertiesExtractor(POIFSFileSystem fs) {
+ super(new HPSFPropertiesOnlyDocument(fs));
+ }
+ public HPSFPropertiesExtractor(NPOIFSFileSystem fs) {
+ super(new HPSFPropertiesOnlyDocument(fs));
+ }
+
+ public String getDocumentSummaryInformationText() {
+ if(document == null) { // event based extractor does not have a document
+ return "";
+ }
+
+ DocumentSummaryInformation dsi = document.getDocumentSummaryInformation();
+ StringBuilder text = new StringBuilder();
+
+ // Normal properties
+ text.append( getPropertiesText(dsi) );
+
+ // Now custom ones
+ CustomProperties cps = dsi == null ? null : dsi.getCustomProperties();
+ if (cps != null) {
+ for (String key : cps.nameSet()) {
+ String val = HelperPropertySet.getPropertyValueText(cps.get(key));
+ text.append(key).append(" = ").append(val).append("\n");
+ }
+ }
+
+ // All done
+ return text.toString();
+ }
+ public String getSummaryInformationText() {
+ if(document == null) { // event based extractor does not have a document
+ return "";
+ }
+
+ SummaryInformation si = document.getSummaryInformation();
+
+ // Just normal properties
+ return getPropertiesText(si);
+ }
+
+ private static String getPropertiesText(SpecialPropertySet ps) {
+ if (ps == null) {
+ // Not defined, oh well
+ return "";
+ }
+
+ StringBuilder text = new StringBuilder();
+
+ PropertyIDMap idMap = ps.getPropertySetIDMap();
+ Property[] props = ps.getProperties();
+ for (Property prop : props) {
+ String type = Long.toString(prop.getID());
+ Object typeObj = idMap.get(prop.getID());
+ if (typeObj != null) {
+ type = typeObj.toString();
+ }
+
+ String val = HelperPropertySet.getPropertyValueText(prop.getValue());
+ text.append(type).append(" = ").append(val).append("\n");
+ }
+
+ return text.toString();
+ }
+
+ /**
+ * @return the text of all the properties defined in
+ * the document.
+ */
+ public String getText() {
+ return getSummaryInformationText() + getDocumentSummaryInformationText();
+ }
+
+ /**
+ * Prevent recursion!
+ */
+ public POITextExtractor getMetadataTextExtractor() {
+ throw new IllegalStateException("You already have the Metadata Text Extractor, not recursing!");
+ }
+
+ private static abstract class HelperPropertySet extends SpecialPropertySet {
+ public HelperPropertySet() {
+ super(null);
+ }
+ public static String getPropertyValueText(Object val) {
+ if (val == null) {
+ return "(not set)";
+ }
+ return SpecialPropertySet.getPropertyStringValue(val);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode();
+ }
+
+ public static void main(String[] args) throws IOException {
+ for (String file : args) {
+ HPSFPropertiesExtractor ext = new HPSFPropertiesExtractor(
+ new NPOIFSFileSystem(new File(file)));
+ try {
+ System.out.println(ext.getText());
+ } finally {
+ ext.close();
+ }
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/package.html b/trunk/src/java/org/apache/poi/hpsf/package.html
new file mode 100644
index 000000000..f5cfe430c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/package.html
@@ -0,0 +1,164 @@
+
+
+
+
+ HPSF
+
+
+
+
+
Processes streams in the Horrible Property Set Format (HPSF) in POI
+ filesystems. Microsoft Office documents, i.e. POI filesystems, usually
+ contain meta data like author, title, last saving time etc. These items
+ are called properties and stored in
+ property set streams along with the document itself. These
+ streams are commonly named \005SummaryInformation and
+ \005DocumentSummaryInformation. However, a POI filesystem may
+ contain further property sets of other names or types.
+
+
In order to extract the properties from a POI filesystem, a property set
+ stream's contents must be parsed into a {@link
+ org.apache.poi.hpsf.PropertySet} instance. Its subclasses {@link
+ org.apache.poi.hpsf.SummaryInformation} and {@link
+ org.apache.poi.hpsf.DocumentSummaryInformation} deal with the well-known
+ property set streams \005SummaryInformation and
+ \005DocumentSummaryInformation. (However, the streams' names are
+ irrelevant. What counts is the property set's first section's format ID -
+ see below.)
+
+
The factory method {@link org.apache.poi.hpsf.PropertySetFactory#create}
+ creates a {@link org.apache.poi.hpsf.PropertySet} instance. This method
+ always returns the most specific property set: If it
+ identifies the stream data as a Summary Information or as a Document
+ Summary Information it returns an instance of the corresponding class, else
+ the general {@link org.apache.poi.hpsf.PropertySet}.
+
+
A {@link org.apache.poi.hpsf.PropertySet} contains a list of {@link
+ org.apache.poi.hpsf.Section}s which can be retrieved with {@link
+ org.apache.poi.hpsf.PropertySet#getSections}. Each {@link
+ org.apache.poi.hpsf.Section} contains a {@link
+ org.apache.poi.hpsf.Property} array which can be retrieved with {@link
+ org.apache.poi.hpsf.Section#getProperties}. Since the vast majority of
+ {@link org.apache.poi.hpsf.PropertySet}s contains only a single {@link
+ org.apache.poi.hpsf.Section}, the convenience method {@link
+ org.apache.poi.hpsf.PropertySet#getProperties} returns the properties of a
+ {@link org.apache.poi.hpsf.PropertySet}'s {@link
+ org.apache.poi.hpsf.Section} (throwing a {@link
+ org.apache.poi.hpsf.NoSingleSectionException} if the {@link
+ org.apache.poi.hpsf.PropertySet} contains more (or less) than exactly one
+ {@link org.apache.poi.hpsf.Section}).
+
+
Each {@link org.apache.poi.hpsf.Property} has an ID, a
+ type, and a value which can be retrieved
+ with {@link org.apache.poi.hpsf.Property#getID}, {@link
+ org.apache.poi.hpsf.Property#getType}, and {@link
+ org.apache.poi.hpsf.Property#getValue}, respectively. The value's class
+ depends on the property's type. The current implementation
+ does not yet support all property types and restricts the values' classes
+ to {@link java.lang.String}, {@link java.lang.Integer} and {@link
+ java.util.Date}. A value of a yet unknown type is returned as a byte array
+ containing the value's origin bytes from the property set stream.
+
+
To retrieve the value of a specific {@link org.apache.poi.hpsf.Property},
+ use {@link org.apache.poi.hpsf.Section#getProperty} or {@link
+ org.apache.poi.hpsf.Section#getPropertyIntValue}.
+
+
The {@link org.apache.poi.hpsf.SummaryInformation} and {@link
+ org.apache.poi.hpsf.DocumentSummaryInformation} classes provide convenience
+ methods for retrieving well-known properties. For example, an application
+ that wants to retrieve a document's title string just calls {@link
+ org.apache.poi.hpsf.SummaryInformation#getTitle} instead of going through
+ the hassle of first finding out what the title's property ID is and then
+ using this ID to get the property's value.
+
+
Writing properties can be done with the classes
+ {@link org.apache.poi.hpsf.MutablePropertySet}, {@link
+ org.apache.poi.hpsf.MutableSection}, and {@link
+ org.apache.poi.hpsf.MutableProperty}.
{@link org.apache.poi.hpsf.PropertySetFactory#create(InputStream)} no
+ longer throws an
+ {@link org.apache.poi.hpsf.UnexpectedPropertySetTypeException}.
+
+
+
+
+
+
To Do
+
+
The following is still left to be implemented. Sponsering could foster
+ these issues considerably.
+
+
+
+
+
Convenience methods for setting summary information and document
+ summary information properties
+
+
+
+
Better codepage support
+
+
+
+
Support for more property (variant) types
+
+
+
+
+
+
+
+ @author Rainer Klute (klute@rainer-klute.de)
+
+
+
+
+
+
+
diff --git a/trunk/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java b/trunk/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java
new file mode 100644
index 000000000..20e6d9f70
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java
@@ -0,0 +1,449 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.wellknown;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
This is a dictionary which maps property ID values to property
+ * ID strings.
+ *
+ *
The methods {@link #getSummaryInformationProperties} and {@link
+ * #getDocumentSummaryInformationProperties} return singleton {@link
+ * PropertyIDMap}s. An application that wants to extend these maps
+ * should treat them as unmodifiable, copy them and modifiy the
+ * copies.
+ */
+public class PropertyIDMap extends HashMap {
+
+ /*
+ * The following definitions are for property IDs in the first
+ * (and only) section of the Summary Information property set.
+ */
+
+ /**
ID of the property that denotes the document's title
*/
+ public static final int PID_TITLE = 2;
+
+ /**
ID of the property that denotes the document's subject
*/
+ public static final int PID_SUBJECT = 3;
+
+ /**
ID of the property that denotes the document's author
*/
+ public static final int PID_AUTHOR = 4;
+
+ /**
ID of the property that denotes the document's keywords
*/
+ public static final int PID_KEYWORDS = 5;
+
+ /**
ID of the property that denotes the document's comments
*/
+ public static final int PID_COMMENTS = 6;
+
+ /**
ID of the property that denotes the document's template
*/
+ public static final int PID_TEMPLATE = 7;
+
+ /**
ID of the property that denotes the document's last author
*/
+ public static final int PID_LASTAUTHOR = 8;
+
+ /**
ID of the property that denotes the document's revision number
*/
+ public static final int PID_REVNUMBER = 9;
+
+ /**
ID of the property that denotes the document's edit time
*/
+ public static final int PID_EDITTIME = 10;
+
+ /**
ID of the property that denotes the date and time the document was
+ * last printed
*/
+ public static final int PID_LASTPRINTED = 11;
+
+ /**
ID of the property that denotes the date and time the document was
+ * created.
*/
+ public static final int PID_CREATE_DTM = 12;
+
+ /**
ID of the property that denotes the date and time the document was
+ * saved
*/
+ public static final int PID_LASTSAVE_DTM = 13;
+
+ /**
ID of the property that denotes the number of pages in the
+ * document
*/
+ public static final int PID_PAGECOUNT = 14;
+
+ /**
ID of the property that denotes the number of words in the
+ * document
*/
+ public static final int PID_WORDCOUNT = 15;
+
+ /**
ID of the property that denotes the number of characters in the
+ * document
*/
+ public static final int PID_CHARCOUNT = 16;
+
+ /**
ID of the property that denotes the document's thumbnail
*/
+ public static final int PID_THUMBNAIL = 17;
+
+ /**
ID of the property that denotes the application that created the
+ * document
*/
+ public static final int PID_APPNAME = 18;
+
+ /**
ID of the property that denotes whether read/write access to the
+ * document is allowed or whether is should be opened as read-only. It can
+ * have the following values:
+ *
+ *
+ *
+ *
+ *
Value
+ *
Description
+ *
+ *
+ *
0
+ *
No restriction
+ *
+ *
+ *
2
+ *
Read-only recommended
+ *
+ *
+ *
4
+ *
Read-only enforced
+ *
+ *
+ *
+ */
+ public static final int PID_SECURITY = 19;
+
+
+
+ /*
+ * The following definitions are for property IDs in the first
+ * section of the Document Summary Information property set.
+ */
+
+ /**
+ *
The entry is a dictionary.
+ */
+ public static final int PID_DICTIONARY = 0;
+
+ /**
+ *
The entry denotes a code page.
+ */
+ public static final int PID_CODEPAGE = 1;
+
+ /**
+ *
The entry is a string denoting the category the file belongs
+ * to, e.g. review, memo, etc. This is useful to find documents of
+ * same type.
+ */
+ public static final int PID_CATEGORY = 2;
+
+ /**
+ *
Target format for power point presentation, e.g. 35mm,
+ * printer, video etc.
+ */
+ public static final int PID_PRESFORMAT = 3;
+
+ /**
+ *
Number of bytes.
+ */
+ public static final int PID_BYTECOUNT = 4;
+
+ /**
+ *
Number of lines.
+ */
+ public static final int PID_LINECOUNT = 5;
+
+ /**
+ *
Number of paragraphs.
+ */
+ public static final int PID_PARCOUNT = 6;
+
+ /**
+ *
Number of slides in a power point presentation.
+ */
+ public static final int PID_SLIDECOUNT = 7;
+
+ /**
+ *
Number of slides with notes.
+ */
+ public static final int PID_NOTECOUNT = 8;
+
+ /**
+ *
Number of hidden slides.
+ */
+ public static final int PID_HIDDENCOUNT = 9;
+
+ /**
+ *
Number of multimedia clips, e.g. sound or video.
+ */
+ public static final int PID_MMCLIPCOUNT = 10;
+
+ /**
+ *
This entry is set to -1 when scaling of the thumbnail is
+ * desired. Otherwise the thumbnail should be cropped.
+ */
+ public static final int PID_SCALE = 11;
+
+ /**
+ *
This entry denotes an internally used property. It is a
+ * vector of variants consisting of pairs of a string (VT_LPSTR)
+ * and a number (VT_I4). The string is a heading name, and the
+ * number tells how many document parts are under that
+ * heading.
+ */
+ public static final int PID_HEADINGPAIR = 12;
+
+ /**
+ *
This entry contains the names of document parts (word: names
+ * of the documents in the master document, excel: sheet names,
+ * power point: slide titles, binder: document names).
+ */
+ public static final int PID_DOCPARTS = 13;
+
+ /**
+ *
This entry contains the name of the project manager.
+ */
+ public static final int PID_MANAGER = 14;
+
+ /**
+ *
This entry contains the company name.
+ */
+ public static final int PID_COMPANY = 15;
+
+ /**
+ *
If this entry is -1 the links are dirty and should be
+ * re-evaluated.
+ */
+ public static final int PID_LINKSDIRTY = 0x10;
+
+ /**
+ *
The entry specifies an estimate of the number of characters
+ * in the document, including whitespace, as an integer
+ */
+ public static final int PID_CCHWITHSPACES = 0x11;
+
+ // 0x12 Unused
+ // 0x13 GKPIDDSI_SHAREDDOC - Must be False
+ // 0x14 GKPIDDSI_LINKBASE - Must not be written
+ // 0x15 GKPIDDSI_HLINKS - Must not be written
+
+ /**
+ *
This entry contains a boolean which marks if the User Defined
+ * Property Set has been updated outside of the Application, if so the
+ * hyperlinks should be updated on document load.
+ */
+ public static final int PID_HYPERLINKSCHANGED = 0x16;
+
+ /**
+ *
This entry contains the version of the Application which wrote the
+ * Property set, stored with the two high order bytes having the major
+ * version number, and the two low order bytes the minor version number.
+ */
+ public static final int PID_VERSION = 0x17;
+
+ /**
+ *
This entry contains the VBA digital signature for the VBA project
+ * embedded in the document.
+ */
+ public static final int PID_DIGSIG = 0x18;
+
+ // 0x19 Unused
+
+ /**
+ *
This entry contains a string of the content type of the file.
+ */
+ public static final int PID_CONTENTTYPE = 0x1A;
+
+ /**
+ *
This entry contains a string of the document status.
+ */
+ public static final int PID_CONTENTSTATUS = 0x1B;
+
+ /**
+ *
This entry contains a string of the document language, but
+ * normally should be empty.
+ */
+ public static final int PID_LANGUAGE = 0x1C;
+
+ /**
+ *
This entry contains a string of the document version, but
+ * normally should be empty
+ */
+ public static final int PID_DOCVERSION = 0x1D;
+
+ /**
+ *
The highest well-known property ID. Applications are free to use
+ * higher values for custom purposes. (This value is based on Office 12,
+ * earlier versions of Office had lower values)
+ */
+ public static final int PID_MAX = 0x1F;
+
+
+
+ /**
+ *
Contains the summary information property ID values and
+ * associated strings. See the overall HPSF documentation for
+ * details!
+ *
+ * @param initialCapacity The initial capacity as defined for
+ * {@link HashMap}
+ * @param loadFactor The load factor as defined for {@link HashMap}
+ */
+ public PropertyIDMap(final int initialCapacity, final float loadFactor)
+ {
+ super(initialCapacity, loadFactor);
+ }
+
+
+
+ /**
+ *
Creates a {@link PropertyIDMap} backed by another map.
+ *
+ * @param map The instance to be created is backed by this map.
+ */
+ public PropertyIDMap(final Map map)
+ {
+ super(map);
+ }
+
+
+
+ /**
+ *
Puts a ID string for an ID into the {@link
+ * PropertyIDMap}.
+ *
+ * @param id The ID.
+ * @param idString The ID string.
+ * @return As specified by the {@link java.util.Map} interface, this method
+ * returns the previous value associated with the specified
+ * id, or null if there was no mapping for
+ * key.
+ */
+ public Object put(final long id, final String idString)
+ {
+ return put(Long.valueOf(id), idString);
+ }
+
+
+
+ /**
+ *
Gets the ID string for an ID from the {@link
+ * PropertyIDMap}.
+ *
+ * @param args The command-line arguments
+ */
+ public static void main(final String[] args)
+ {
+ PropertyIDMap s1 = getSummaryInformationProperties();
+ PropertyIDMap s2 = getDocumentSummaryInformationProperties();
+ System.out.println("s1: " + s1);
+ System.out.println("s2: " + s2);
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/wellknown/SectionIDMap.java b/trunk/src/java/org/apache/poi/hpsf/wellknown/SectionIDMap.java
new file mode 100644
index 000000000..1f5760df2
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/wellknown/SectionIDMap.java
@@ -0,0 +1,173 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hpsf.wellknown;
+
+import java.util.HashMap;
+
+import org.apache.poi.util.StringUtil;
+
+/**
+ *
Maps section format IDs to {@link PropertyIDMap}s. It is
+ * initialized with two well-known section format IDs: those of the
+ * \005SummaryInformation stream and the
+ * \005DocumentSummaryInformation stream.
+ *
+ *
If you have a section format ID you can use it as a key to query
+ * this map. If you get a {@link PropertyIDMap} returned your section
+ * is well-known and you can query the {@link PropertyIDMap} for PID
+ * strings. If you get back null you are on your own.
+ *
+ *
This {@link java.util.Map} expects the byte arrays of section format IDs
+ * as keys. A key maps to a {@link PropertyIDMap} describing the
+ * property IDs in sections with the specified section format ID.
+ */
+@SuppressWarnings({"rawtypes","unchecked"}) // Java Generics have issues on this style of class...
+public class SectionIDMap extends HashMap {
+ /**
+ *
Returns the singleton instance of the default {@link
+ * SectionIDMap}.
+ *
+ * @return The instance value
+ */
+ public static SectionIDMap getInstance()
+ {
+ if (defaultMap == null)
+ {
+ final SectionIDMap m = new SectionIDMap();
+ m.put(SUMMARY_INFORMATION_ID,
+ PropertyIDMap.getSummaryInformationProperties());
+ m.put(DOCUMENT_SUMMARY_INFORMATION_ID[0],
+ PropertyIDMap.getDocumentSummaryInformationProperties());
+ defaultMap = m;
+ }
+ return defaultMap;
+ }
+
+
+
+ /**
+ *
Returns the property ID string that is associated with a
+ * given property ID in a section format ID's namespace.
+ *
+ * @param sectionFormatID Each section format ID has its own name
+ * space of property ID strings and thus must be specified.
+ * @param pid The property ID
+ * @return The well-known property ID string associated with the
+ * property ID pid in the name space spanned by
+ * sectionFormatID . If the pid
+ * /sectionFormatID combination is not well-known, the
+ * string "[undefined]" is returned.
+ */
+ public static String getPIDString(final byte[] sectionFormatID,
+ final long pid)
+ {
+ final PropertyIDMap m = getInstance().get(sectionFormatID);
+ if (m == null) {
+ return UNDEFINED;
+ }
+ final String s = (String) m.get(pid);
+ if (s == null)
+ return UNDEFINED;
+ return s;
+ }
+
+
+
+ /**
+ *
Returns the {@link PropertyIDMap} for a given section format
+ * ID.
+ *
+ * @param sectionFormatID the section format ID
+ * @return the property ID map
+ */
+ public PropertyIDMap get(final byte[] sectionFormatID)
+ {
+ return (PropertyIDMap)super.get(new String(sectionFormatID, StringUtil.UTF8));
+ }
+
+ /**
+ *
Associates a section format ID with a {@link
+ * PropertyIDMap}.
+ *
+ * @param sectionFormatID the section format ID
+ * @param propertyIDMap the property ID map
+ * @return as defined by {@link java.util.Map#put}
+ */
+ public PropertyIDMap put(final byte[] sectionFormatID,
+ final PropertyIDMap propertyIDMap)
+ {
+ return (PropertyIDMap)super.put(new String(sectionFormatID, StringUtil.UTF8), propertyIDMap);
+ }
+
+ /**
+ * Associates the string representation of a section
+ * format ID with a {@link PropertyIDMap}
+ *
+ * @param key the key of the PropertyIDMap
+ * @param value the PropertyIDMap itself
+ *
+ * @return the previous PropertyIDMap stored under this key, or {@code null} if there wasn't one
+ */
+ protected PropertyIDMap put(String key, PropertyIDMap value) {
+ return (PropertyIDMap)super.put(key, value);
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hpsf/wellknown/package.html b/trunk/src/java/org/apache/poi/hpsf/wellknown/package.html
new file mode 100644
index 000000000..05b4ffb14
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hpsf/wellknown/package.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+ Specific support for DocumentSummaryInformation, SummaryInformation types.
+
Support classes for "well-known" section format IDs and property IDs. The
+ streams \005DocumentSummaryInformation and
+ \005SummaryInformation (or any streams with the same section
+ format IDs as the aforementioned) are considered well-known. So are most
+ property IDs in these streams.
+
+
+ @author Rainer Klute (klute@rainer-klute.de)
+
+
+
+
+
+
+
diff --git a/trunk/src/java/org/apache/poi/hssf/OldExcelFormatException.java b/trunk/src/java/org/apache/poi/hssf/OldExcelFormatException.java
new file mode 100644
index 000000000..e42e613d8
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/OldExcelFormatException.java
@@ -0,0 +1,25 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf;
+
+import org.apache.poi.OldFileFormatException;
+
+public class OldExcelFormatException extends OldFileFormatException {
+ public OldExcelFormatException(String s) {
+ super(s);
+ }
+}
\ No newline at end of file
diff --git a/trunk/src/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java b/trunk/src/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java
new file mode 100644
index 000000000..256d0230f
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java
@@ -0,0 +1,164 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ====================================================================
+ */
+
+package org.apache.poi.hssf.dev;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.ddf.EscherRecord;
+import org.apache.poi.hssf.model.InternalWorkbook;
+import org.apache.poi.hssf.record.DrawingGroupRecord;
+import org.apache.poi.hssf.usermodel.HSSFPatriarch;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.util.StringUtil;
+
+/**
+ * Utility for representing drawings contained in a binary Excel file as a XML tree
+ */
+public class BiffDrawingToXml {
+ private static final String SHEET_NAME_PARAM = "-sheet-name";
+ private static final String SHEET_INDEXES_PARAM = "-sheet-indexes";
+ private static final String EXCLUDE_WORKBOOK_RECORDS = "-exclude-workbook";
+
+ private static int getAttributeIndex(String attribute, String[] params) {
+ for (int i = 0; i < params.length; i++) {
+ String param = params[i];
+ if (attribute.equals(param)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static boolean isExcludeWorkbookRecords(String[] params) {
+ return -1 != getAttributeIndex(EXCLUDE_WORKBOOK_RECORDS, params);
+ }
+
+ private static List getIndexesByName(String[] params, HSSFWorkbook workbook) {
+ List list = new ArrayList();
+ int pos = getAttributeIndex(SHEET_NAME_PARAM, params);
+ if (-1 != pos) {
+ if (pos >= params.length) {
+ throw new IllegalArgumentException("sheet name param value was not specified");
+ }
+ String sheetName = params[pos + 1];
+ int sheetPos = workbook.getSheetIndex(sheetName);
+ if (-1 == sheetPos){
+ throw new IllegalArgumentException("specified sheet name has not been found in xls file");
+ }
+ list.add(sheetPos);
+ }
+ return list;
+ }
+
+ private static List getIndexesByIdArray(String[] params) {
+ List list = new ArrayList();
+ int pos = getAttributeIndex(SHEET_INDEXES_PARAM, params);
+ if (-1 != pos) {
+ if (pos >= params.length) {
+ throw new IllegalArgumentException("sheet list value was not specified");
+ }
+ String sheetParam = params[pos + 1];
+ String[] sheets = sheetParam.split(",");
+ for (String sheet : sheets) {
+ list.add(Integer.parseInt(sheet));
+ }
+ }
+ return list;
+ }
+
+ private static List getSheetsIndexes(String[] params, HSSFWorkbook workbook) {
+ List list = new ArrayList();
+ list.addAll(getIndexesByIdArray(params));
+ list.addAll(getIndexesByName(params, workbook));
+ if (0 == list.size()) {
+ int size = workbook.getNumberOfSheets();
+ for (int i = 0; i < size; i++) {
+ list.add(i);
+ }
+ }
+ return list;
+ }
+
+ private static String getInputFileName(String[] params) {
+ return params[params.length - 1];
+ }
+
+ private static String getOutputFileName(String input) {
+ if (input.contains("xls")) {
+ return input.replace(".xls", ".xml");
+ }
+ return input + ".xml";
+ }
+
+ public static void main(String[] params) throws IOException {
+ if (0 == params.length) {
+ System.out.println("Usage: BiffDrawingToXml [options] inputWorkbook");
+ System.out.println("Options:");
+ System.out.println(" -exclude-workbook exclude workbook-level records");
+ System.out.println(" -sheet-indexes output sheets with specified indexes");
+ System.out.println(" -sheet-namek output sheets with specified name");
+ return;
+ }
+ String input = getInputFileName(params);
+ FileInputStream inp = new FileInputStream(input);
+ String output = getOutputFileName(input);
+ FileOutputStream outputStream = new FileOutputStream(output);
+ writeToFile(outputStream, inp, isExcludeWorkbookRecords(params), params);
+ inp.close();
+ outputStream.close();
+ }
+
+ public static void writeToFile(OutputStream fos, InputStream xlsWorkbook, boolean excludeWorkbookRecords, String[] params) throws IOException {
+ HSSFWorkbook workbook = new HSSFWorkbook(xlsWorkbook);
+ InternalWorkbook internalWorkbook = workbook.getInternalWorkbook();
+ DrawingGroupRecord r = (DrawingGroupRecord) internalWorkbook.findFirstRecordBySid(DrawingGroupRecord.sid);
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("\n");
+ String tab = "\t";
+ if (!excludeWorkbookRecords && r != null) {
+ r.decode();
+ List escherRecords = r.getEscherRecords();
+ for (EscherRecord record : escherRecords) {
+ builder.append(record.toXml(tab));
+ }
+ }
+ List sheets = getSheetsIndexes(params, workbook);
+ for (Integer i : sheets) {
+ HSSFPatriarch p = workbook.getSheetAt(i).getDrawingPatriarch();
+ if(p != null ) {
+ builder.append(tab).append("\n");
+ builder.append(p.getBoundAggregate().toXml(tab + "\t"));
+ builder.append(tab).append("\n");
+ }
+ }
+ builder.append("\n");
+ fos.write(builder.toString().getBytes(StringUtil.UTF8));
+ fos.close();
+ workbook.close();
+ }
+
+}
diff --git a/trunk/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/trunk/src/java/org/apache/poi/hssf/dev/BiffViewer.java
new file mode 100644
index 000000000..5465f0ff1
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/dev/BiffViewer.java
@@ -0,0 +1,880 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.dev;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hssf.record.ArrayRecord;
+import org.apache.poi.hssf.record.AutoFilterInfoRecord;
+import org.apache.poi.hssf.record.BOFRecord;
+import org.apache.poi.hssf.record.BackupRecord;
+import org.apache.poi.hssf.record.BlankRecord;
+import org.apache.poi.hssf.record.BookBoolRecord;
+import org.apache.poi.hssf.record.BoolErrRecord;
+import org.apache.poi.hssf.record.BottomMarginRecord;
+import org.apache.poi.hssf.record.BoundSheetRecord;
+import org.apache.poi.hssf.record.CFHeader12Record;
+import org.apache.poi.hssf.record.CFHeaderRecord;
+import org.apache.poi.hssf.record.CFRule12Record;
+import org.apache.poi.hssf.record.CFRuleRecord;
+import org.apache.poi.hssf.record.CalcCountRecord;
+import org.apache.poi.hssf.record.CalcModeRecord;
+import org.apache.poi.hssf.record.CodepageRecord;
+import org.apache.poi.hssf.record.ColumnInfoRecord;
+import org.apache.poi.hssf.record.ContinueRecord;
+import org.apache.poi.hssf.record.CountryRecord;
+import org.apache.poi.hssf.record.DBCellRecord;
+import org.apache.poi.hssf.record.DConRefRecord;
+import org.apache.poi.hssf.record.DSFRecord;
+import org.apache.poi.hssf.record.DVALRecord;
+import org.apache.poi.hssf.record.DVRecord;
+import org.apache.poi.hssf.record.DateWindow1904Record;
+import org.apache.poi.hssf.record.DefaultColWidthRecord;
+import org.apache.poi.hssf.record.DefaultRowHeightRecord;
+import org.apache.poi.hssf.record.DeltaRecord;
+import org.apache.poi.hssf.record.DimensionsRecord;
+import org.apache.poi.hssf.record.DrawingGroupRecord;
+import org.apache.poi.hssf.record.DrawingRecordForBiffViewer;
+import org.apache.poi.hssf.record.DrawingSelectionRecord;
+import org.apache.poi.hssf.record.EOFRecord;
+import org.apache.poi.hssf.record.ExtSSTRecord;
+import org.apache.poi.hssf.record.ExtendedFormatRecord;
+import org.apache.poi.hssf.record.ExternSheetRecord;
+import org.apache.poi.hssf.record.ExternalNameRecord;
+import org.apache.poi.hssf.record.FeatHdrRecord;
+import org.apache.poi.hssf.record.FeatRecord;
+import org.apache.poi.hssf.record.FilePassRecord;
+import org.apache.poi.hssf.record.FileSharingRecord;
+import org.apache.poi.hssf.record.FnGroupCountRecord;
+import org.apache.poi.hssf.record.FontRecord;
+import org.apache.poi.hssf.record.FooterRecord;
+import org.apache.poi.hssf.record.FormatRecord;
+import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.GridsetRecord;
+import org.apache.poi.hssf.record.GutsRecord;
+import org.apache.poi.hssf.record.HCenterRecord;
+import org.apache.poi.hssf.record.HeaderRecord;
+import org.apache.poi.hssf.record.HideObjRecord;
+import org.apache.poi.hssf.record.HorizontalPageBreakRecord;
+import org.apache.poi.hssf.record.HyperlinkRecord;
+import org.apache.poi.hssf.record.IndexRecord;
+import org.apache.poi.hssf.record.InterfaceEndRecord;
+import org.apache.poi.hssf.record.InterfaceHdrRecord;
+import org.apache.poi.hssf.record.IterationRecord;
+import org.apache.poi.hssf.record.LabelRecord;
+import org.apache.poi.hssf.record.LabelSSTRecord;
+import org.apache.poi.hssf.record.LeftMarginRecord;
+import org.apache.poi.hssf.record.MMSRecord;
+import org.apache.poi.hssf.record.MergeCellsRecord;
+import org.apache.poi.hssf.record.MulBlankRecord;
+import org.apache.poi.hssf.record.MulRKRecord;
+import org.apache.poi.hssf.record.NameCommentRecord;
+import org.apache.poi.hssf.record.NameRecord;
+import org.apache.poi.hssf.record.NoteRecord;
+import org.apache.poi.hssf.record.NumberRecord;
+import org.apache.poi.hssf.record.ObjRecord;
+import org.apache.poi.hssf.record.PaletteRecord;
+import org.apache.poi.hssf.record.PaneRecord;
+import org.apache.poi.hssf.record.PasswordRecord;
+import org.apache.poi.hssf.record.PasswordRev4Record;
+import org.apache.poi.hssf.record.PrecisionRecord;
+import org.apache.poi.hssf.record.PrintGridlinesRecord;
+import org.apache.poi.hssf.record.PrintHeadersRecord;
+import org.apache.poi.hssf.record.PrintSetupRecord;
+import org.apache.poi.hssf.record.ProtectRecord;
+import org.apache.poi.hssf.record.ProtectionRev4Record;
+import org.apache.poi.hssf.record.RKRecord;
+import org.apache.poi.hssf.record.RecalcIdRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.record.RecordInputStream.LeftoverDataException;
+import org.apache.poi.hssf.record.RefModeRecord;
+import org.apache.poi.hssf.record.RefreshAllRecord;
+import org.apache.poi.hssf.record.RightMarginRecord;
+import org.apache.poi.hssf.record.RowRecord;
+import org.apache.poi.hssf.record.SCLRecord;
+import org.apache.poi.hssf.record.SSTRecord;
+import org.apache.poi.hssf.record.SaveRecalcRecord;
+import org.apache.poi.hssf.record.SelectionRecord;
+import org.apache.poi.hssf.record.SharedFormulaRecord;
+import org.apache.poi.hssf.record.StringRecord;
+import org.apache.poi.hssf.record.StyleRecord;
+import org.apache.poi.hssf.record.SupBookRecord;
+import org.apache.poi.hssf.record.TabIdRecord;
+import org.apache.poi.hssf.record.TableRecord;
+import org.apache.poi.hssf.record.TableStylesRecord;
+import org.apache.poi.hssf.record.TextObjectRecord;
+import org.apache.poi.hssf.record.TopMarginRecord;
+import org.apache.poi.hssf.record.UncalcedRecord;
+import org.apache.poi.hssf.record.UnknownRecord;
+import org.apache.poi.hssf.record.UseSelFSRecord;
+import org.apache.poi.hssf.record.VCenterRecord;
+import org.apache.poi.hssf.record.VerticalPageBreakRecord;
+import org.apache.poi.hssf.record.WSBoolRecord;
+import org.apache.poi.hssf.record.WindowOneRecord;
+import org.apache.poi.hssf.record.WindowProtectRecord;
+import org.apache.poi.hssf.record.WindowTwoRecord;
+import org.apache.poi.hssf.record.WriteAccessRecord;
+import org.apache.poi.hssf.record.WriteProtectRecord;
+import org.apache.poi.hssf.record.chart.AreaFormatRecord;
+import org.apache.poi.hssf.record.chart.AreaRecord;
+import org.apache.poi.hssf.record.chart.AxisLineFormatRecord;
+import org.apache.poi.hssf.record.chart.AxisOptionsRecord;
+import org.apache.poi.hssf.record.chart.AxisParentRecord;
+import org.apache.poi.hssf.record.chart.AxisRecord;
+import org.apache.poi.hssf.record.chart.AxisUsedRecord;
+import org.apache.poi.hssf.record.chart.BarRecord;
+import org.apache.poi.hssf.record.chart.BeginRecord;
+import org.apache.poi.hssf.record.chart.CatLabRecord;
+import org.apache.poi.hssf.record.chart.CategorySeriesAxisRecord;
+import org.apache.poi.hssf.record.chart.ChartEndBlockRecord;
+import org.apache.poi.hssf.record.chart.ChartEndObjectRecord;
+import org.apache.poi.hssf.record.chart.ChartFRTInfoRecord;
+import org.apache.poi.hssf.record.chart.ChartFormatRecord;
+import org.apache.poi.hssf.record.chart.ChartRecord;
+import org.apache.poi.hssf.record.chart.ChartStartBlockRecord;
+import org.apache.poi.hssf.record.chart.ChartStartObjectRecord;
+import org.apache.poi.hssf.record.chart.DatRecord;
+import org.apache.poi.hssf.record.chart.DataFormatRecord;
+import org.apache.poi.hssf.record.chart.DefaultDataLabelTextPropertiesRecord;
+import org.apache.poi.hssf.record.chart.EndRecord;
+import org.apache.poi.hssf.record.chart.FontBasisRecord;
+import org.apache.poi.hssf.record.chart.FontIndexRecord;
+import org.apache.poi.hssf.record.chart.FrameRecord;
+import org.apache.poi.hssf.record.chart.LegendRecord;
+import org.apache.poi.hssf.record.chart.LineFormatRecord;
+import org.apache.poi.hssf.record.chart.LinkedDataRecord;
+import org.apache.poi.hssf.record.chart.ObjectLinkRecord;
+import org.apache.poi.hssf.record.chart.PlotAreaRecord;
+import org.apache.poi.hssf.record.chart.PlotGrowthRecord;
+import org.apache.poi.hssf.record.chart.SeriesIndexRecord;
+import org.apache.poi.hssf.record.chart.SeriesListRecord;
+import org.apache.poi.hssf.record.chart.SeriesRecord;
+import org.apache.poi.hssf.record.chart.SeriesTextRecord;
+import org.apache.poi.hssf.record.chart.SeriesToChartGroupRecord;
+import org.apache.poi.hssf.record.chart.SheetPropertiesRecord;
+import org.apache.poi.hssf.record.chart.TextRecord;
+import org.apache.poi.hssf.record.chart.TickRecord;
+import org.apache.poi.hssf.record.chart.UnitsRecord;
+import org.apache.poi.hssf.record.chart.ValueRangeRecord;
+import org.apache.poi.hssf.record.pivottable.DataItemRecord;
+import org.apache.poi.hssf.record.pivottable.ExtendedPivotTableViewFieldsRecord;
+import org.apache.poi.hssf.record.pivottable.PageItemRecord;
+import org.apache.poi.hssf.record.pivottable.StreamIDRecord;
+import org.apache.poi.hssf.record.pivottable.ViewDefinitionRecord;
+import org.apache.poi.hssf.record.pivottable.ViewFieldsRecord;
+import org.apache.poi.hssf.record.pivottable.ViewSourceRecord;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.StringUtil;
+
+/**
+ * Utility for reading in BIFF8 records and displaying data from them.
+ * @see #main
+ */
+public final class BiffViewer {
+ private static final char[] NEW_LINE_CHARS = System.getProperty("line.separator").toCharArray();
+ private static final POILogger logger = POILogFactory.getLogger(BiffViewer.class);
+
+ private BiffViewer() {
+ // no instances of this class
+ }
+
+ /**
+ * Create an array of records from an input stream
+ *
+ * @param is the InputStream from which the records will be obtained
+ * @param ps the PrintWriter to output the record data
+ * @param recListener the record listener to notify about read records
+ * @param dumpInterpretedRecords if {@code true}, the read records will be written to the PrintWriter
+ *
+ * @return an array of Records created from the InputStream
+ * @exception org.apache.poi.util.RecordFormatException on error processing the InputStream
+ */
+ public static Record[] createRecords(InputStream is, PrintWriter ps, BiffRecordListener recListener, boolean dumpInterpretedRecords)
+ throws org.apache.poi.util.RecordFormatException {
+ List temp = new ArrayList();
+
+ RecordInputStream recStream = new RecordInputStream(is);
+ while (true) {
+ boolean hasNext;
+ try {
+ hasNext = recStream.hasNextRecord();
+ } catch (LeftoverDataException e) {
+ logger.log(POILogger.ERROR, "Discarding " + recStream.remaining() + " bytes and continuing", e);
+ recStream.readRemainder();
+ hasNext = recStream.hasNextRecord();
+ }
+ if (!hasNext) {
+ break;
+ }
+ recStream.nextRecord();
+ if (recStream.getSid() == 0) {
+ continue;
+ }
+ Record record;
+ if (dumpInterpretedRecords) {
+ record = createRecord (recStream);
+ if (record.getSid() == ContinueRecord.sid) {
+ continue;
+ }
+ temp.add(record);
+
+ if (dumpInterpretedRecords) {
+ for (String header : recListener.getRecentHeaders()) {
+ ps.println(header);
+ }
+ ps.print(record.toString());
+ }
+ } else {
+ recStream.readRemainder();
+ }
+ ps.println();
+ }
+ Record[] result = new Record[temp.size()];
+ temp.toArray(result);
+ return result;
+ }
+
+
+ /**
+ * Essentially a duplicate of RecordFactory. Kept separate as not to screw
+ * up non-debug operations.
+ *
+ */
+ private static Record createRecord(RecordInputStream in) {
+ switch (in.getSid()) {
+ case AreaFormatRecord.sid: return new AreaFormatRecord(in);
+ case AreaRecord.sid: return new AreaRecord(in);
+ case ArrayRecord.sid: return new ArrayRecord(in);
+ case AxisLineFormatRecord.sid: return new AxisLineFormatRecord(in);
+ case AxisOptionsRecord.sid: return new AxisOptionsRecord(in);
+ case AxisParentRecord.sid: return new AxisParentRecord(in);
+ case AxisRecord.sid: return new AxisRecord(in);
+ case AxisUsedRecord.sid: return new AxisUsedRecord(in);
+ case AutoFilterInfoRecord.sid: return new AutoFilterInfoRecord(in);
+ case BOFRecord.sid: return new BOFRecord(in);
+ case BackupRecord.sid: return new BackupRecord(in);
+ case BarRecord.sid: return new BarRecord(in);
+ case BeginRecord.sid: return new BeginRecord(in);
+ case BlankRecord.sid: return new BlankRecord(in);
+ case BookBoolRecord.sid: return new BookBoolRecord(in);
+ case BoolErrRecord.sid: return new BoolErrRecord(in);
+ case BottomMarginRecord.sid: return new BottomMarginRecord(in);
+ case BoundSheetRecord.sid: return new BoundSheetRecord(in);
+ case CFHeaderRecord.sid: return new CFHeaderRecord(in);
+ case CFHeader12Record.sid: return new CFHeader12Record(in);
+ case CFRuleRecord.sid: return new CFRuleRecord(in);
+ case CFRule12Record.sid: return new CFRule12Record(in);
+ // TODO Add CF Ex, and remove from UnknownRecord
+ case CalcCountRecord.sid: return new CalcCountRecord(in);
+ case CalcModeRecord.sid: return new CalcModeRecord(in);
+ case CategorySeriesAxisRecord.sid:return new CategorySeriesAxisRecord(in);
+ case ChartFormatRecord.sid: return new ChartFormatRecord(in);
+ case ChartRecord.sid: return new ChartRecord(in);
+ case CodepageRecord.sid: return new CodepageRecord(in);
+ case ColumnInfoRecord.sid: return new ColumnInfoRecord(in);
+ case ContinueRecord.sid: return new ContinueRecord(in);
+ case CountryRecord.sid: return new CountryRecord(in);
+ case DBCellRecord.sid: return new DBCellRecord(in);
+ case DSFRecord.sid: return new DSFRecord(in);
+ case DatRecord.sid: return new DatRecord(in);
+ case DataFormatRecord.sid: return new DataFormatRecord(in);
+ case DateWindow1904Record.sid: return new DateWindow1904Record(in);
+ case DConRefRecord.sid: return new DConRefRecord(in);
+ case DefaultColWidthRecord.sid: return new DefaultColWidthRecord(in);
+ case DefaultDataLabelTextPropertiesRecord.sid: return new DefaultDataLabelTextPropertiesRecord(in);
+ case DefaultRowHeightRecord.sid: return new DefaultRowHeightRecord(in);
+ case DeltaRecord.sid: return new DeltaRecord(in);
+ case DimensionsRecord.sid: return new DimensionsRecord(in);
+ case DrawingGroupRecord.sid: return new DrawingGroupRecord(in);
+ case DrawingRecordForBiffViewer.sid: return new DrawingRecordForBiffViewer(in);
+ case DrawingSelectionRecord.sid: return new DrawingSelectionRecord(in);
+ case DVRecord.sid: return new DVRecord(in);
+ case DVALRecord.sid: return new DVALRecord(in);
+ case EOFRecord.sid: return new EOFRecord(in);
+ case EndRecord.sid: return new EndRecord(in);
+ case ExtSSTRecord.sid: return new ExtSSTRecord(in);
+ case ExtendedFormatRecord.sid: return new ExtendedFormatRecord(in);
+ case ExternSheetRecord.sid: return new ExternSheetRecord(in);
+ case ExternalNameRecord.sid: return new ExternalNameRecord(in);
+ case FeatRecord.sid: return new FeatRecord(in);
+ case FeatHdrRecord.sid: return new FeatHdrRecord(in);
+ case FilePassRecord.sid: return new FilePassRecord(in);
+ case FileSharingRecord.sid: return new FileSharingRecord(in);
+ case FnGroupCountRecord.sid: return new FnGroupCountRecord(in);
+ case FontBasisRecord.sid: return new FontBasisRecord(in);
+ case FontIndexRecord.sid: return new FontIndexRecord(in);
+ case FontRecord.sid: return new FontRecord(in);
+ case FooterRecord.sid: return new FooterRecord(in);
+ case FormatRecord.sid: return new FormatRecord(in);
+ case FormulaRecord.sid: return new FormulaRecord(in);
+ case FrameRecord.sid: return new FrameRecord(in);
+ case GridsetRecord.sid: return new GridsetRecord(in);
+ case GutsRecord.sid: return new GutsRecord(in);
+ case HCenterRecord.sid: return new HCenterRecord(in);
+ case HeaderRecord.sid: return new HeaderRecord(in);
+ case HideObjRecord.sid: return new HideObjRecord(in);
+ case HorizontalPageBreakRecord.sid: return new HorizontalPageBreakRecord(in);
+ case HyperlinkRecord.sid: return new HyperlinkRecord(in);
+ case IndexRecord.sid: return new IndexRecord(in);
+ case InterfaceEndRecord.sid: return InterfaceEndRecord.create(in);
+ case InterfaceHdrRecord.sid: return new InterfaceHdrRecord(in);
+ case IterationRecord.sid: return new IterationRecord(in);
+ case LabelRecord.sid: return new LabelRecord(in);
+ case LabelSSTRecord.sid: return new LabelSSTRecord(in);
+ case LeftMarginRecord.sid: return new LeftMarginRecord(in);
+ case LegendRecord.sid: return new LegendRecord(in);
+ case LineFormatRecord.sid: return new LineFormatRecord(in);
+ case LinkedDataRecord.sid: return new LinkedDataRecord(in);
+ case MMSRecord.sid: return new MMSRecord(in);
+ case MergeCellsRecord.sid: return new MergeCellsRecord(in);
+ case MulBlankRecord.sid: return new MulBlankRecord(in);
+ case MulRKRecord.sid: return new MulRKRecord(in);
+ case NameRecord.sid: return new NameRecord(in);
+ case NameCommentRecord.sid: return new NameCommentRecord(in);
+ case NoteRecord.sid: return new NoteRecord(in);
+ case NumberRecord.sid: return new NumberRecord(in);
+ case ObjRecord.sid: return new ObjRecord(in);
+ case ObjectLinkRecord.sid: return new ObjectLinkRecord(in);
+ case PaletteRecord.sid: return new PaletteRecord(in);
+ case PaneRecord.sid: return new PaneRecord(in);
+ case PasswordRecord.sid: return new PasswordRecord(in);
+ case PasswordRev4Record.sid: return new PasswordRev4Record(in);
+ case PlotAreaRecord.sid: return new PlotAreaRecord(in);
+ case PlotGrowthRecord.sid: return new PlotGrowthRecord(in);
+ case PrecisionRecord.sid: return new PrecisionRecord(in);
+ case PrintGridlinesRecord.sid: return new PrintGridlinesRecord(in);
+ case PrintHeadersRecord.sid: return new PrintHeadersRecord(in);
+ case PrintSetupRecord.sid: return new PrintSetupRecord(in);
+ case ProtectRecord.sid: return new ProtectRecord(in);
+ case ProtectionRev4Record.sid: return new ProtectionRev4Record(in);
+ case RKRecord.sid: return new RKRecord(in);
+ case RecalcIdRecord.sid: return new RecalcIdRecord(in);
+ case RefModeRecord.sid: return new RefModeRecord(in);
+ case RefreshAllRecord.sid: return new RefreshAllRecord(in);
+ case RightMarginRecord.sid: return new RightMarginRecord(in);
+ case RowRecord.sid: return new RowRecord(in);
+ case SCLRecord.sid: return new SCLRecord(in);
+ case SSTRecord.sid: return new SSTRecord(in);
+ case SaveRecalcRecord.sid: return new SaveRecalcRecord(in);
+ case SelectionRecord.sid: return new SelectionRecord(in);
+ case SeriesIndexRecord.sid: return new SeriesIndexRecord(in);
+ case SeriesListRecord.sid: return new SeriesListRecord(in);
+ case SeriesRecord.sid: return new SeriesRecord(in);
+ case SeriesTextRecord.sid: return new SeriesTextRecord(in);
+ case SeriesToChartGroupRecord.sid:return new SeriesToChartGroupRecord(in);
+ case SharedFormulaRecord.sid: return new SharedFormulaRecord(in);
+ case SheetPropertiesRecord.sid: return new SheetPropertiesRecord(in);
+ case StringRecord.sid: return new StringRecord(in);
+ case StyleRecord.sid: return new StyleRecord(in);
+ case SupBookRecord.sid: return new SupBookRecord(in);
+ case TabIdRecord.sid: return new TabIdRecord(in);
+ case TableStylesRecord.sid: return new TableStylesRecord(in);
+ case TableRecord.sid: return new TableRecord(in);
+ case TextObjectRecord.sid: return new TextObjectRecord(in);
+ case TextRecord.sid: return new TextRecord(in);
+ case TickRecord.sid: return new TickRecord(in);
+ case TopMarginRecord.sid: return new TopMarginRecord(in);
+ case UncalcedRecord.sid: return new UncalcedRecord(in);
+ case UnitsRecord.sid: return new UnitsRecord(in);
+ case UseSelFSRecord.sid: return new UseSelFSRecord(in);
+ case VCenterRecord.sid: return new VCenterRecord(in);
+ case ValueRangeRecord.sid: return new ValueRangeRecord(in);
+ case VerticalPageBreakRecord.sid: return new VerticalPageBreakRecord(in);
+ case WSBoolRecord.sid: return new WSBoolRecord(in);
+ case WindowOneRecord.sid: return new WindowOneRecord(in);
+ case WindowProtectRecord.sid: return new WindowProtectRecord(in);
+ case WindowTwoRecord.sid: return new WindowTwoRecord(in);
+ case WriteAccessRecord.sid: return new WriteAccessRecord(in);
+ case WriteProtectRecord.sid: return new WriteProtectRecord(in);
+
+ // chart
+ case CatLabRecord.sid: return new CatLabRecord(in);
+ case ChartEndBlockRecord.sid: return new ChartEndBlockRecord(in);
+ case ChartEndObjectRecord.sid: return new ChartEndObjectRecord(in);
+ case ChartFRTInfoRecord.sid: return new ChartFRTInfoRecord(in);
+ case ChartStartBlockRecord.sid: return new ChartStartBlockRecord(in);
+ case ChartStartObjectRecord.sid: return new ChartStartObjectRecord(in);
+
+ // pivot table
+ case StreamIDRecord.sid: return new StreamIDRecord(in);
+ case ViewSourceRecord.sid: return new ViewSourceRecord(in);
+ case PageItemRecord.sid: return new PageItemRecord(in);
+ case ViewDefinitionRecord.sid: return new ViewDefinitionRecord(in);
+ case ViewFieldsRecord.sid: return new ViewFieldsRecord(in);
+ case DataItemRecord.sid: return new DataItemRecord(in);
+ case ExtendedPivotTableViewFieldsRecord.sid: return new ExtendedPivotTableViewFieldsRecord(in);
+ }
+ return new UnknownRecord(in);
+ }
+
+ private static final class CommandArgs {
+
+ private final boolean _biffhex;
+ private final boolean _noint;
+ private final boolean _out;
+ private final boolean _rawhex;
+ private final boolean _noHeader;
+ private final File _file;
+
+ private CommandArgs(boolean biffhex, boolean noint, boolean out, boolean rawhex, boolean noHeader, File file) {
+ _biffhex = biffhex;
+ _noint = noint;
+ _out = out;
+ _rawhex = rawhex;
+ _file = file;
+ _noHeader = noHeader;
+ }
+
+ public static CommandArgs parse(String[] args) throws CommandParseException {
+ int nArgs = args.length;
+ boolean biffhex = false;
+ boolean noint = false;
+ boolean out = false;
+ boolean rawhex = false;
+ boolean noheader = false;
+ File file = null;
+ for (int i=0; i
+ *
+ * Usage:
+
+
+
diff --git a/trunk/src/java/org/apache/poi/hssf/eventmodel/ERFListener.java b/trunk/src/java/org/apache/poi/hssf/eventmodel/ERFListener.java
new file mode 100644
index 000000000..91276490b
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/eventmodel/ERFListener.java
@@ -0,0 +1,40 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.eventmodel;
+
+import org.apache.poi.hssf.record.Record;
+
+/**
+ * An ERFListener is registered with the EventRecordFactory.
+ * An ERFListener listens for Records coming from the stream
+ * via the EventRecordFactory
+ *
+ * @see EventRecordFactory
+ */
+public interface ERFListener
+{
+ /**
+ * Process a Record. This method is called by the
+ * EventRecordFactory when a record is returned.
+ *
+ * @param rec the record to be processed
+ *
+ * @return boolean specifying whether the effort was a success.
+ */
+ public boolean processRecord(Record rec);
+}
diff --git a/trunk/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java b/trunk/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java
new file mode 100644
index 000000000..519d4bbdb
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java
@@ -0,0 +1,118 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.eventmodel;
+
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.apache.poi.hssf.record.*;
+
+/**
+ * Event-based record factory. As opposed to RecordFactory
+ * this version sends {@link ERFListener#processRecord(Record) } messages to
+ * the supplied listener. Record notifications are sent one record behind
+ * to ensure that {@link ContinueRecord}s are processed first.
+ */
+public final class EventRecordFactory {
+
+ private final ERFListener _listener;
+ private final short[] _sids;
+
+ /**
+ * Create an EventRecordFactory
+ *
+ * @param listener the listener to be informed about events
+ * @param sids an array of Record.sid values identifying the records
+ * the listener will work with. Alternatively if this is "null" then
+ * all records are passed. For all 'known' record types use {@link RecordFactory#getAllKnownRecordSIDs()}
+ */
+ public EventRecordFactory(ERFListener listener, short[] sids) {
+ _listener = listener;
+ if (sids == null) {
+ _sids = null;
+ } else {
+ _sids = sids.clone();
+ Arrays.sort(_sids); // for faster binary search
+ }
+ }
+ private boolean isSidIncluded(short sid) {
+ if (_sids == null) {
+ return true;
+ }
+ return Arrays.binarySearch(_sids, sid) >= 0;
+ }
+
+
+ /**
+ * sends the record event to all registered listeners.
+ * @param record the record to be thrown.
+ * @return false to abort. This aborts
+ * out of the event loop should the listener return false
+ */
+ private boolean processRecord(Record record) {
+ if (!isSidIncluded(record.getSid())) {
+ return true;
+ }
+ return _listener.processRecord(record);
+ }
+
+ /**
+ * Create an array of records from an input stream
+ *
+ * @param in the InputStream from which the records will be
+ * obtained
+ *
+ * @exception RecordFormatException on error processing the
+ * InputStream
+ */
+ public void processRecords(InputStream in) throws RecordFormatException {
+ Record last_record = null;
+
+ RecordInputStream recStream = new RecordInputStream(in);
+
+ while (recStream.hasNextRecord()) {
+ recStream.nextRecord();
+ Record[] recs = RecordFactory.createRecord(recStream); // handle MulRK records
+ if (recs.length > 1) {
+ for (Record rec : recs) {
+ if ( last_record != null ) {
+ if (!processRecord(last_record)) {
+ return;
+ }
+ }
+ last_record = rec; // do to keep the algorithm homogeneous...you can't
+ } // actually continue a number record anyhow.
+ } else {
+ Record record = recs[ 0 ];
+
+ if (record != null) {
+ if (last_record != null) {
+ if (!processRecord(last_record)) {
+ return;
+ }
+ }
+ last_record = record;
+ }
+ }
+ }
+
+ if (last_record != null) {
+ processRecord(last_record);
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hssf/eventusermodel/AbortableHSSFListener.java b/trunk/src/java/org/apache/poi/hssf/eventusermodel/AbortableHSSFListener.java
new file mode 100644
index 000000000..9000a7476
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/eventusermodel/AbortableHSSFListener.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.hssf.eventusermodel;
+
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.eventusermodel.HSSFUserException;
+
+/**
+ * Abstract class for use with the HSSFRequest and HSSFEventFactory, which
+ * allows for the halting of processing.
+ * Users should create subclass of this (which implements the usual
+ * HSSFListener), and then override the #abortableProcessRecord(Record)
+ * method to do their processing.
+ * This should then be registered with the HSSFRequest (associating
+ * it with Record SID's) as usual.
+ *
+ * @see org.apache.poi.hssf.eventusermodel.HSSFEventFactory
+ * @see org.apache.poi.hssf.eventusermodel.HSSFRequest
+ * @see org.apache.poi.hssf.eventusermodel.HSSFUserException
+ */
+
+public abstract class AbortableHSSFListener implements HSSFListener
+{
+ /**
+ * This method, inherited from HSSFListener is implemented as a stub.
+ * It is never called by HSSFEventFactory or HSSFRequest.
+ * You should implement #abortableProcessRecord instead
+ */
+ @Override
+ public void processRecord(Record record)
+ {
+ }
+
+ /**
+ * Process an HSSF Record. Called when a record occurs in an HSSF file.
+ * Provides two options for halting the processing of the HSSF file.
+ *
+ * The return value provides a means of non-error termination with a
+ * user-defined result code. A value of zero must be returned to
+ * continue processing, any other value will halt processing by
+ * HSSFEventFactory with the code being passed back by
+ * its abortable process events methods.
+ *
+ * Error termination can be done by throwing the HSSFUserException.
+ *
+ * Note that HSSFEventFactory will not call the inherited process
+ *
+ * @param record the record to be processed
+ *
+ * @return result code of zero for continued processing.
+ *
+ * @throws HSSFUserException User code can throw this to abort
+ * file processing by HSSFEventFactory and return diagnostic information.
+ */
+ public abstract short abortableProcessRecord(Record record) throws HSSFUserException;
+}
diff --git a/trunk/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java b/trunk/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java
new file mode 100644
index 000000000..eb65517ce
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java
@@ -0,0 +1,189 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.eventusermodel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hssf.model.HSSFFormulaParser;
+import org.apache.poi.hssf.model.InternalWorkbook;
+import org.apache.poi.hssf.record.BoundSheetRecord;
+import org.apache.poi.hssf.record.EOFRecord;
+import org.apache.poi.hssf.record.ExternSheetRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SSTRecord;
+import org.apache.poi.hssf.record.SupBookRecord;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * When working with the EventUserModel, if you want to
+ * process formulas, you need an instance of
+ * {@link InternalWorkbook} to pass to a {@link HSSFWorkbook},
+ * to finally give to {@link HSSFFormulaParser},
+ * and this will build you stub ones.
+ * Since you're working with the EventUserModel, you
+ * wouldn't want to get a full {@link InternalWorkbook} and
+ * {@link HSSFWorkbook}, as they would eat too much memory.
+ * Instead, you should collect a few key records as they
+ * go past, then call this once you have them to build a
+ * stub {@link InternalWorkbook}, and from that a stub
+ * {@link HSSFWorkbook}, to use with the {@link HSSFFormulaParser}.
+ *
+ * The records you should collect are:
+ * * {@link ExternSheetRecord}
+ * * {@link BoundSheetRecord}
+ * You should probably also collect {@link SSTRecord},
+ * but it's not required to pass this in.
+ *
+ * To help, this class includes a HSSFListener wrapper
+ * that will do the collecting for you.
+ */
+public class EventWorkbookBuilder {
+
+
+ /**
+ * Creates a stub Workbook from the supplied records,
+ * suitable for use with the {@link HSSFFormulaParser}
+ * @param externs The ExternSheetRecords in your file
+ * @param bounds The BoundSheetRecords in your file
+ * @param sst The SSTRecord in your file.
+ * @return A stub Workbook suitable for use with {@link HSSFFormulaParser}
+ */
+ public static InternalWorkbook createStubWorkbook(ExternSheetRecord[] externs,
+ BoundSheetRecord[] bounds, SSTRecord sst) {
+ List wbRecords = new ArrayList();
+
+ // Core Workbook records go first
+ if(bounds != null) {
+ for (BoundSheetRecord bound : bounds) {
+ wbRecords.add(bound);
+ }
+ }
+ if(sst != null) {
+ wbRecords.add(sst);
+ }
+
+ // Now we can have the ExternSheetRecords,
+ // preceded by a SupBookRecord
+ if(externs != null) {
+ wbRecords.add(SupBookRecord.createInternalReferences(
+ (short)externs.length));
+ for (ExternSheetRecord extern : externs) {
+ wbRecords.add(extern);
+ }
+ }
+
+ // Finally we need an EoF record
+ wbRecords.add(EOFRecord.instance);
+
+ return InternalWorkbook.createWorkbook(wbRecords);
+ }
+
+ /**
+ * Creates a stub workbook from the supplied records,
+ * suitable for use with the {@link HSSFFormulaParser}
+ * @param externs The ExternSheetRecords in your file
+ * @param bounds The BoundSheetRecords in your file
+ * @return A stub Workbook suitable for use with {@link HSSFFormulaParser}
+ */
+ public static InternalWorkbook createStubWorkbook(ExternSheetRecord[] externs,
+ BoundSheetRecord[] bounds) {
+ return createStubWorkbook(externs, bounds, null);
+ }
+
+
+ /**
+ * A wrapping HSSFListener which will collect
+ * {@link BoundSheetRecord}s and {@link ExternSheetRecord}s as
+ * they go past, so you can create a Stub {@link InternalWorkbook} from
+ * them once required.
+ */
+ public static class SheetRecordCollectingListener implements HSSFListener {
+ private final HSSFListener childListener;
+ private final List boundSheetRecords = new ArrayList();
+ private final List externSheetRecords = new ArrayList();
+ private SSTRecord sstRecord = null;
+
+ public SheetRecordCollectingListener(HSSFListener childListener) {
+ this.childListener = childListener;
+ }
+
+
+ public BoundSheetRecord[] getBoundSheetRecords() {
+ return boundSheetRecords.toArray(
+ new BoundSheetRecord[boundSheetRecords.size()]
+ );
+ }
+ public ExternSheetRecord[] getExternSheetRecords() {
+ return externSheetRecords.toArray(
+ new ExternSheetRecord[externSheetRecords.size()]
+ );
+ }
+ public SSTRecord getSSTRecord() {
+ return sstRecord;
+ }
+
+ public HSSFWorkbook getStubHSSFWorkbook() {
+ // Create a base workbook
+ HSSFWorkbook wb = HSSFWorkbook.create(getStubWorkbook());
+ // Stub the sheets, so sheet name lookups work
+ for (BoundSheetRecord bsr : boundSheetRecords) {
+ wb.createSheet(bsr.getSheetname());
+ }
+ // Ready for Formula use!
+ return wb;
+ }
+ public InternalWorkbook getStubWorkbook() {
+ return createStubWorkbook(
+ getExternSheetRecords(), getBoundSheetRecords(),
+ getSSTRecord()
+ );
+ }
+
+
+ /**
+ * Process this record ourselves, and then
+ * pass it on to our child listener
+ */
+ @Override
+ public void processRecord(Record record) {
+ // Handle it ourselves
+ processRecordInternally(record);
+
+ // Now pass on to our child
+ childListener.processRecord(record);
+ }
+
+ /**
+ * Process the record ourselves, but do not
+ * pass it on to the child Listener.
+ *
+ * @param record the record to be processed
+ */
+ public void processRecordInternally(Record record) {
+ if(record instanceof BoundSheetRecord) {
+ boundSheetRecords.add((BoundSheetRecord)record);
+ }
+ else if(record instanceof ExternSheetRecord) {
+ externSheetRecords.add((ExternSheetRecord)record);
+ }
+ else if(record instanceof SSTRecord) {
+ sstRecord = (SSTRecord)record;
+ }
+ }
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java b/trunk/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java
new file mode 100644
index 000000000..4a786962c
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java
@@ -0,0 +1,201 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.hssf.eventusermodel;
+
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.poi.hssf.record.CellValueRecordInterface;
+import org.apache.poi.hssf.record.ExtendedFormatRecord;
+import org.apache.poi.hssf.record.FormatRecord;
+import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.NumberRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.usermodel.HSSFDataFormat;
+import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
+import org.apache.poi.util.LocaleUtil;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * A proxy HSSFListener that keeps track of the document formatting records, and
+ * provides an easy way to look up the format strings used by cells from their
+ * ids.
+ */
+public class FormatTrackingHSSFListener implements HSSFListener {
+ private static POILogger logger = POILogFactory.getLogger(FormatTrackingHSSFListener.class);
+ private final HSSFListener _childListener;
+ private final HSSFDataFormatter _formatter;
+ private final NumberFormat _defaultFormat;
+ private final Map _customFormatRecords = new HashMap();
+ private final List _xfRecords = new ArrayList();
+
+ /**
+ * Creates a format tracking wrapper around the given listener, using
+ * the {@link Locale#getDefault() default locale} for the formats.
+ *
+ * @param childListener the listener to be wrapped
+ */
+ public FormatTrackingHSSFListener(HSSFListener childListener) {
+ this(childListener, LocaleUtil.getUserLocale());
+ }
+
+ /**
+ * Creates a format tracking wrapper around the given listener, using
+ * the given locale for the formats.
+ *
+ * @param childListener the listener to be wrapped
+ * @param locale the locale for the formats
+ */
+ public FormatTrackingHSSFListener(
+ HSSFListener childListener, Locale locale) {
+ _childListener = childListener;
+ _formatter = new HSSFDataFormatter(locale);
+ _defaultFormat = NumberFormat.getInstance(locale);
+ }
+
+ protected int getNumberOfCustomFormats() {
+ return _customFormatRecords.size();
+ }
+
+ protected int getNumberOfExtendedFormats() {
+ return _xfRecords.size();
+ }
+
+ /**
+ * Process this record ourselves, and then pass it on to our child listener
+ */
+ @Override
+ public void processRecord(Record record) {
+ // Handle it ourselves
+ processRecordInternally(record);
+
+ // Now pass on to our child
+ _childListener.processRecord(record);
+ }
+
+ /**
+ * Process the record ourselves, but do not pass it on to the child
+ * Listener.
+ *
+ * @param record the record to be processed
+ */
+ public void processRecordInternally(Record record) {
+ if (record instanceof FormatRecord) {
+ FormatRecord fr = (FormatRecord) record;
+ _customFormatRecords.put(Integer.valueOf(fr.getIndexCode()), fr);
+ }
+ if (record instanceof ExtendedFormatRecord) {
+ ExtendedFormatRecord xr = (ExtendedFormatRecord) record;
+ _xfRecords.add(xr);
+ }
+ }
+
+ /**
+ * Formats the given numeric of date cells contents as a String, in as
+ * close as we can to the way that Excel would do so. Uses the various
+ * format records to manage this.
+ *
+ * TODO - move this to a central class in such a way that hssf.usermodel can
+ * make use of it too
+ *
+ * @param cell the cell
+ *
+ * @return the given numeric of date cells contents as a String
+ */
+ public String formatNumberDateCell(CellValueRecordInterface cell) {
+ double value;
+ if (cell instanceof NumberRecord) {
+ value = ((NumberRecord) cell).getValue();
+ } else if (cell instanceof FormulaRecord) {
+ value = ((FormulaRecord) cell).getValue();
+ } else {
+ throw new IllegalArgumentException("Unsupported CellValue Record passed in " + cell);
+ }
+
+ // Get the built in format, if there is one
+ int formatIndex = getFormatIndex(cell);
+ String formatString = getFormatString(cell);
+
+ if (formatString == null) {
+ return _defaultFormat.format(value);
+ }
+ // Format, using the nice new
+ // HSSFDataFormatter to do the work for us
+ return _formatter.formatRawCellContents(value, formatIndex, formatString);
+ }
+
+ /**
+ * Returns the format string, eg $##.##, for the given number format index.
+ *
+ * @param formatIndex the format index
+ *
+ * @return the format string
+ */
+ public String getFormatString(int formatIndex) {
+ String format = null;
+ if (formatIndex >= HSSFDataFormat.getNumberOfBuiltinBuiltinFormats()) {
+ FormatRecord tfr = _customFormatRecords.get(Integer.valueOf(formatIndex));
+ if (tfr == null) {
+ logger.log( POILogger.ERROR, "Requested format at index " + formatIndex
+ + ", but it wasn't found");
+ } else {
+ format = tfr.getFormatString();
+ }
+ } else {
+ format = HSSFDataFormat.getBuiltinFormat((short) formatIndex);
+ }
+ return format;
+ }
+
+ /**
+ * Returns the format string, eg $##.##, used by your cell
+ *
+ * @param cell the cell
+ *
+ * @return the format string
+ */
+ public String getFormatString(CellValueRecordInterface cell) {
+ int formatIndex = getFormatIndex(cell);
+ if (formatIndex == -1) {
+ // Not found
+ return null;
+ }
+ return getFormatString(formatIndex);
+ }
+
+ /**
+ * Returns the index of the format string, used by your cell, or -1 if none found
+ *
+ * @param cell the cell
+ *
+ * @return the index of the format string
+ */
+ public int getFormatIndex(CellValueRecordInterface cell) {
+ ExtendedFormatRecord xfr = _xfRecords.get(cell.getXFIndex());
+ if (xfr == null) {
+ logger.log( POILogger.ERROR, "Cell " + cell.getRow() + "," + cell.getColumn()
+ + " uses XF with index " + cell.getXFIndex() + ", but we don't have that");
+ return -1;
+ }
+ return xfr.getFormatIndex();
+ }
+}
diff --git a/trunk/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java b/trunk/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java
new file mode 100644
index 000000000..929ec6cd1
--- /dev/null
+++ b/trunk/src/java/org/apache/poi/hssf/eventusermodel/HSSFEventFactory.java
@@ -0,0 +1,188 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.eventusermodel;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Set;
+
+import org.apache.poi.hssf.eventusermodel.HSSFUserException;
+import org.apache.poi.hssf.record.*;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import static org.apache.poi.hssf.model.InternalWorkbook.WORKBOOK_DIR_ENTRY_NAMES;
+
+/**
+ * Low level event based HSSF reader. Pass either a DocumentInputStream to
+ * process events along with a request object or pass a POIFS POIFSFileSystem to
+ * processWorkbookEvents along with a request.
+ *
+ * This will cause your file to be processed a record at a time. Each record with
+ * a static id matching one that you have registered in your HSSFRequest will be passed
+ * to your associated HSSFListener.
+ */
+public class HSSFEventFactory {
+ /** Creates a new instance of HSSFEventFactory */
+ public HSSFEventFactory() {
+ // no instance fields
+ }
+
+ /**
+ * Processes a file into essentially record events.
+ *
+ * @param req an Instance of HSSFRequest which has your registered listeners
+ * @param fs a POIFS filesystem containing your workbook
+ *
+ * @throws IOException if the workbook contained errors
+ */
+ public void processWorkbookEvents(HSSFRequest req, POIFSFileSystem fs) throws IOException {
+ processWorkbookEvents(req, fs.getRoot());
+ }
+
+ /**
+ * Processes a file into essentially record events.
+ *
+ * @param req an Instance of HSSFRequest which has your registered listeners
+ * @param dir a DirectoryNode containing your workbook
+ *
+ * @throws IOException if the workbook contained errors
+ */
+ public void processWorkbookEvents(HSSFRequest req, DirectoryNode dir) throws IOException {
+ // some old documents have "WORKBOOK" or "BOOK"
+ String name = null;
+ Set