Merged revisions 627779-634630 via svnmerge from

https://svn.apache.org/repos/asf/poi/trunk

........
  r627779 | nick | 2008-02-14 16:32:49 +0100 (Thu, 14 Feb 2008) | 1 line
  
  In the interests of sanity, stop having hssf test data files in scratchpad and main, go to just having them in main
........
  r627788 | nick | 2008-02-14 17:01:10 +0100 (Thu, 14 Feb 2008) | 1 line
  
  Big formula update from Josh from bug #44364 - support for Match, NA and SumProduct functions, and initial error support in functions
........
  r627999 | nick | 2008-02-15 11:30:10 +0100 (Fri, 15 Feb 2008) | 1 line
  
  To avoid confusion and repeated changes in svn, update the TestDataValidation test to output its file (that needs opening in excel to check to output) into the system tmp directory
........
  r628027 | nick | 2008-02-15 12:45:13 +0100 (Fri, 15 Feb 2008) | 1 line
  
  Fix for bug #44403 - Have mid use the third argument properly, and test
........
  r628029 | nick | 2008-02-15 12:53:25 +0100 (Fri, 15 Feb 2008) | 1 line
  
  Fix for bug #44413 from Josh - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself
........
  r628033 | nick | 2008-02-15 13:04:42 +0100 (Fri, 15 Feb 2008) | 1 line
  
  Fix from Josh from bug #44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name
........
  r628035 | nick | 2008-02-15 13:13:25 +0100 (Fri, 15 Feb 2008) | 1 line
  
  Fix from Josh from bug #44421 - Update Match function to properly support Area references
........
  r628044 | nick | 2008-02-15 13:59:40 +0100 (Fri, 15 Feb 2008) | 1 line
  
  Partial fix for bug #44410 - support whole column ranges such as C:C in the formula evaluator (so SUM(D:D) will now work). However, the formula string will still be displayed wrong
........
  r628065 | nick | 2008-02-15 14:50:38 +0100 (Fri, 15 Feb 2008) | 1 line
  
  Further support for whole-column references, including formula strings and the evaluator. Also has some new tests for it
........
  r628714 | nick | 2008-02-18 14:08:16 +0100 (Mon, 18 Feb 2008) | 1 line
  
  Update notice for latest guidance on ooxml xsd licence, and update getting involved to link to the newly released binary file format docs
........
  r629552 | nick | 2008-02-20 19:14:30 +0100 (Wed, 20 Feb 2008) | 1 line
  
  Patch from Josh from bug #44403 - Further support for unusual, but valid, arguments to the Mid function
........
  r629738 | nick | 2008-02-21 11:36:08 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Fix from Josh from bug #44456 - Update contrib SViewer to not fail if a HSSFRow is null
........
  r629742 | nick | 2008-02-21 11:49:25 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Use the right way to figure out how many rows on a sheet, so we display the row number for all of them on the left hand side. Also, tidy up some imports
........
  r629755 | nick | 2008-02-21 12:34:25 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Fix bug 38921, where HSSFPalette.findSimilar() wasn't working properly, and add tests for it
........
  r629821 | nick | 2008-02-21 16:08:44 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Patch from Josh from bug #44371 - support for OFFSET function, and various tweaks to the formula evaluator to support this
........
  r629829 | nick | 2008-02-21 16:35:59 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Patch from Josh from bug #44366 - InputStreams passed to POIFSFileSystem are now automatically closed. A warning is generated for people who might've relied on them not being closed before, and a wrapper to restore the old behaviour is supplied
........
  r629831 | nick | 2008-02-21 16:40:34 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Patch from Josh from bug #44437 - improved unit test for poifs
........
  r629832 | nick | 2008-02-21 16:42:06 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Patch from Josh from bug #44437 - improved unit test for poifs
........
  r629837 | nick | 2008-02-21 16:48:52 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Patch from Josh from bug #44449 - Handle SharedFormulas better, for where there are formulas for the same area on two sheets, and when the shared formula flag is set incorrectly
........
  r629849 | nick | 2008-02-21 17:22:18 +0100 (Thu, 21 Feb 2008) | 1 line
  
  Add a disabled test for a file with whacky StyleRecords that trigger an AIOOB
........
  r629865 | nick | 2008-02-21 17:44:46 +0100 (Thu, 21 Feb 2008) | 1 line
  
  At the request of legal-discuss, shuffle the ooxml xsd licence details into LICENSE from NOTICE
........
  r630160 | nick | 2008-02-22 12:23:50 +0100 (Fri, 22 Feb 2008) | 1 line
  
  Patch from Josh from bug #44450 - VLookup and HLookup support, and improvements to Lookup and Offset
........
  r630164 | nick | 2008-02-22 12:40:00 +0100 (Fri, 22 Feb 2008) | 1 line
  
  Bug #44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this
........
  r633114 | nick | 2008-03-03 16:01:18 +0100 (Mon, 03 Mar 2008) | 1 line
  
  Patch from Paolo from bug #44481 - getVerticallyCenter shouldn't take a parameter, but leave the old version in as deprecated for now
........
  r633118 | nick | 2008-03-03 16:10:46 +0100 (Mon, 03 Mar 2008) | 1 line
  
  Fix from Yegor from bug #44491 - don't have the new style handy POIDocument property stuff break old style hpsf+hssf use
........
  r633126 | nick | 2008-03-03 16:26:38 +0100 (Mon, 03 Mar 2008) | 1 line
  
  Patch from Josh from bug #44495 - Handle named cell ranges in formulas that have lower case parts
........
  r633151 | nick | 2008-03-03 17:09:02 +0100 (Mon, 03 Mar 2008) | 1 line
  
  Patch from Josh from bug #44510 - Fix how DVALRecord works with dropdowns
........
  r633169 | nick | 2008-03-03 17:55:00 +0100 (Mon, 03 Mar 2008) | 1 line
  
  Patch from Josh from bug #44508 - Fix formula evaluation with evaluateInCell on boolean formulas
........
  r633205 | nick | 2008-03-03 18:47:36 +0100 (Mon, 03 Mar 2008) | 1 line
  
  Fix indent, add more documentation, and make the error message more helpful
........
  r633505 | nick | 2008-03-04 16:06:29 +0100 (Tue, 04 Mar 2008) | 1 line
  
  Problem files from bug #44501
........
  r633547 | nick | 2008-03-04 17:53:32 +0100 (Tue, 04 Mar 2008) | 1 line
  
  Big patch from Josh from bug #44504 - lots of formula parser improvements
........
  r633548 | nick | 2008-03-04 17:59:02 +0100 (Tue, 04 Mar 2008) | 1 line
  
  Changelog update for last patch
........
  r634318 | nick | 2008-03-06 16:54:06 +0100 (Thu, 06 Mar 2008) | 1 line
  
  Change the behaviour on short last blocks to be a warning not an exception, as some people seem to have "real" valid files that trigger this. Fixed bug #28231
........
  r634371 | nick | 2008-03-06 19:06:48 +0100 (Thu, 06 Mar 2008) | 1 line
  
  Embeded files from bug #44524
........
  r634372 | nick | 2008-03-06 19:13:47 +0100 (Thu, 06 Mar 2008) | 1 line
  
  Add broken test for bug #43901
........
  r634617 | nick | 2008-03-07 12:18:02 +0100 (Fri, 07 Mar 2008) | 1 line
  
  Patch from Josh from bug #43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)
........
  r634619 | nick | 2008-03-07 12:36:14 +0100 (Fri, 07 Mar 2008) | 1 line
  
  Improved support for read-only recommended workbooks, fixing bug #44536
........
  r634630 | nick | 2008-03-07 13:06:18 +0100 (Fri, 07 Mar 2008) | 1 line
  
  Patch largely from Josh from bug #44539 - Support for area references in formulas of rows >= 32768
........


git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@634936 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Ugo Cei 2008-03-08 11:49:00 +00:00
parent c2e6c06bb4
commit 1fc10ad669
204 changed files with 11096 additions and 4046 deletions

View File

@ -557,8 +557,7 @@ under the License.
<batchtest todir="${main.reports.test}">
<fileset dir="${main.src.test}">
<include name="**/Test*.java"/>
<exclude name="**/AllTests.java"/>
<exclude name="**/TestEmptyDocument.java"/>
<exclude name="**/All*Tests.java"/>
<exclude name="**/TestUnfixedBugs.java"/>
<exclude name="**/TestcaseRecordInputStream.java"/>
</fileset>
@ -663,7 +662,7 @@ under the License.
<pathelement location="${scratchpad.output.test.dir}"/>
<pathelement location="${junit.jar1.dir}"/>
</classpath>
<sysproperty key="HSSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hssf/data"/>
<sysproperty key="HSSF.testdata.path" file="${main.src.test}/org/apache/poi/hssf/data"/>
<sysproperty key="HPSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hpsf/data"/>
<sysproperty key="HDF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hdf/data"/>
<sysproperty key="HWPF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hwpf/data"/>
@ -698,7 +697,7 @@ under the License.
<pathelement location="${scratchpad.output.test.dir}"/>
<pathelement location="${junit.jar1.dir}"/>
</classpath>
<sysproperty key="HSSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hssf/data"/>
<sysproperty key="HSSF.testdata.path" file="${main.src.test}/org/apache/poi/hssf/data"/>
<sysproperty key="HPSF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hpsf/data"/>
<sysproperty key="HWPF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hwpf/data"/>
<sysproperty key="HSLF.testdata.path" file="${scratchpad.src.test}/org/apache/poi/hslf/data"/>

View File

@ -200,3 +200,20 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Office Open XML (OOXML) xsds:
-----------------------------
These were downloaded as part of the Office Open XML ECMA Specification
from <http://www.ecma-international.org/publications/standards/Ecma-376.htm>
These are included within the Apache POI distribution, and are available
under compatible licensing terms.
Copyright - ECMA International, "made available without restriction"
http://www.ecma-international.org/memento/Ecmabylaws.htm - section 9.4
Patent License - Microsoft Open Specification Promise (OSP)
http://www.microsoft.com/interop/osp/

View File

@ -19,17 +19,14 @@ Since this is a data file, and has no compiled version (the original
See http://www.gnome.ru/projects/vsdump_en.html
Office Open XML experimental support:
XML Beans - http://xmlbeans.apache.org/
Apache Licence Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0
DOM4J - http://www.dom4j.org/
BSD Licence - http://www.dom4j.org/license.html
Jaxen - http://jaxen.org/
Apache Style Licence - http://jaxen.org/license.html
OpenXml4J - http://www.openxml4j.org/
BSD Licence or Apache Licence Version 2.0 -
http://www.openxml4j.org/Licensing/Default.html
Office Open XML ECMA Specification -
http://www.ecma-international.org/publications/standards/Ecma-376.htm
Microsoft Open Specification Promise (OSP) -
http://www.microsoft.com/interop/osp/
The Office Open XML experimental support had additional dependencies,
with their own licensing:
* XML Beans - http://xmlbeans.apache.org/
Apache Licence Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0
* DOM4J - http://www.dom4j.org/
BSD Licence - http://www.dom4j.org/license.html
* Jaxen - http://jaxen.org/
Apache Style Licence - http://jaxen.org/license.html
* OpenXml4J - http://www.openxml4j.org/
BSD Licence or Apache Licence Version 2.0 -
http://www.openxml4j.org/Licensing/Default.html

View File

@ -21,11 +21,8 @@
package org.apache.poi.hssf.contrib.view;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import org.apache.poi.hssf.usermodel.*;
@ -47,7 +44,9 @@ public class SVRowHeader extends JList {
this.sheet = sheet;
}
public int getSize() { return sheet.getPhysicalNumberOfRows(); }
public int getSize() {
return sheet.getLastRowNum() + 1;
}
public Object getElementAt(int index) {
return Integer.toString(index+1);
}
@ -73,7 +72,13 @@ public class SVRowHeader extends JList {
public Component getListCellRendererComponent( JList list,
Object value, int index, boolean isSelected, boolean cellHasFocus) {
Dimension d = getPreferredSize();
int rowHeight = (int)sheet.getRow(index).getHeightInPoints();
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());

View File

@ -20,11 +20,9 @@ package org.apache.poi.hssf.contrib.view;
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import org.apache.poi.hssf.usermodel.*;

View File

@ -19,8 +19,6 @@
package org.apache.poi.hssf.contrib.view;
import java.util.Hashtable;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.border.*;
@ -28,14 +26,11 @@ import javax.swing.border.*;
import java.awt.Component;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Font;
import java.io.Serializable;
import java.text.*;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
/**

View File

@ -23,13 +23,10 @@ package org.apache.poi.hssf.contrib.view;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.applet.*;
import java.io.*;
import javax.swing.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFCell;
/**
* Sheet Viewer - Views XLS files via HSSF. Can be used as an applet with
@ -143,6 +140,10 @@ public class SViewer extends JApplet {
/**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];

View File

@ -25,7 +25,6 @@ import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import org.apache.poi.hssf.usermodel.*;
@ -260,6 +259,9 @@ public class SViewerPanel extends JPanel {
/**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");
}
try {
FileInputStream in = new FileInputStream(args[0]);
HSSFWorkbook wb = new HSSFWorkbook(in);

View File

@ -36,6 +36,31 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44539 - Support for area references in formulas of rows >= 32768</action>
<action dev="POI-DEVELOPERS" type="add">44536 - Improved support for detecting read-only recommended files</action>
<action dev="POI-DEVELOPERS" type="fix">43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)</action>
<action dev="POI-DEVELOPERS" type="fix">28231 - For apparently truncated files, which are somehow still valid, now issue a truncation warning but carry on, rather than giving an exception as before</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Improvements to FormulaParser - operators, precedence, error literals, quotes in string literals, range checking on IntPtg, formulas with extra un-parsed stuff at the end, improved parse error handling</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval</action>
<action dev="POI-DEVELOPERS" type="fix">44508 - Fix formula evaluation with evaluateInCell on boolean formulas</action>
<action dev="POI-DEVELOPERS" type="fix">44510 - Fix how DVALRecord works with dropdowns</action>
<action dev="POI-DEVELOPERS" type="fix">44495 - Handle named cell ranges in formulas that have lower case parts</action>
<action dev="POI-DEVELOPERS" type="fix">44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF</action>
<action dev="POI-DEVELOPERS" type="fix">44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this</action>
<action dev="POI-DEVELOPERS" type="add">44450 - Support for Lookup, HLookup and VLookup functions</action>
<action dev="POI-DEVELOPERS" type="fix">44449 - Avoid getting confused when two sheets have shared formulas for the same areas, and when the shared formula is set incorrectly</action>
<action dev="POI-DEVELOPERS" type="fix">44366 - InputStreams passed to POIFSFileSystem are now automatically closed. A warning is generated for people who might've relied on them not being closed before, and a wrapper to restore the old behaviour is supplied</action>
<action dev="POI-DEVELOPERS" type="add">44371 - Support for the Offset function</action>
<action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
<action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
<action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
<action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
<action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>
<action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action>
<action dev="POI-DEVELOPERS" type="fix">44403 - Fix for Mid function handling its arguments wrong</action>
<action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
<action dev="POI-DEVELOPERS" type="fix">44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored.</action>
<action dev="POI-DEVELOPERS" type="fix">38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour)</action>
<action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>

View File

@ -53,6 +53,43 @@
license.
</p>
</section>
<section><title>Publicly Available Information on the file formats</title>
<p>
In early 2008, Microsoft made a fairly complete set of documentation
on the binary file formats freely and publicly available. These were
released under the <link href="http://www.microsoft.com/interop/osp">Open
Specification Promise</link>, which does allow us to use them for
building open source software under the <link
href="http://www.apache.org/foundation/licence-FAQ.html">
Apache Software License</link>.
</p>
<p>
You can download the documentation on Excel, Word, PowerPoint and
Escher (drawing) from
<link href="http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx">http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx</link>.
Documentation on a few of the supporting technologies used in these
file formats can be downloaded from
<link href="http://www.microsoft.com/interop/docs/supportingtechnologies.mspx">http://www.microsoft.com/interop/docs/supportingtechnologies.mspx</link>.
</p>
<p>
Previously, Microsoft published a book on the Excel 97 file format.
It can still be of plenty of use, and is handy dead tree form. Pick up
a copy of "Excel 97 Developer's Kit" from your favourite second hand
book store.
</p>
<p>
The newer Office Open XML (ooxml) file formats are documented as part
of the ECMA / ISO standardisation effort for the formats. This
documentation is quite large, but you can normally find the bit you
need without too much effort! This can be downloaded from
<link href="http://www.ecma-international.org/publications/standards/Ecma-376.htm">http://www.ecma-international.org/publications/standards/Ecma-376.htm</link>,
and is also under the <link href="http://www.microsoft.com/interop/osp">OSP</link>.
</p>
<p>
It is also worth checking the documentation and code of the other
open source implementations of the file formats.
</p>
</section>
<section><title>I just signed an NDA to get a spec from Microsoft and I'd like to contribute</title>
<p>
In short, stay away, stay far far away. Implementing these file formats
@ -66,13 +103,14 @@
<p>
If you've ever received information regarding the OLE 2 Compound Document
Format under any type of exclusionary agreement from Microsoft, or
(probably illegally) received such information from a person bound by
such an agreement, you cannot participate in this project. (Sorry)
(possibly illegally) received such information from a person bound by
such an agreement, you cannot participate in this project. (Sorry)
</p>
<p>
Those submitting patches that show insight into the file format may be
asked to state explicitly that they are eligible or possibly sign an
agreement.
asked to state explicitly that they have only ever read the publicly
available file format information, and not any received under an NDA
or similar.
</p>
</section>
</section>
@ -86,7 +124,9 @@
<li>Documentation is always the best place to start contributing, maybe you found that if the documentation just told you how to do X then it would make more sense, modify the documentation.</li>
<li>Get used to building POI, you'll be doing it a lot, be one with the build, know its targets, etc.</li>
<li>Write Unit Tests. Great way to understand POI. Look for classes that aren't tested, or aren't tested on a public/protected method level, start there.</li>
<li>(HSSF)Get the Excel 97 Developer's Kit - its out of print but its dirt cheap (seen copies for under $15 (US)) used on <link href="http://www.amazon.com">amazon</link>. It explains the Excel file format.</li>
<li>Download the file format documentation from Microsoft -
<link href="http://www.microsoft.com/interop/docs/OfficeBinaryFormats.mspx">OLE2 Binary File Formats</link> or
<link href="http://www.ecma-international.org/publications/standards/Ecma-376.htm">OOXML XML File Formats</link></li>
<li>Submit patches (see below) of your contributions, modifications.</li>
<li>Fill out new features, see <link href="http://issues.apache.org/bugzilla/buglist.cgi?product=POI">Bug database</link> for suggestions.</li>
</ul>

View File

@ -45,8 +45,8 @@
<link href="http://svn.apache.org/viewcvs.cgi/poi/trunk/src/scratchpad/">scratchpad area</link>
of the POI SVN repository.
Ensure that you have the scratchpad jar or the scratchpad
build area in your
classpath before experimenting with this code.
build area in your classpath before experimenting with
this code - the main POI jar is not enough.
</note>
<p>The <link href="./quick-guide.html">quick guide</link> documentation provides
information on using this API. Comments and fixes gratefully accepted on the POI

View File

@ -33,6 +33,36 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44539 - Support for area references in formulas of rows >= 32768</action>
<action dev="POI-DEVELOPERS" type="add">44536 - Improved support for detecting read-only recommended files</action>
<action dev="POI-DEVELOPERS" type="fix">43901 - Correctly update the internal last cell number when adding and removing cells (previously sometimes off-by-one)</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Improvements to FormulaParser - operators, precedence, error literals, quotes in string literals, range checking on IntPtg, formulas with extra un-parsed stuff at the end, improved parse error handling</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Added initial support for recognising external functions like YEARFRAC and ISEVEN (using NameXPtg), via LinkTable support</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Improvements to FormulaParser - operators, precedence, error literals, quotes in string literals, range checking on IntPtg, formulas with extra un-parsed stuff at the end, improved parse error handling</action>
<action dev="POI-DEVELOPERS" type="fix">44504 - Fixed number conversion inconsistencies in many functions, and improved RefEval</action>
<action dev="POI-DEVELOPERS" type="fix">44508 - Fix formula evaluation with evaluateInCell on boolean formulas</action>
<action dev="POI-DEVELOPERS" type="fix">44510 - Fix how DVALRecord works with dropdowns</action>
<action dev="POI-DEVELOPERS" type="fix">44495 - Handle named cell ranges in formulas that have lower case parts</action>
<action dev="POI-DEVELOPERS" type="fix">44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF</action>
<action dev="POI-DEVELOPERS" type="fix">44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this</action>
<action dev="POI-DEVELOPERS" type="fix">44495 - Handle named cell ranges in formulas that have lower case parts</action>
<action dev="POI-DEVELOPERS" type="fix">44491 - Don't have the new-style "HPSF properties are always available" affect the old-style use of HPSF alongside HSSF</action>
<action dev="POI-DEVELOPERS" type="fix">44471 - Crystal Reports generates files with short StyleRecords, which isn't allowed in the spec. Work around this</action>
<action dev="POI-DEVELOPERS" type="add">44450 - Support for Lookup, HLookup and VLookup functions</action>
<action dev="POI-DEVELOPERS" type="fix">44449 - Avoid getting confused when two sheets have shared formulas for the same areas, and when the shared formula is set incorrectly</action>
<action dev="POI-DEVELOPERS" type="fix">44366 - InputStreams passed to POIFSFileSystem are now automatically closed. A warning is generated for people who might've relied on them not being closed before, and a wrapper to restore the old behaviour is supplied</action>
<action dev="POI-DEVELOPERS" type="add">44371 - Support for the Offset function</action>
<action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
<action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
<action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
<action dev="POI-DEVELOPERS" type="fix">44410 - Support for whole-column ranges, such as C:C, in formula strings and the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44421 - Update Match function to properly support Area references</action>
<action dev="POI-DEVELOPERS" type="fix">44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name</action>
<action dev="POI-DEVELOPERS" type="fix">44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself</action>
<action dev="POI-DEVELOPERS" type="fix">44403 - Fix for Mid function handling its arguments wrong</action>
<action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
<action dev="POI-DEVELOPERS" type="fix">44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored.</action>
<action dev="POI-DEVELOPERS" type="fix">38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour)</action>
<action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>

View File

@ -54,16 +54,24 @@ public abstract class POIDocument {
/** For our own logging use */
protected POILogger logger = POILogFactory.getLogger(this.getClass());
/**
/* Have the property streams been read yet? (Only done on-demand) */
protected boolean initialized = false;
/**
* Fetch the Document Summary Information of the document
*/
public DocumentSummaryInformation getDocumentSummaryInformation() { return dsInf; }
public DocumentSummaryInformation getDocumentSummaryInformation() {
if(!initialized) readProperties();
return dsInf;
}
/**
* Fetch the Summary Information of the document
*/
public SummaryInformation getSummaryInformation() { return sInf; }
public SummaryInformation getSummaryInformation() {
if(!initialized) readProperties();
return sInf;
}
/**
* Find, and create objects for, the standard
@ -89,6 +97,9 @@ public abstract class POIDocument {
} else if(ps != null) {
logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass());
}
// Mark the fact that we've now loaded up the properties
initialized = true;
}
/**
@ -133,7 +144,7 @@ public abstract class POIDocument {
* @param writtenEntries a list of POIFS entries to add the property names too
*/
protected void writeProperties(POIFSFileSystem outFS, List writtenEntries) throws IOException {
if(sInf != null) {
if(sInf != null) {
writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME,sInf,outFS);
if(writtenEntries != null) {
writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,302 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.poi.hssf.record.CRNCountRecord;
import org.apache.poi.hssf.record.CRNRecord;
import org.apache.poi.hssf.record.CountryRecord;
import org.apache.poi.hssf.record.ExternSheetRecord;
import org.apache.poi.hssf.record.ExternSheetSubRecord;
import org.apache.poi.hssf.record.ExternalNameRecord;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SupBookRecord;
/**
* Link Table (OOO pdf reference: 4.10.3 ) <p/>
*
* The main data of all types of references is stored in the Link Table inside the Workbook Globals
* Substream (4.2.5). The Link Table itself is optional and occurs only, if there are any
* references in the document.
* <p/>
*
* In BIFF8 the Link Table consists of
* <ul>
* <li>one or more EXTERNALBOOK Blocks<p/>
* each consisting of
* <ul>
* <li>exactly one EXTERNALBOOK (0x01AE) record</li>
* <li>zero or more EXTERNALNAME (0x0023) records</li>
* <li>zero or more CRN Blocks<p/>
* each consisting of
* <ul>
* <li>exactly one XCT (0x0059)record</li>
* <li>zero or more CRN (0x005A) records (documentation says one or more)</li>
* </ul>
* </li>
* </ul>
* </li>
* <li>exactly one EXTERNSHEET (0x0017) record</li>
* <li>zero or more DEFINEDNAME (0x0018) records</li>
* </ul>
*
*
* @author Josh Micich
*/
final class LinkTable {
private static final class CRNBlock {
private final CRNCountRecord _countRecord;
private final CRNRecord[] _crns;
public CRNBlock(RecordStream rs) {
_countRecord = (CRNCountRecord) rs.getNext();
int nCRNs = _countRecord.getNumberOfCRNs();
CRNRecord[] crns = new CRNRecord[nCRNs];
for (int i = 0; i < crns.length; i++) {
crns[i] = (CRNRecord) rs.getNext();
}
_crns = crns;
}
public CRNRecord[] getCrns() {
return (CRNRecord[]) _crns.clone();
}
}
private static final class ExternalBookBlock {
private final SupBookRecord _externalBookRecord;
private final ExternalNameRecord[] _externalNameRecords;
private final CRNBlock[] _crnBlocks;
public ExternalBookBlock(RecordStream rs) {
_externalBookRecord = (SupBookRecord) rs.getNext();
List temp = new ArrayList();
while(rs.peekNextClass() == ExternalNameRecord.class) {
temp.add(rs.getNext());
}
_externalNameRecords = new ExternalNameRecord[temp.size()];
temp.toArray(_externalNameRecords);
temp.clear();
while(rs.peekNextClass() == CRNCountRecord.class) {
temp.add(new CRNBlock(rs));
}
_crnBlocks = new CRNBlock[temp.size()];
temp.toArray(_crnBlocks);
}
public ExternalBookBlock(short numberOfSheets) {
_externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets);
_externalNameRecords = new ExternalNameRecord[0];
_crnBlocks = new CRNBlock[0];
}
public SupBookRecord getExternalBookRecord() {
return _externalBookRecord;
}
public String getNameText(int definedNameIndex) {
return _externalNameRecords[definedNameIndex].getText();
}
}
private final ExternalBookBlock[] _externalBookBlocks;
private final ExternSheetRecord _externSheetRecord;
private final List _definedNames;
private final int _recordCount;
private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this
public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList) {
_workbookRecordList = workbookRecordList;
RecordStream rs = new RecordStream(inputList, startIndex);
List temp = new ArrayList();
while(rs.peekNextClass() == SupBookRecord.class) {
temp.add(new ExternalBookBlock(rs));
}
if(temp.size() < 1) {
throw new RuntimeException("Need at least one EXTERNALBOOK blocks");
}
_externalBookBlocks = new ExternalBookBlock[temp.size()];
temp.toArray(_externalBookBlocks);
temp.clear();
// If link table is present, there is always 1 of ExternSheetRecord
Record next = rs.getNext();
_externSheetRecord = (ExternSheetRecord)next;
_definedNames = new ArrayList();
// collect zero or more DEFINEDNAMEs id=0x18
while(rs.peekNextClass() == NameRecord.class) {
NameRecord nr = (NameRecord)rs.getNext();
_definedNames.add(nr);
}
_recordCount = rs.getCountRead();
_workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount));
}
public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) {
_workbookRecordList = workbookRecordList;
_definedNames = new ArrayList();
_externalBookBlocks = new ExternalBookBlock[] {
new ExternalBookBlock(numberOfSheets),
};
_externSheetRecord = new ExternSheetRecord();
_recordCount = 2;
// tell _workbookRecordList about the 2 new records
SupBookRecord supbook = _externalBookBlocks[0].getExternalBookRecord();
int idx = findFirstRecordLocBySid(CountryRecord.sid);
if(idx < 0) {
throw new RuntimeException("CountryRecord not found");
}
_workbookRecordList.add(idx+1, _externSheetRecord);
_workbookRecordList.add(idx+1, supbook);
}
/**
* TODO - would not be required if calling code used RecordStream or similar
*/
public int getRecordCount() {
return _recordCount;
}
public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) {
Iterator iterator = _definedNames.iterator();
while (iterator.hasNext()) {
NameRecord record = ( NameRecord ) iterator.next();
//print areas are one based
if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
return record;
}
}
return null;
}
public void removeBuiltinRecord(byte name, int sheetIndex) {
//the name array is smaller so searching through it should be faster than
//using the findFirstXXXX methods
NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
if (record != null) {
_definedNames.remove(record);
}
// TODO - do we need "Workbook.records.remove(...);" similar to that in Workbook.removeName(int namenum) {}?
}
public int getNumNames() {
return _definedNames.size();
}
public NameRecord getNameRecord(int index) {
return (NameRecord) _definedNames.get(index);
}
public void addName(NameRecord name) {
_definedNames.add(name);
// TODO - this is messy
// Not the most efficient way but the other way was causing too many bugs
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
int countNames = _definedNames.size();
_workbookRecordList.add(idx+countNames, name);
}
public void removeName(int namenum) {
_definedNames.remove(namenum);
}
public short getIndexToSheet(short num) {
return _externSheetRecord.getREFRecordAt(num).getIndexToFirstSupBook();
}
public int getSheetIndexFromExternSheetIndex(int externSheetNumber) {
if (externSheetNumber >= _externSheetRecord.getNumOfREFStructures()) {
return -1;
}
return _externSheetRecord.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
}
public short addSheetIndexToExternSheet(short sheetNumber) {
ExternSheetSubRecord record = new ExternSheetSubRecord();
record.setIndexToFirstSupBook(sheetNumber);
record.setIndexToLastSupBook(sheetNumber);
_externSheetRecord.addREFRecord(record);
_externSheetRecord.setNumOfREFStructures((short)(_externSheetRecord.getNumOfREFStructures() + 1));
return (short)(_externSheetRecord.getNumOfREFStructures() - 1);
}
public short checkExternSheet(int sheetNumber) {
//Trying to find reference to this sheet
int nESRs = _externSheetRecord.getNumOfREFStructures();
for(short i=0; i< nESRs; i++) {
ExternSheetSubRecord esr = _externSheetRecord.getREFRecordAt(i);
if (esr.getIndexToFirstSupBook() == sheetNumber
&& esr.getIndexToLastSupBook() == sheetNumber){
return i;
}
}
//We Haven't found reference to this sheet
return addSheetIndexToExternSheet((short) sheetNumber);
}
/**
* copied from Workbook
*/
private int findFirstRecordLocBySid(short sid) {
int index = 0;
for (Iterator iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) {
Record record = ( Record ) iterator.next();
if (record.getSid() == sid) {
return index;
}
index ++;
}
return -1;
}
public int getNumberOfREFStructures() {
return _externSheetRecord.getNumOfREFStructures();
}
public String resolveNameXText(int refIndex, int definedNameIndex) {
short extBookIndex = _externSheetRecord.getREFRecordAt(refIndex).getIndexToSupBook();
return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex);
}
}

View File

@ -0,0 +1,65 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.model;
import java.util.List;
import org.apache.poi.hssf.record.Record;
/**
* Simplifies iteration over a sequence of <tt>Record</tt> objects.
*
* @author Josh Micich
*/
final class RecordStream {
private final List _list;
private int _nextIndex;
private int _countRead;
public RecordStream(List inputList, int startIndex) {
_list = inputList;
_nextIndex = startIndex;
_countRead = 0;
}
public boolean hasNext() {
return _nextIndex < _list.size();
}
public Record getNext() {
if(_nextIndex >= _list.size()) {
throw new RuntimeException("Attempt to read past end of record stream");
}
_countRead ++;
return (Record) _list.get(_nextIndex++);
}
/**
* @return the {@link Class} of the next Record. <code>null</code> if this stream is exhausted.
*/
public Class peekNextClass() {
if(_nextIndex >= _list.size()) {
return null;
}
return _list.get(_nextIndex).getClass();
}
public int getCountRead() {
return _countRead;
}
}

View File

@ -79,10 +79,8 @@ public class Workbook implements Model
*/
protected SSTRecord sst = null;
/**
* Holds the Extern Sheet with references to bound sheets
*/
protected ExternSheetRecord externSheet= null;
private LinkTable linkTable; // optionally occurs if there are references in the document. (4.10.3)
/**
* holds the "boundsheet" records (aka bundlesheet) so that they can have their
@ -92,8 +90,6 @@ public class Workbook implements Model
protected ArrayList formats = new ArrayList();
protected ArrayList names = new ArrayList();
protected ArrayList hyperlinks = new ArrayList();
protected int numxfs = 0; // hold the number of extended format records
@ -134,6 +130,7 @@ public class Workbook implements Model
new Integer(recs.size()));
Workbook retval = new Workbook();
ArrayList records = new ArrayList(recs.size() / 3);
retval.records.setRecords(records);
int k;
for (k = 0; k < recs.size(); k++) {
@ -192,21 +189,16 @@ public class Workbook implements Model
retval.records.setBackuppos( k );
break;
case ExternSheetRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found extern sheet record at " + k);
retval.externSheet = ( ExternSheetRecord ) rec;
break;
throw new RuntimeException("Extern sheet is part of LinkTable");
case NameRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found name record at " + k);
retval.names.add(rec);
// retval.records.namepos = k;
break;
throw new RuntimeException("DEFINEDNAME is part of LinkTable");
case SupBookRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found SupBook record at " + k);
retval.linkTable = new LinkTable(recs, k, retval.records);
// retval.records.supbookpos = k;
break;
k+=retval.linkTable.getRecordCount() - 1;
continue;
case FormatRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found format record at " + k);
@ -262,8 +254,6 @@ public class Workbook implements Model
break;
}
}
retval.records.setRecords(records);
if (retval.windowOne == null) {
retval.windowOne = (WindowOneRecord) retval.createWindowOne();
@ -283,6 +273,7 @@ public class Workbook implements Model
log.log( DEBUG, "creating new workbook from scratch" );
Workbook retval = new Workbook();
ArrayList records = new ArrayList( 30 );
retval.records.setRecords(records);
ArrayList formats = new ArrayList( 8 );
records.add( retval.createBOF() );
@ -339,8 +330,9 @@ public class Workbook implements Model
records.add( retval.createStyle( k ) );
}
records.add( retval.createUseSelFS() );
for ( int k = 0; k < 1; k++ )
{ // now just do 1
int nBoundSheets = 1; // now just do 1
for ( int k = 0; k < nBoundSheets; k++ ) {
BoundSheetRecord bsr =
(BoundSheetRecord) retval.createBoundSheet( k );
@ -351,12 +343,14 @@ public class Workbook implements Model
// retval.records.supbookpos = retval.records.bspos + 1;
// retval.records.namepos = retval.records.supbookpos + 2;
records.add( retval.createCountry() );
for ( int k = 0; k < nBoundSheets; k++ ) {
retval.getOrCreateLinkTable().checkExternSheet(k);
}
retval.sst = (SSTRecord) retval.createSST();
records.add( retval.sst );
records.add( retval.createExtendedSST() );
records.add( retval.createEOF() );
retval.records.setRecords(records);
if (log.check( POILogger.DEBUG ))
log.log( DEBUG, "exit create new workbook from scratch" );
return retval;
@ -369,36 +363,20 @@ public class Workbook implements Model
* @param sheetIndex Index to match
* @return null if no builtin NameRecord matches
*/
public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
{
Iterator iterator = names.iterator();
while (iterator.hasNext()) {
NameRecord record = ( NameRecord ) iterator.next();
//print areas are one based
if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
return record;
}
}
return null;
}
public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
{
return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex);
}
/**
* Removes the specified Builtin NameRecord that matches the name and index
* @param name byte representation of the builtin to match
* @param sheetIndex zero-based sheet reference
*/
public void removeBuiltinRecord(byte name, int sheetIndex) {
//the name array is smaller so searching through it should be faster than
//using the findFirstXXXX methods
NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
if (record != null) {
names.remove(record);
}
}
public void removeBuiltinRecord(byte name, int sheetIndex) {
linkTable.removeBuiltinRecord(name, sheetIndex);
// TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}?
}
public int getNumRecords() {
return records.size();
@ -614,6 +592,7 @@ public class Workbook implements Model
records.add(records.getBspos()+1, bsr);
records.setBspos( records.getBspos() + 1 );
boundsheets.add(bsr);
getOrCreateLinkTable().checkExternSheet(sheetnum);
fixTabIdRecord();
}
}
@ -1824,14 +1803,26 @@ public class Workbook implements Model
protected Record createEOF() {
return new EOFRecord();
}
/**
* lazy initialization
* Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record
*/
private LinkTable getOrCreateLinkTable() {
if(linkTable == null) {
linkTable = new LinkTable((short) getNumSheets(), records);
}
return linkTable;
}
public SheetReferences getSheetReferences() {
SheetReferences refs = new SheetReferences();
if (externSheet != null) {
for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) {
if (linkTable != null) {
int numRefStructures = linkTable.getNumberOfREFStructures();
for (short k = 0; k < numRefStructures; k++) {
String sheetName = findSheetNameFromExternSheet((short)k);
String sheetName = findSheetNameFromExternSheet(k);
refs.addSheetReference(sheetName, k);
}
@ -1846,7 +1837,8 @@ public class Workbook implements Model
public String findSheetNameFromExternSheet(short num){
String result="";
short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook();
short indexToSheet = linkTable.getIndexToSheet(num);
if (indexToSheet>-1) { //error check, bail out gracefully!
result = getSheetName(indexToSheet);
}
@ -1861,10 +1853,7 @@ public class Workbook implements Model
*/
public int getSheetIndexFromExternSheetIndex(int externSheetNumber)
{
if (externSheetNumber >= externSheet.getNumOfREFStructures())
return -1;
else
return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber);
}
/** returns the extern sheet number for specific sheet number ,
@ -1873,58 +1862,17 @@ public class Workbook implements Model
* @return index to extern sheet
*/
public short checkExternSheet(int sheetNumber){
int i = 0;
boolean flag = false;
short result = 0;
if (externSheet == null) {
externSheet = createExternSheet();
}
//Trying to find reference to this sheet
while (i < externSheet.getNumOfREFStructures() && !flag){
ExternSheetSubRecord record = externSheet.getREFRecordAt(i);
if (record.getIndexToFirstSupBook() == sheetNumber &&
record.getIndexToLastSupBook() == sheetNumber){
flag = true;
result = (short) i;
}
++i;
}
//We Havent found reference to this sheet
if (!flag) {
result = addSheetIndexToExternSheet((short) sheetNumber);
}
return result;
return getOrCreateLinkTable().checkExternSheet(sheetNumber);
}
private short addSheetIndexToExternSheet(short sheetNumber){
short result;
ExternSheetSubRecord record = new ExternSheetSubRecord();
record.setIndexToFirstSupBook(sheetNumber);
record.setIndexToLastSupBook(sheetNumber);
externSheet.addREFRecord(record);
externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1));
result = (short)(externSheet.getNumOfREFStructures() - 1);
return result;
}
/** gets the total number of names
* @return number of names
*/
public int getNumNames(){
int result = names.size();
return result;
if(linkTable == null) {
return 0;
}
return linkTable.getNumNames();
}
/** gets the name record
@ -1932,28 +1880,14 @@ public class Workbook implements Model
* @return name record
*/
public NameRecord getNameRecord(int index){
NameRecord result = (NameRecord) names.get(index);
return result;
return linkTable.getNameRecord(index);
}
/** creates new name
* @return new name record
*/
public NameRecord createName(){
NameRecord name = new NameRecord();
// Not the most efficient way but the other way was causing too many bugs
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
records.add(idx+names.size()+1, name);
names.add(name);
return name;
return addName(new NameRecord());
}
@ -1962,67 +1896,41 @@ public class Workbook implements Model
*/
public NameRecord addName(NameRecord name)
{
// Not the most efficient way but the other way was causing too many bugs
int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
records.add(idx+names.size()+1, name);
names.add(name);
getOrCreateLinkTable().addName(name);
return name;
}
/**Generates a NameRecord to represent a built-in region
* @return a new NameRecord unless the index is invalid
*/
public NameRecord createBuiltInName(byte builtInName, int index)
{
if (index == -1 || index+1 > (int)Short.MAX_VALUE)
throw new IllegalArgumentException("Index is not valid ["+index+"]");
NameRecord name = new NameRecord(builtInName, (short)(index));
addName(name);
return name;
}
/**Generates a NameRecord to represent a built-in region
* @return a new NameRecord unless the index is invalid
*/
public NameRecord createBuiltInName(byte builtInName, int index)
{
if (index == -1 || index+1 > Short.MAX_VALUE)
throw new IllegalArgumentException("Index is not valid ["+index+"]");
NameRecord name = new NameRecord(builtInName, (short)(index));
addName(name);
return name;
}
/** removes the name
* @param namenum name index
*/
public void removeName(int namenum){
if (names.size() > namenum) {
if (linkTable.getNumNames() > namenum) {
int idx = findFirstRecordLocBySid(NameRecord.sid);
records.remove(idx + namenum);
names.remove(namenum);
linkTable.removeName(namenum);
}
}
/** creates a new extern sheet record
* @return the new extern sheet record
*/
protected ExternSheetRecord createExternSheet(){
ExternSheetRecord externSheet = new ExternSheetRecord();
int idx = findFirstRecordLocBySid(CountryRecord.sid);
records.add(idx+1, externSheet);
// records.add(records.supbookpos + 1 , rec);
//We also adds the supBook for internal reference
SupBookRecord supbook = new SupBookRecord();
supbook.setNumberOfSheets((short)getNumSheets());
//supbook.setFlag();
records.add(idx+1, supbook);
// records.add(records.supbookpos + 1 , supbook);
return externSheet;
}
/**
* Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat.
* @param format the format string
@ -2392,6 +2300,17 @@ public class Workbook implements Model
}
return this.fileShare;
}
/**
* is the workbook protected with a password (not encrypted)?
*/
public boolean isWriteProtected() {
if (this.fileShare == null) {
return false;
}
FileSharingRecord frec = getFileSharing();
return (frec.getReadOnly() == 1);
}
/**
* protect a workbook with a password (not encypted, just sets writeprotect
@ -2419,5 +2338,14 @@ public class Workbook implements Model
writeProtect = null;
}
/**
* @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table
* @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record
* @return the string representation of the defined or external name
*/
public String resolveNameXText(int refIndex, int definedNameIndex) {
return linkTable.resolveNameXText(refIndex, definedNameIndex);
}
}

View File

@ -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.hssf.record;
import org.apache.poi.util.LittleEndian;
/**
* XCT CRN Count <P>
*
* REFERENCE: 5.114<P>
*
* @author Josh Micich
*/
public final class CRNCountRecord extends Record {
public final static short sid = 0x59;
private static final short BASE_RECORD_SIZE = 4;
private int field_1_number_crn_records;
private int field_2_sheet_table_index;
public CRNCountRecord() {
throw new RuntimeException("incomplete code");
}
public CRNCountRecord(RecordInputStream in) {
super(in);
}
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT An XCT RECORD");
}
}
public int getNumberOfCRNs() {
return field_1_number_crn_records;
}
protected void fillFields(RecordInputStream in) {
field_1_number_crn_records = in.readShort();
if(field_1_number_crn_records < 0) {
// TODO - seems like the sign bit of this field might be used for some other purpose
// see example file for test case "TestBugs.test19599()"
field_1_number_crn_records = (short)-field_1_number_crn_records;
}
field_2_sheet_table_index = in.readShort();
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName()).append(" [XCT");
sb.append(" nCRNs=").append(field_1_number_crn_records);
sb.append(" sheetIx=").append(field_2_sheet_table_index);
sb.append("]");
return sb.toString();
}
public int serialize(int offset, byte [] data) {
LittleEndian.putShort(data, 0 + offset, sid);
LittleEndian.putShort(data, 2 + offset, BASE_RECORD_SIZE);
LittleEndian.putShort(data, 4 + offset, (short)field_1_number_crn_records);
LittleEndian.putShort(data, 6 + offset, (short)field_2_sheet_table_index);
return getRecordSize();
}
public int getRecordSize() {
return BASE_RECORD_SIZE + 4;
}
/**
* return the non static version of the id for this record.
*/
public short getSid() {
return sid;
}
}

View File

@ -0,0 +1,99 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.record;
import org.apache.poi.hssf.record.constant.ConstantValueParser;
import org.apache.poi.util.LittleEndian;
/**
* Title: CRN <P>
* Description: This record stores the contents of an external cell or cell range <P>
* REFERENCE: 5.23<P>
*
* @author josh micich
*/
public final class CRNRecord extends Record {
public final static short sid = 0x5A;
private int field_1_last_column_index;
private int field_2_first_column_index;
private int field_3_row_index;
private Object[] field_4_constant_values;
public CRNRecord() {
throw new RuntimeException("incomplete code");
}
public CRNRecord(RecordInputStream in) {
super(in);
}
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT An XCT RECORD");
}
}
public int getNumberOfCRNs() {
return field_1_last_column_index;
}
protected void fillFields(RecordInputStream in) {
field_1_last_column_index = in.readByte() & 0x00FF;
field_2_first_column_index = in.readByte() & 0x00FF;
field_3_row_index = in.readShort();
int nValues = field_1_last_column_index - field_2_first_column_index + 1;
field_4_constant_values = ConstantValueParser.parse(in, nValues);
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName()).append(" [CRN");
sb.append(" rowIx=").append(field_3_row_index);
sb.append(" firstColIx=").append(field_2_first_column_index);
sb.append(" lastColIx=").append(field_1_last_column_index);
sb.append("]");
return sb.toString();
}
private int getDataSize() {
return 4 + ConstantValueParser.getEncodedSize(field_4_constant_values);
}
public int serialize(int offset, byte [] data) {
int dataSize = getDataSize();
LittleEndian.putShort(data, 0 + offset, sid);
LittleEndian.putShort(data, 2 + offset, (short) dataSize);
LittleEndian.putByte(data, 4 + offset, field_1_last_column_index);
LittleEndian.putByte(data, 5 + offset, field_2_first_column_index);
LittleEndian.putShort(data, 6 + offset, (short) field_3_row_index);
return getRecordSize();
}
public int getRecordSize() {
return getDataSize() + 4;
}
/**
* return the non static version of the id for this record.
*/
public short getSid() {
return sid;
}
}

View File

@ -1,4 +1,3 @@
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
@ -20,13 +19,11 @@ package org.apache.poi.hssf.record;
import org.apache.poi.util.LittleEndian;
/**
* Title: DVAL Record<P>
* Title: DATAVALIDATIONS Record<P>
* Description: used in data validation ;
* This record is the list header of all data validation records in the current sheet.
* This record is the list header of all data validation records (0x01BE) in the current sheet.
* @author Dragos Buleandra (dragos.buleandra@trade2b.ro)
* @version 2.0-pre
*/
public class DVALRecord extends Record
{
public final static short sid = 0x01B2;
@ -41,13 +38,14 @@ public class DVALRecord extends Record
/** Object ID of the drop down arrow object for list boxes ;
* in our case this will be always FFFF , until
* MSODrawingGroup and MSODrawing records are implemented */
private int field_cbo_id = 0xFFFFFFFF;
private int field_cbo_id;
/** Number of following DV Records */
private int field_5_dv_no = 0x00000000;
private int field_5_dv_no;
public DVALRecord()
{
public DVALRecord() {
field_cbo_id = 0xFFFFFFFF;
field_5_dv_no = 0x00000000;
}
/**

View File

@ -0,0 +1,179 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.record;
import java.util.List;
import java.util.Stack;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.StringUtil;
/**
* EXTERNALNAME<p/>
*
* @author josh micich
*/
public final class ExternalNameRecord extends Record {
public final static short sid = 0x23; // as per BIFF8. (some old versions used 0x223)
private static final int OPT_BUILTIN_NAME = 0x0001;
private static final int OPT_AUTOMATIC_LINK = 0x0002;
private static final int OPT_PICTURE_LINK = 0x0004;
private static final int OPT_STD_DOCUMENT_NAME = 0x0008;
private static final int OPT_OLE_LINK = 0x0010;
// private static final int OPT_CLIP_FORMAT_MASK = 0x7FE0;
private static final int OPT_ICONIFIED_PICTURE_LINK = 0x8000;
private short field_1_option_flag;
private short field_2_index;
private short field_3_not_used;
private String field_4_name;
private Stack field_5_name_definition;
public ExternalNameRecord(RecordInputStream in) {
super(in);
}
/**
* Convenience Function to determine if the name is a built-in name
*/
public boolean isBuiltInName() {
return (field_1_option_flag & OPT_BUILTIN_NAME) != 0;
}
/**
* For OLE and DDE, links can be either 'automatic' or 'manual'
*/
public boolean isAutomaticLink() {
return (field_1_option_flag & OPT_AUTOMATIC_LINK) != 0;
}
/**
* only for OLE and DDE
*/
public boolean isPicureLink() {
return (field_1_option_flag & OPT_PICTURE_LINK) != 0;
}
/**
* DDE links only. If <code>true</code>, this denotes the 'StdDocumentName'
*/
public boolean isStdDocumentNameIdentifier() {
return (field_1_option_flag & OPT_STD_DOCUMENT_NAME) != 0;
}
public boolean isOLELink() {
return (field_1_option_flag & OPT_OLE_LINK) != 0;
}
public boolean isIconifiedPictureLink() {
return (field_1_option_flag & OPT_ICONIFIED_PICTURE_LINK) != 0;
}
/**
* @return the standard String representation of this name
*/
public String getText() {
return field_4_name;
}
/**
* called by constructor, should throw runtime exception in the event of a
* record passed with a differing ID.
*
* @param id alleged id for this record
*/
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT A valid ExternalName RECORD");
}
}
private int getDataSize(){
return 2 + 2 + field_4_name.length() + 2 + getNameDefinitionSize();
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
* byte array.
*
* @param offset to begin writing at
* @param data byte array containing instance data
* @return number of bytes written
*/
public int serialize( int offset, byte[] data ) {
// TODO - junit tests
int dataSize = getDataSize();
LittleEndian.putShort( data, 0 + offset, sid );
LittleEndian.putShort( data, 2 + offset, (short) dataSize );
LittleEndian.putShort( data, 4 + offset, field_1_option_flag );
LittleEndian.putShort( data, 6 + offset, field_2_index );
LittleEndian.putShort( data, 8 + offset, field_3_not_used );
short nameLen = (short) field_4_name.length();
LittleEndian.putShort( data, 10 + offset, nameLen );
StringUtil.putCompressedUnicode( field_4_name, data, 10 + offset );
short defLen = (short) getNameDefinitionSize();
LittleEndian.putShort( data, 12 + nameLen + offset, defLen );
Ptg.serializePtgStack(field_5_name_definition, data, 12 + nameLen + offset );
return dataSize + 4;
}
private int getNameDefinitionSize() {
int result = 0;
List list = field_5_name_definition;
for (int k = 0; k < list.size(); k++)
{
Ptg ptg = ( Ptg ) list.get(k);
result += ptg.getSize();
}
return result;
}
public int getRecordSize(){
return 6 + 2 + field_4_name.length() + 2 + getNameDefinitionSize();
}
protected void fillFields(RecordInputStream in) {
field_1_option_flag = in.readShort();
field_2_index = in.readShort();
field_3_not_used = in.readShort();
short nameLength = in.readShort();
field_4_name = in.readCompressedUnicode(nameLength);
short formulaLen = in.readShort();
field_5_name_definition = Ptg.createParsedExpressionTokens(formulaLen, in);
}
public short getSid() {
return sid;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName()).append(" [EXTERNALNAME ");
sb.append(" ").append(field_4_name);
sb.append(" ix=").append(field_2_index);
sb.append("]");
return sb.toString();
}
}

View File

@ -19,6 +19,8 @@
package org.apache.poi.hssf.record;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.StringUtil;
/**
@ -29,6 +31,8 @@ import org.apache.poi.util.StringUtil;
*/
public class FileSharingRecord extends Record {
private static POILogger logger = POILogFactory.getLogger(FileSharingRecord.class);
public final static short sid = 0x5b;
private short field_1_readonly;
private short field_2_password;
@ -58,8 +62,23 @@ public class FileSharingRecord extends Record {
field_1_readonly = in.readShort();
field_2_password = in.readShort();
field_3_username_length = in.readByte();
// Is this really correct? The latest docs
// seem to hint there's nothing between the
// username length and the username string
field_4_unknown = in.readShort();
field_5_username = in.readCompressedUnicode(field_3_username_length);
// Ensure we don't try to read more data than
// there actually is
if(field_3_username_length > in.remaining()) {
logger.log(POILogger.WARN, "FileSharingRecord defined a username of length " + field_3_username_length + ", but only " + in.remaining() + " bytes were left, truncating");
field_3_username_length = (byte)in.remaining();
}
if(field_3_username_length > 0) {
field_5_username = in.readCompressedUnicode(field_3_username_length);
} else {
field_5_username = "";
}
}
//this is the world's lamest "security". thanks to Wouter van Vugt for making me

View File

@ -726,7 +726,7 @@ public class NameRecord extends Record {
for(int i=0; i<refs.length; i++) {
ptg = new Area3DPtg();
((Area3DPtg) ptg).setExternSheetIndex(externSheetIndex);
((Area3DPtg) ptg).setArea(refs[i].toString());
((Area3DPtg) ptg).setArea(refs[i].formatAsString());
field_13_name_definition.push(ptg);
this.setDefinitionTextLength( (short)(getDefinitionLength() + ptg.getSize()) );
}

View File

@ -77,7 +77,11 @@ public class RecordFactory
NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class,
FileSharingRecord.class, ChartTitleFormatRecord.class,
DVRecord.class, DVALRecord.class, UncalcedRecord.class,
HyperlinkRecord.class
HyperlinkRecord.class,
ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class
SupBookRecord.class,
CRNCountRecord.class,
CRNRecord.class,
};
}
private static Map recordsMap = recordsToMap(records);

View File

@ -267,7 +267,7 @@ public class RecordInputStream extends InputStream
public String readCompressedUnicode(int length) {
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length");
throw new IllegalArgumentException("Illegal length " + length);
}
StringBuffer buf = new StringBuffer(length);

View File

@ -1,4 +1,3 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -15,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record;
import java.util.Stack;
@ -34,10 +32,7 @@ import org.apache.poi.hssf.record.formula.*;
* record types.
* @author Danny Mui at apache dot org
*/
public class SharedFormulaRecord
extends Record
{
public final class SharedFormulaRecord extends Record {
public final static short sid = 0x4BC;
private int field_1_first_row;
@ -58,15 +53,15 @@ public class SharedFormulaRecord
public SharedFormulaRecord(RecordInputStream in)
{
super(in);
super(in);
}
protected void validateSid(short id)
{
if (id != this.sid)
{
throw new RecordFormatException("Not a valid SharedFormula");
}
if (id != this.sid)
{
throw new RecordFormatException("Not a valid SharedFormula");
}
}
public int getFirstRow() {
@ -96,14 +91,14 @@ public class SharedFormulaRecord
public int serialize(int offset, byte [] data)
{
//Because this record is converted to individual Formula records, this method is not required.
throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
//Because this record is converted to individual Formula records, this method is not required.
throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
}
public int getRecordSize()
{
//Because this record is converted to individual Formula records, this method is not required.
throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
//Because this record is converted to individual Formula records, this method is not required.
throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
}
@ -257,39 +252,40 @@ public class SharedFormulaRecord
}
}
private short fixupRelativeColumn(int currentcolumn, short column, boolean relative) {
if(relative) {
if((column&128)!=0) column=(short)(column-256);
column+=currentcolumn;
}
return column;
}
private int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
if(relative) {
// mask out upper bits to produce 'wrapping' at column 256 ("IV")
return (column + currentcolumn) & 0x00FF;
}
return column;
}
private short fixupRelativeRow(int currentrow, short row, boolean relative) {
if(relative) {
row+=currentrow;
}
return row;
}
private int fixupRelativeRow(int currentrow, int row, boolean relative) {
if(relative) {
// mask out upper bits to produce 'wrapping' at row 65536
return (row+currentrow) & 0x00FFFF;
}
return row;
}
/**
* Mirroring formula records so it is registered in the ValueRecordsAggregate
*/
public boolean isInValueSection()
{
return true;
}
/**
* Mirroring formula records so it is registered in the ValueRecordsAggregate
*/
public boolean isInValueSection()
{
return true;
}
/**
* Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
*/
public boolean isValue() {
return true;
}
/**
* Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
*/
public boolean isValue() {
return true;
}
public Object clone() {
//Because this record is converted to individual Formula records, this method is not required.
throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
//Because this record is converted to individual Formula records, this method is not required.
throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
}
}

View File

@ -88,13 +88,18 @@ public class StyleRecord
else if (getType() == STYLE_USER_DEFINED)
{
field_2_name_length = in.readShort();
field_3_string_options = in.readByte();
byte[] string = in.readRemainder();
if (fHighByte.isSet(field_3_string_options)) {
field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length);
}else {
field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length);
// Some files from Crystal Reports lack
// the remaining fields, which is naughty
if(in.remaining() > 0) {
field_3_string_options = in.readByte();
byte[] string = in.readRemainder();
if (fHighByte.isSet(field_3_string_options)) {
field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length);
} else {
field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length);
}
}
}

View File

@ -1,4 +1,3 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -15,72 +14,159 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record;
import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
import org.apache.poi.util.LittleEndian;
/**
* Title: Sup Book <P>
* Description: A Extrenal Workbook Deciption (Sup Book)
* Title: Sup Book (EXTERNALBOOK) <P>
* Description: A External Workbook Description (Suplemental Book)
* Its only a dummy record for making new ExternSheet Record <P>
* REFERENCE: <P>
* REFERENCE: 5.38<P>
* @author Libin Roman (Vista Portal LDT. Developer)
* @author Andrew C. Oliver (acoliver@apache.org)
*
*/
public class SupBookRecord extends Record
{
public final class SupBookRecord extends Record {
public final static short sid = 0x1AE;
private static final short SMALL_RECORD_SIZE = 4;
private static final short TAG_INTERNAL_REFERENCES = 0x0401;
private static final short TAG_ADD_IN_FUNCTIONS = 0x3A01;
private short field_1_number_of_sheets;
private short field_2_flag;
private UnicodeString field_2_encoded_url;
private UnicodeString[] field_3_sheet_names;
private boolean _isAddInFunctions;
public SupBookRecord()
{
setFlag((short)0x401);
public static SupBookRecord createInternalReferences(short numberOfSheets) {
return new SupBookRecord(false, numberOfSheets);
}
public static SupBookRecord createAddInFunctions() {
return new SupBookRecord(true, (short)0);
}
public static SupBookRecord createExternalReferences(UnicodeString url, UnicodeString[] sheetNames) {
return new SupBookRecord(url, sheetNames);
}
private SupBookRecord(boolean isAddInFuncs, short numberOfSheets) {
// else not 'External References'
field_1_number_of_sheets = numberOfSheets;
field_2_encoded_url = null;
field_3_sheet_names = null;
_isAddInFunctions = isAddInFuncs;
}
public SupBookRecord(UnicodeString url, UnicodeString[] sheetNames) {
field_1_number_of_sheets = (short) sheetNames.length;
field_2_encoded_url = url;
field_3_sheet_names = sheetNames;
_isAddInFunctions = false;
}
/**
* Constructs a Extern Sheet record and sets its fields appropriately.
*
* @param in the RecordInputstream to read the record from
* @param id id must be 0x16 or an exception will be throw upon validation
* @param size the size of the data area of the record
* @param data data of the record (should not contain sid/len)
*/
public SupBookRecord(RecordInputStream in)
{
public SupBookRecord(RecordInputStream in) {
super(in);
}
protected void validateSid(short id)
{
if (id != sid)
{
throw new RecordFormatException("NOT An Supbook RECORD");
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT An ExternSheet RECORD");
}
}
/**
* @param in the RecordInputstream to read the record from
*/
protected void fillFields(RecordInputStream in)
{
//For now We use it only for one case
//When we need to add an named range when no named ranges was
//before it
field_1_number_of_sheets = in.readShort();
field_2_flag = in.readShort();
public boolean isExternalReferences() {
return field_3_sheet_names != null;
}
public boolean isInternalReferences() {
return field_3_sheet_names == null && !_isAddInFunctions;
}
public boolean isAddInFunctions() {
return field_3_sheet_names == null && _isAddInFunctions;
}
/**
* called by the constructor, should set class level fields. Should throw
* runtime exception for bad/incomplete data.
*
* @param data raw data
* @param size size of data
* @param offset of the record's data (provided a big array of the file)
*/
protected void fillFields(RecordInputStream in) {
field_1_number_of_sheets = in.readShort();
if(in.getLength() > SMALL_RECORD_SIZE) {
// 5.38.1 External References
_isAddInFunctions = false;
field_2_encoded_url = in.readUnicodeString();
UnicodeString[] sheetNames = new UnicodeString[field_1_number_of_sheets];
for (int i = 0; i < sheetNames.length; i++) {
sheetNames[i] = in.readUnicodeString();
}
field_3_sheet_names = sheetNames;
return;
}
// else not 'External References'
field_2_encoded_url = null;
field_3_sheet_names = null;
short nextShort = in.readShort();
if(nextShort == TAG_INTERNAL_REFERENCES) {
// 5.38.2 'Internal References'
_isAddInFunctions = false;
} else if(nextShort == TAG_ADD_IN_FUNCTIONS) {
// 5.38.3 'Add-In Functions'
_isAddInFunctions = true;
if(field_1_number_of_sheets != 1) {
throw new RuntimeException("Expected 0x0001 for number of sheets field in 'Add-In Functions' but got ("
+ field_1_number_of_sheets + ")");
}
} else {
throw new RuntimeException("invalid EXTERNALBOOK code ("
+ Integer.toHexString(nextShort) + ")");
}
}
public String toString()
{
StringBuffer buffer = new StringBuffer();
buffer.append("[SUPBOOK]\n");
buffer.append("numberosheets = ").append(getNumberOfSheets()).append('\n');
buffer.append("flag = ").append(getFlag()).append('\n');
buffer.append("[/SUPBOOK]\n");
return buffer.toString();
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName()).append(" [SUPBOOK ");
if(isExternalReferences()) {
sb.append("External References");
sb.append(" nSheets=").append(field_1_number_of_sheets);
sb.append(" url=").append(field_2_encoded_url);
} else if(_isAddInFunctions) {
sb.append("Add-In Functions");
} else {
sb.append("Internal References ");
sb.append(" nSheets= ").append(field_1_number_of_sheets);
}
return sb.toString();
}
private int getDataSize() {
if(!isExternalReferences()) {
return SMALL_RECORD_SIZE;
}
int sum = 2; // u16 number of sheets
UnicodeRecordStats urs = new UnicodeRecordStats();
field_2_encoded_url.getRecordSize(urs);
sum += urs.recordSize;
for(int i=0; i<field_3_sheet_names.length; i++) {
urs = new UnicodeRecordStats();
field_3_sheet_names[i].getRecordSize(urs);
sum += urs.recordSize;
}
return sum;
}
/**
@ -92,14 +178,30 @@ public class SupBookRecord extends Record
* @param data byte array containing instance data
* @return number of bytes written
*/
public int serialize(int offset, byte [] data)
{
public int serialize(int offset, byte [] data) {
LittleEndian.putShort(data, 0 + offset, sid);
LittleEndian.putShort(data, 2 + offset, (short) 4);
int dataSize = getDataSize();
LittleEndian.putShort(data, 2 + offset, (short) dataSize);
LittleEndian.putShort(data, 4 + offset, field_1_number_of_sheets);
LittleEndian.putShort(data, 6 + offset, field_2_flag);
return getRecordSize();
if(isExternalReferences()) {
int currentOffset = 6 + offset;
UnicodeRecordStats urs = new UnicodeRecordStats();
field_2_encoded_url.serialize(urs, currentOffset, data);
currentOffset += urs.recordSize;
for(int i=0; i<field_3_sheet_names.length; i++) {
urs = new UnicodeRecordStats();
field_3_sheet_names[i].serialize(urs, currentOffset, data);
currentOffset += urs.recordSize;
}
} else {
short field2val = _isAddInFunctions ? TAG_ADD_IN_FUNCTIONS : TAG_INTERNAL_REFERENCES;
LittleEndian.putShort(data, 6 + offset, field2val);
}
return dataSize + 4;
}
public void setNumberOfSheets(short number){
@ -110,21 +212,18 @@ public class SupBookRecord extends Record
return field_1_number_of_sheets;
}
public void setFlag(short flag){
field_2_flag = flag;
}
public short getFlag() {
return field_2_flag;
}
public int getRecordSize()
{
return 4 + 4;
public int getRecordSize() {
return getDataSize() + 4;
}
public short getSid()
{
return sid;
}
public UnicodeString getURL() {
return field_2_encoded_url;
}
public UnicodeString[] getSheetNames() {
return (UnicodeString[]) field_3_sheet_names.clone();
}
}

View File

@ -34,7 +34,7 @@ import java.util.List;
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class ValueRecordsAggregate
public final class ValueRecordsAggregate
extends Record
{
public final static short sid = -1000;
@ -127,7 +127,7 @@ public class ValueRecordsAggregate
FormulaRecordAggregate lastFormulaAggregate = null;
// First up, locate all the shared formulas
// First up, locate all the shared formulas for this sheet
List sharedFormulas = new java.util.ArrayList();
for (k = offset; k < records.size(); k++)
{
@ -135,6 +135,10 @@ public class ValueRecordsAggregate
if (rec instanceof SharedFormulaRecord) {
sharedFormulas.add(rec);
}
if(rec instanceof EOFRecord) {
// End of current sheet. Ignore all subsequent shared formula records (Bugzilla 44449)
break;
}
}
// Now do the main processing sweep
@ -156,6 +160,8 @@ public class ValueRecordsAggregate
// for us
boolean found = false;
for (int i=sharedFormulas.size()-1;i>=0;i--) {
// TODO - there is no junit test case to justify this reversed loop
// perhaps it could just run in the normal direction?
SharedFormulaRecord shrd = (SharedFormulaRecord)sharedFormulas.get(i);
if (shrd.isFormulaInShared(formula)) {
shrd.convertSharedFormulaRecord(formula);
@ -164,9 +170,7 @@ public class ValueRecordsAggregate
}
}
if (!found) {
//Sometimes the shared formula flag "seems" to be errornously set,
//cant really do much about that.
//throw new RecordFormatException("Could not find appropriate shared formula");
handleMissingSharedFormulaRecord(formula);
}
}
@ -185,6 +189,24 @@ public class ValueRecordsAggregate
return k;
}
/**
* Sometimes the shared formula flag "seems" to be erroneously set, in which case there is no
* call to <tt>SharedFormulaRecord.convertSharedFormulaRecord</tt> and hence the
* <tt>parsedExpression</tt> field of this <tt>FormulaRecord</tt> will not get updated.<br/>
* As it turns out, this is not a problem, because in these circumstances, the existing value
* for <tt>parsedExpression</tt> is perfectly OK.<p/>
*
* This method may also be used for setting breakpoints to help diagnose issues regarding the
* abnormally-set 'shared formula' flags.
* (see TestValueRecordsAggregate.testSpuriousSharedFormulaFlag()).<p/>
*
* The method currently does nothing but do not delete it without finding a nice home for this
* comment.
*/
private static void handleMissingSharedFormulaRecord(FormulaRecord formula) {
// could log an info message here since this is a fairly unusual occurrence.
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
@ -300,7 +322,7 @@ public class ValueRecordsAggregate
return rec;
}
public class MyIterator implements Iterator {
private final class MyIterator implements Iterator {
short nextColumn=-1;
int nextRow,lastRow;

View File

@ -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.hssf.record.constant;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.record.UnicodeString;
import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
/**
* To support Constant Values (2.5.7) as required by the CRN record.
* This class should probably also be used for two dimensional arrays which are encoded by
* EXTERNALNAME (5.39) records and Array tokens.<p/>
* TODO - code in ArrayPtg should be merged with this code. It currently supports only 2 of the constant types
*
* @author Josh Micich
*/
public final class ConstantValueParser {
// note - value 3 seems to be unused
private static final int TYPE_EMPTY = 0;
private static final int TYPE_NUMBER = 1;
private static final int TYPE_STRING = 2;
private static final int TYPE_BOOLEAN = 4;
private static final int TRUE_ENCODING = 1;
private static final int FALSE_ENCODING = 0;
// TODO - is this the best way to represent 'EMPTY'?
private static final Object EMPTY_REPRESENTATION = null;
private ConstantValueParser() {
// no instances of this class
}
public static Object[] parse(RecordInputStream in, int nValues) {
Object[] result = new Object[nValues];
for (int i = 0; i < result.length; i++) {
result[i] = readAConstantValue(in);
}
return result;
}
private static Object readAConstantValue(RecordInputStream in) {
byte grbit = in.readByte();
switch(grbit) {
case TYPE_EMPTY:
in.readLong(); // 8 byte 'not used' field
return EMPTY_REPRESENTATION;
case TYPE_NUMBER:
return new Double(in.readDouble());
case TYPE_STRING:
return in.readUnicodeString();
case TYPE_BOOLEAN:
return readBoolean(in);
}
return null;
}
private static Object readBoolean(RecordInputStream in) {
byte val = in.readByte();
in.readLong(); // 8 byte 'not used' field
switch(val) {
case FALSE_ENCODING:
return Boolean.FALSE;
case TRUE_ENCODING:
return Boolean.TRUE;
}
// Don't tolerate unusual boolean encoded values (unless it becomes evident that they occur)
throw new RuntimeException("unexpected boolean encoding (" + val + ")");
}
public static int getEncodedSize(Object[] values) {
// start with one byte 'type' code for each value
int result = values.length * 1;
for (int i = 0; i < values.length; i++) {
result += getEncodedSize(values[i]);
}
return 0;
}
/**
* @return encoded size without the 'type' code byte
*/
private static int getEncodedSize(Object object) {
if(object == EMPTY_REPRESENTATION) {
return 8;
}
Class cls = object.getClass();
if(cls == Boolean.class || cls == Double.class) {
return 8;
}
UnicodeString strVal = (UnicodeString)object;
UnicodeRecordStats urs = new UnicodeRecordStats();
strVal.getRecordSize(urs);
return urs.recordSize;
}
}

View File

@ -29,10 +29,13 @@ import org.apache.poi.hssf.model.Workbook;
* @author Andrew C. Oliver (acoliver at apache dot org)
*/
public abstract class AbstractFunctionPtg extends OperationPtg {
//constant used allow a ptgAttr to be mapped properly for its functionPtg
public static final String ATTR_NAME = "specialflag";
public static final short INDEX_EXTERNAL = 255;
/**
* The name of the IF function (i.e. "IF"). Extracted as a constant for clarity.
*/
public static final String FUNCTION_NAME_IF = "IF";
/** All external functions have function index 255 */
private static final short FUNCTION_INDEX_EXTERNAL = 255;
private static BinaryTree map = produceHash();
protected static Object[][] functionData = produceFunctionData();
@ -66,6 +69,13 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
public String getName() {
return lookupName(field_2_fnc_index);
}
/**
* external functions get some special processing
* @return <code>true</code> if this is an external function
*/
public boolean isExternalFunction() {
return field_2_fnc_index == FUNCTION_INDEX_EXTERNAL;
}
public String toFormulaString(Workbook book) {
return getName();
@ -73,39 +83,57 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
public String toFormulaString(String[] operands) {
StringBuffer buf = new StringBuffer();
if (field_2_fnc_index != 1) {
buf.append(getName());
buf.append('(');
}
if (operands.length >0) {
for (int i=0;i<operands.length;i++) {
buf.append(operands[i]);
buf.append(',');
}
buf.deleteCharAt(buf.length()-1);
}
if (field_2_fnc_index != 1) {
buf.append(")");
}
if(isExternalFunction()) {
buf.append(operands[0]); // first operand is actually the function name
appendArgs(buf, 1, operands);
} else {
buf.append(getName());
appendArgs(buf, 0, operands);
}
return buf.toString();
}
private static void appendArgs(StringBuffer buf, int firstArgIx, String[] operands) {
buf.append('(');
for (int i=firstArgIx;i<operands.length;i++) {
if (i>firstArgIx) {
buf.append(',');
}
buf.append(operands[i]);
}
buf.append(")");
}
public abstract void writeBytes(byte[] array, int offset);
public abstract int getSize();
/**
* Used to detect whether a function name found in a formula is one of the standard excel functions
* <p>
* The name matching is case insensitive.
* @return <code>true</code> if the name specifies a standard worksheet function,
* <code>false</code> if the name should be assumed to be an external function.
*/
public static final boolean isInternalFunctionName(String name) {
return map.containsValue(name.toUpperCase());
}
protected String lookupName(short index) {
return ((String)map.get(new Integer(index)));
}
protected short lookupIndex(String name) {
Integer index = (Integer) map.getKeyForValue(name);
/**
* Resolves internal function names into function indexes.
* <p>
* The name matching is case insensitive.
* @return the standard worksheet function index if found, otherwise <tt>FUNCTION_INDEX_EXTERNAL</tt>
*/
protected static short lookupIndex(String name) {
Integer index = (Integer) map.getKeyForValue(name.toUpperCase());
if (index != null) return index.shortValue();
return INDEX_EXTERNAL;
return FUNCTION_INDEX_EXTERNAL;
}
/**
@ -115,7 +143,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
BinaryTree dmap = new BinaryTree();
dmap.put(new Integer(0),"COUNT");
dmap.put(new Integer(1),"specialflag");
dmap.put(new Integer(1),FUNCTION_NAME_IF);
dmap.put(new Integer(2),"ISNA");
dmap.put(new Integer(3),"ISERROR");
dmap.put(new Integer(4),"SUM");
@ -354,7 +382,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
dmap.put(new Integer(252),"FREQUENCY");
dmap.put(new Integer(253),"ADDTOOLBAR");
dmap.put(new Integer(254),"DELETETOOLBAR");
dmap.put(new Integer(255),"externalflag");
dmap.put(new Integer(FUNCTION_INDEX_EXTERNAL),"externalflag");
dmap.put(new Integer(256),"RESETTOOLBAR");
dmap.put(new Integer(257),"EVALUATE");
dmap.put(new Integer(258),"GETTOOLBAR");

View File

@ -17,15 +17,13 @@
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.SheetReferences;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;
/**
@ -38,15 +36,15 @@ import org.apache.poi.util.BitFieldFactory;
* @version 1.0-pre
*/
public class Area3DPtg extends Ptg
public class Area3DPtg extends Ptg implements AreaI
{
public final static byte sid = 0x3b;
private final static int SIZE = 11; // 10 + 1 for Ptg
private short field_1_index_extern_sheet;
private short field_2_first_row;
private short field_3_last_row;
private short field_4_first_column;
private short field_5_last_column;
private int field_2_first_row;
private int field_3_last_row;
private int field_4_first_column;
private int field_5_last_column;
private BitField rowRelative = BitFieldFactory.getInstance( 0x8000 );
private BitField colRelative = BitFieldFactory.getInstance( 0x4000 );
@ -66,10 +64,24 @@ public class Area3DPtg extends Ptg
public Area3DPtg(RecordInputStream in)
{
field_1_index_extern_sheet = in.readShort();
field_2_first_row = in.readShort();
field_3_last_row = in.readShort();
field_4_first_column = in.readShort();
field_5_last_column = in.readShort();
field_2_first_row = in.readUShort();
field_3_last_row = in.readUShort();
field_4_first_column = in.readUShort();
field_5_last_column = in.readUShort();
}
public Area3DPtg(short firstRow, short lastRow, short firstColumn, short lastColumn,
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative,
short externalSheetIndex) {
setFirstRow(firstRow);
setLastRow(lastRow);
setFirstColumn(firstColumn);
setLastColumn(lastColumn);
setFirstRowRelative(firstRowRelative);
setLastRowRelative(lastRowRelative);
setFirstColRelative(firstColRelative);
setLastColRelative(lastColRelative);
setExternSheetIndex(externalSheetIndex);
}
public String toString()
@ -87,7 +99,7 @@ public class Area3DPtg extends Ptg
buffer.append( "lastColRowRel = "
+ isLastRowRelative() ).append( "\n" );
buffer.append( "firstColRel = " + isFirstColRelative() ).append( "\n" );
buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" );
buffer.append( "lastColRel = " + isLastColRelative() ).append( "\n" );
return buffer.toString();
}
@ -95,10 +107,10 @@ public class Area3DPtg extends Ptg
{
array[0 + offset] = (byte) ( sid + ptgClass );
LittleEndian.putShort( array, 1 + offset, getExternSheetIndex() );
LittleEndian.putShort( array, 3 + offset, getFirstRow() );
LittleEndian.putShort( array, 5 + offset, getLastRow() );
LittleEndian.putShort( array, 7 + offset, getFirstColumnRaw() );
LittleEndian.putShort( array, 9 + offset, getLastColumnRaw() );
LittleEndian.putShort( array, 3 + offset, (short)getFirstRow() );
LittleEndian.putShort( array, 5 + offset, (short)getLastRow() );
LittleEndian.putShort( array, 7 + offset, (short)getFirstColumnRaw() );
LittleEndian.putShort( array, 9 + offset, (short)getLastColumnRaw() );
}
public int getSize()
@ -116,32 +128,32 @@ public class Area3DPtg extends Ptg
field_1_index_extern_sheet = index;
}
public short getFirstRow()
public int getFirstRow()
{
return field_2_first_row;
}
public void setFirstRow( short row )
public void setFirstRow( int row )
{
field_2_first_row = row;
}
public short getLastRow()
public int getLastRow()
{
return field_3_last_row;
}
public void setLastRow( short row )
public void setLastRow( int row )
{
field_3_last_row = row;
}
public short getFirstColumn()
public int getFirstColumn()
{
return (short) ( field_4_first_column & 0xFF );
return field_4_first_column & 0xFF;
}
public short getFirstColumnRaw()
public int getFirstColumnRaw()
{
return field_4_first_column;
}
@ -167,12 +179,12 @@ public class Area3DPtg extends Ptg
field_4_first_column = column;
}
public short getLastColumn()
public int getLastColumn()
{
return (short) ( field_5_last_column & 0xFF );
return field_5_last_column & 0xFF;
}
public short getLastColumnRaw()
public int getLastColumnRaw()
{
return field_5_last_column;
}
@ -204,7 +216,7 @@ public class Area3DPtg extends Ptg
*/
public void setFirstRowRelative( boolean rel )
{
field_4_first_column = rowRelative.setShortBoolean( field_4_first_column, rel );
field_4_first_column = rowRelative.setBoolean( field_4_first_column, rel );
}
/**
@ -212,7 +224,7 @@ public class Area3DPtg extends Ptg
*/
public void setFirstColRelative( boolean rel )
{
field_4_first_column = colRelative.setShortBoolean( field_4_first_column, rel );
field_4_first_column = colRelative.setBoolean( field_4_first_column, rel );
}
/**
@ -221,7 +233,7 @@ public class Area3DPtg extends Ptg
*/
public void setLastRowRelative( boolean rel )
{
field_5_last_column = rowRelative.setShortBoolean( field_5_last_column, rel );
field_5_last_column = rowRelative.setBoolean( field_5_last_column, rel );
}
/**
@ -229,7 +241,7 @@ public class Area3DPtg extends Ptg
*/
public void setLastColRelative( boolean rel )
{
field_5_last_column = colRelative.setShortBoolean( field_5_last_column, rel );
field_5_last_column = colRelative.setBoolean( field_5_last_column, rel );
}
@ -243,39 +255,38 @@ public class Area3DPtg extends Ptg
public void setArea( String ref )
{
AreaReference ar = new AreaReference( ref );
CellReference[] crs = ar.getCells();
CellReference firstCell = crs[0];
CellReference lastCell = firstCell;
if(crs.length > 1) {
lastCell = crs[1];
}
CellReference frstCell = ar.getFirstCell();
CellReference lastCell = ar.getLastCell();
setFirstRow( (short) firstCell.getRow() );
setFirstColumn( (short) firstCell.getCol() );
setLastRow( (short) lastCell.getRow() );
setLastColumn( (short) lastCell.getCol() );
setFirstColRelative( !firstCell.isColAbsolute() );
setFirstRow( (short) frstCell.getRow() );
setFirstColumn( frstCell.getCol() );
setLastRow( (short) lastCell.getRow() );
setLastColumn( lastCell.getCol() );
setFirstColRelative( !frstCell.isColAbsolute() );
setLastColRelative( !lastCell.isColAbsolute() );
setFirstRowRelative( !firstCell.isRowAbsolute() );
setFirstRowRelative( !frstCell.isRowAbsolute() );
setLastRowRelative( !lastCell.isRowAbsolute() );
}
/**
* @return text representation of this area reference that can be used in text
* formulas. The sheet name will get properly delimited if required.
*/
/**
* @return text representation of this area reference that can be used in text
* formulas. The sheet name will get properly delimited if required.
*/
public String toFormulaString(Workbook book)
{
// First do the sheet name
StringBuffer retval = new StringBuffer();
String sheetName = Ref3DPtg.getSheetName(book, field_1_index_extern_sheet);
if(sheetName != null) {
SheetNameFormatter.appendFormat(retval, sheetName);
retval.append( '!' );
}
retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).toString() );
retval.append( ':' );
retval.append( ( new CellReference( getLastRow(), getLastColumn(), !isLastRowRelative(), !isLastColRelative() ) ).toString() );
// Now the normal area bit
retval.append( AreaPtg.toFormulaString(this, book) );
// All done
return retval.toString();
}
@ -292,7 +303,7 @@ public class Area3DPtg extends Ptg
ptg.field_3_last_row = field_3_last_row;
ptg.field_4_first_column = field_4_first_column;
ptg.field_5_last_column = field_5_last_column;
ptg.setClass(ptgClass);
ptg.setClass(ptgClass);
return ptg;
}

View File

@ -36,16 +36,14 @@ import org.apache.poi.hssf.model.Workbook;
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class AreaAPtg
extends AreaPtg
{
public final class AreaAPtg extends AreaPtg {
public final static short sid = 0x65;
protected AreaAPtg() {
//Required for clone methods
}
public AreaAPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
public AreaAPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
super(firstRow, lastRow, firstColumn, lastColumn, firstRowRelative, lastRowRelative, firstColRelative, lastColRelative);
}

View File

@ -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.record.formula;
/**
* Common interface for AreaPtg and Area3DPtg, and their
* child classes.
*/
public interface AreaI {
/**
* @return the first row in the area
*/
public int getFirstRow();
/**
* @return last row in the range (x2 in x1,y1-x2,y2)
*/
public int getLastRow();
/**
* @return the first column number in the area.
*/
public int getFirstColumn();
/**
* @return lastcolumn in the area
*/
public int getLastColumn();
/**
* @return isrelative first column to relative or not
*/
public boolean isFirstColRelative();
/**
* @return lastcol relative or not
*/
public boolean isLastColRelative();
/**
* @return whether or not the first row is a relative reference or not.
*/
public boolean isFirstRowRelative();
/**
* @return last row relative or not
*/
public boolean isLastRowRelative();
}

View File

@ -34,14 +34,18 @@ import org.apache.poi.hssf.record.RecordInputStream;
*/
public class AreaPtg
extends Ptg
extends Ptg implements AreaI
{
public final static short sid = 0x25;
private final static int SIZE = 9;
private short field_1_first_row;
private short field_2_last_row;
private short field_3_first_column;
private short field_4_last_column;
/** zero based, unsigned 16 bit */
private int field_1_first_row;
/** zero based, unsigned 16 bit */
private int field_2_last_row;
/** zero based, unsigned 8 bit */
private int field_3_first_column;
/** zero based, unsigned 8 bit */
private int field_4_last_column;
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
@ -53,17 +57,25 @@ public class AreaPtg
public AreaPtg(String arearef) {
AreaReference ar = new AreaReference(arearef);
setFirstRow((short)ar.getCells()[0].getRow());
setFirstColumn((short)ar.getCells()[0].getCol());
setLastRow((short)ar.getCells()[1].getRow());
setLastColumn((short)ar.getCells()[1].getCol());
setFirstColRelative(!ar.getCells()[0].isColAbsolute());
setLastColRelative(!ar.getCells()[1].isColAbsolute());
setFirstRowRelative(!ar.getCells()[0].isRowAbsolute());
setLastRowRelative(!ar.getCells()[1].isRowAbsolute());
CellReference firstCell = ar.getFirstCell();
CellReference lastCell = ar.getLastCell();
setFirstRow(firstCell.getRow());
setFirstColumn(firstCell.getCol());
setLastRow(lastCell.getRow());
setLastColumn(lastCell.getCol());
setFirstColRelative(!firstCell.isColAbsolute());
setLastColRelative(!lastCell.isColAbsolute());
setFirstRowRelative(!firstCell.isRowAbsolute());
setLastRowRelative(!lastCell.isRowAbsolute());
}
public AreaPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
public AreaPtg(int firstRow, int lastRow, int firstColumn, int lastColumn,
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
checkColumnBounds(firstColumn);
checkColumnBounds(lastColumn);
checkRowBounds(firstRow);
checkRowBounds(lastRow);
setFirstRow(firstRow);
setLastRow(lastRow);
setFirstColumn(firstColumn);
@ -74,12 +86,23 @@ public class AreaPtg
setLastColRelative(lastColRelative);
}
private static void checkColumnBounds(int colIx) {
if((colIx & 0x0FF) != colIx) {
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
}
}
private static void checkRowBounds(int rowIx) {
if((rowIx & 0x0FFFF) != rowIx) {
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
}
}
public AreaPtg(RecordInputStream in)
{
field_1_first_row = in.readShort();
field_2_last_row = in.readShort();
field_3_first_column = in.readShort();
field_4_last_column = in.readShort();
field_1_first_row = in.readUShort();
field_2_last_row = in.readUShort();
field_3_first_column = in.readUShort();
field_4_last_column = in.readUShort();
//System.out.println(toString());
}
@ -108,10 +131,10 @@ public class AreaPtg
public void writeBytes(byte [] array, int offset) {
array[offset] = (byte) (sid + ptgClass);
LittleEndian.putShort(array,offset+1,field_1_first_row);
LittleEndian.putShort(array,offset+3,field_2_last_row);
LittleEndian.putShort(array,offset+5,field_3_first_column);
LittleEndian.putShort(array,offset+7,field_4_last_column);
LittleEndian.putShort(array,offset+1,(short)field_1_first_row);
LittleEndian.putShort(array,offset+3,(short)field_2_last_row);
LittleEndian.putShort(array,offset+5,(short)field_3_first_column);
LittleEndian.putShort(array,offset+7,(short)field_4_last_column);
}
public int getSize()
@ -122,42 +145,42 @@ public class AreaPtg
/**
* @return the first row in the area
*/
public short getFirstRow()
public int getFirstRow()
{
return field_1_first_row;
}
/**
* sets the first row
* @param row number (0-based)
* @param rowIx number (0-based)
*/
public void setFirstRow(short row)
{
field_1_first_row = row;
public void setFirstRow(int rowIx) {
checkRowBounds(rowIx);
field_1_first_row = rowIx;
}
/**
* @return last row in the range (x2 in x1,y1-x2,y2)
*/
public short getLastRow()
public int getLastRow()
{
return field_2_last_row;
}
/**
* @param row last row number in the area
* @param rowIx last row number in the area
*/
public void setLastRow(short row)
{
field_2_last_row = row;
public void setLastRow(int rowIx) {
checkRowBounds(rowIx);
field_2_last_row = rowIx;
}
/**
* @return the first column number in the area.
*/
public short getFirstColumn()
public int getFirstColumn()
{
return columnMask.getShortValue(field_3_first_column);
return columnMask.getValue(field_3_first_column);
}
/**
@ -165,7 +188,7 @@ public class AreaPtg
*/
public short getFirstColumnRaw()
{
return field_3_first_column;
return (short) field_3_first_column; // TODO
}
/**
@ -181,7 +204,7 @@ public class AreaPtg
* @param rel is relative or not.
*/
public void setFirstRowRelative(boolean rel) {
field_3_first_column=rowRelative.setShortBoolean(field_3_first_column,rel);
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
}
/**
@ -196,21 +219,21 @@ public class AreaPtg
* set whether the first column is relative
*/
public void setFirstColRelative(boolean rel) {
field_3_first_column=colRelative.setShortBoolean(field_3_first_column,rel);
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
}
/**
* set the first column in the area
*/
public void setFirstColumn(short column)
{
field_3_first_column=columnMask.setShortValue(field_3_first_column, column);
public void setFirstColumn(int colIx) {
checkColumnBounds(colIx);
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
}
/**
* set the first column irespective of the bitmasks
*/
public void setFirstColumnRaw(short column)
public void setFirstColumnRaw(int column)
{
field_3_first_column = column;
}
@ -218,9 +241,9 @@ public class AreaPtg
/**
* @return lastcolumn in the area
*/
public short getLastColumn()
public int getLastColumn()
{
return columnMask.getShortValue(field_4_last_column);
return columnMask.getValue(field_4_last_column);
}
/**
@ -228,7 +251,7 @@ public class AreaPtg
*/
public short getLastColumnRaw()
{
return field_4_last_column;
return (short) field_4_last_column;
}
/**
@ -245,7 +268,7 @@ public class AreaPtg
* <code>false</code>
*/
public void setLastRowRelative(boolean rel) {
field_4_last_column=rowRelative.setShortBoolean(field_4_last_column,rel);
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
}
/**
@ -260,16 +283,16 @@ public class AreaPtg
* set whether the last column should be relative or not
*/
public void setLastColRelative(boolean rel) {
field_4_last_column=colRelative.setShortBoolean(field_4_last_column,rel);
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
}
/**
* set the last column in the area
*/
public void setLastColumn(short column)
{
field_4_last_column=columnMask.setShortValue(field_4_last_column, column);
public void setLastColumn(int colIx) {
checkColumnBounds(colIx);
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
}
/**
@ -279,11 +302,20 @@ public class AreaPtg
{
field_4_last_column = column;
}
public String toFormulaString(Workbook book)
{
return (new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative())).toString() + ":" +
(new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative())).toString();
return toFormulaString(this, book);
}
protected static String toFormulaString(AreaI area, Workbook book) {
CellReference topLeft = new CellReference(area.getFirstRow(),area.getFirstColumn(),!area.isFirstRowRelative(),!area.isFirstColRelative());
CellReference botRight = new CellReference(area.getLastRow(),area.getLastColumn(),!area.isLastRowRelative(),!area.isLastColRelative());
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
return (new AreaReference(topLeft, botRight)).formatAsString();
} else {
return topLeft.formatAsString() + ":" + botRight.formatAsString();
}
}
public byte getDefaultOperandClass() {

View File

@ -36,7 +36,7 @@ import org.apache.poi.hssf.model.Workbook;
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class AreaVPtg
public final class AreaVPtg
extends AreaPtg
{
public final static short sid = 0x45;
@ -45,7 +45,7 @@ public class AreaVPtg
//Required for clone methods
}
public AreaVPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
public AreaVPtg(int firstRow, int lastRow, int firstColumn, int lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
super(firstRow, lastRow, firstColumn, lastColumn, firstRowRelative, lastRowRelative, firstColRelative, lastColRelative);
}

View File

@ -72,8 +72,10 @@ public class ArrayPtg extends Ptg
field_7_reserved = in.readByte();
}
/** Read in the actual token (array) values. This occurs AFTER the last
* Ptg in the expression.
/**
* Read in the actual token (array) values. This occurs
* AFTER the last Ptg in the expression.
* See page 304-305 of Excel97-2007BinaryFileFormat(xls)Specification.pdf
*/
public void readTokenValues(RecordInputStream in) {
token_1_columns = (short)(0x00ff & in.readByte());
@ -88,18 +90,17 @@ public class ArrayPtg extends Ptg
token_3_arrayValues = new Object[token_1_columns][token_2_rows];
for (int x=0;x<token_1_columns;x++) {
for (int y=0;y<token_2_rows;y++) {
byte grbit = in.readByte();
if (grbit == 0x01) {
token_3_arrayValues[x][y] = new Double(in.readDouble());
} else if (grbit == 0x02) {
//Ignore the doco, it is actually a unicode string with all the
//trimmings ie 16 bit size, option byte etc
token_3_arrayValues[x][y] = in.readUnicodeString();
} else throw new RecordFormatException("Unknown grbit '"+grbit+"'");
}
for (int y=0;y<token_2_rows;y++) {
byte grbit = in.readByte();
if (grbit == 0x01) {
token_3_arrayValues[x][y] = new Double(in.readDouble());
} else if (grbit == 0x02) {
//Ignore the doco, it is actually a unicode string with all the
//trimmings ie 16 bit size, option byte etc
token_3_arrayValues[x][y] = in.readUnicodeString();
} else throw new RecordFormatException("Unknown grbit '"+grbit+"' at " + x + "," + y + " with " + in.remaining() + " bytes left");
}
}
}
public String toString()

View File

@ -26,66 +26,67 @@ import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
/**
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
public class ErrPtg extends Ptg
{
public final class ErrPtg extends Ptg {
// convenient access to namespace
private static final HSSFErrorConstants EC = null;
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
public static final ErrPtg NULL_INTERSECTION = new ErrPtg(EC.ERROR_NULL);
/** <b>#DIV/0!</b> - Division by zero */
public static final ErrPtg DIV_ZERO = new ErrPtg(EC.ERROR_DIV_0);
/** <b>#VALUE!</b> - Wrong type of operand */
public static final ErrPtg VALUE_INVALID = new ErrPtg(EC.ERROR_VALUE);
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static final ErrPtg REF_INVALID = new ErrPtg(EC.ERROR_REF);
/** <b>#NAME?</b> - Wrong function or range name */
public static final ErrPtg NAME_INVALID = new ErrPtg(EC.ERROR_NAME);
/** <b>#NUM!</b> - Value range overflow */
public static final ErrPtg NUM_ERROR = new ErrPtg(EC.ERROR_NUM);
/** <b>#N/A</b> - Argument or function not available */
public static final ErrPtg N_A = new ErrPtg(EC.ERROR_NA);
public static final short sid = 0x1c;
private static final int SIZE = 2;
private byte field_1_error_code;
private int field_1_error_code;
/** Creates new ErrPtg */
public ErrPtg()
{
public ErrPtg(int errorCode) {
if(!HSSFErrorConstants.isValidCode(errorCode)) {
throw new IllegalArgumentException("Invalid error code (" + errorCode + ")");
}
field_1_error_code = errorCode;
}
public ErrPtg(RecordInputStream in)
{
field_1_error_code = in.readByte();
public ErrPtg(RecordInputStream in) {
this(in.readByte());
}
public void writeBytes(byte [] array, int offset)
{
array[offset] = (byte) (sid + ptgClass);
array[offset + 1] = field_1_error_code;
array[offset + 1] = (byte)field_1_error_code;
}
public String toFormulaString(Workbook book)
{
switch(field_1_error_code)
{
case HSSFErrorConstants.ERROR_NULL:
return "#NULL!";
case HSSFErrorConstants.ERROR_DIV_0:
return "#DIV/0!";
case HSSFErrorConstants.ERROR_VALUE:
return "#VALUE!";
case HSSFErrorConstants.ERROR_REF:
return "#REF!";
case HSSFErrorConstants.ERROR_NAME:
return "#NAME?";
case HSSFErrorConstants.ERROR_NUM:
return "#NUM!";
case HSSFErrorConstants.ERROR_NA:
return "#N/A";
}
// Shouldn't happen anyway. Excel docs say that this is returned for all other codes.
return "#N/A";
public String toFormulaString(Workbook book) {
return HSSFErrorConstants.getText(field_1_error_code);
}
public int getSize()
{
public int getSize() {
return SIZE;
}
public byte getDefaultOperandClass()
{
public byte getDefaultOperandClass() {
return Ptg.CLASS_VALUE;
}
public Object clone() {
ErrPtg ptg = new ErrPtg();
ptg.field_1_error_code = field_1_error_code;
return ptg;
return new ErrPtg(field_1_error_code);
}
public int getErrorCode() {
return field_1_error_code;
}
}

View File

@ -62,6 +62,10 @@ public class FuncPtg extends AbstractFunctionPtg{
numParams=0;
}
}
public FuncPtg(int functionIndex, int numberOfParameters) {
field_2_fnc_index = (short) functionIndex;
numParams = numberOfParameters;
}
public void writeBytes(byte[] array, int offset) {

View File

@ -1,4 +1,3 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -16,12 +15,6 @@
limitations under the License.
==================================================================== */
/*
* IntPtg.java
*
* Created on October 29, 2001, 7:37 PM
*/
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
@ -29,64 +22,45 @@ import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
/**
* Integer (unsigned short intger)
* Integer (unsigned short integer)
* Stores an unsigned short value (java int) in a formula
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Jason Height (jheight at chariot dot net dot au)
*/
public final class IntPtg extends Ptg {
// 16 bit unsigned integer
private static final int MIN_VALUE = 0x0000;
private static final int MAX_VALUE = 0xFFFF;
/**
* Excel represents integers 0..65535 with the tInt token.
* @return <code>true</code> if the specified value is within the range of values
* <tt>IntPtg</tt> can represent.
*/
public static boolean isInRange(int i) {
return i>=MIN_VALUE && i <=MAX_VALUE;
}
public class IntPtg
extends Ptg
{
public final static int SIZE = 3;
public final static byte sid = 0x1e;
private int field_1_value;
private IntPtg() {
//Required for clone methods
public IntPtg(RecordInputStream in) {
this(in.readUShort());
}
public IntPtg(RecordInputStream in)
{
setValue(in.readUShort());
}
// IntPtg should be able to create itself, shouldnt have to call setValue
public IntPtg(String formulaToken) {
setValue(Integer.parseInt(formulaToken));
}
/**
* Sets the wrapped value.
* Normally you should call with a positive int.
*/
public void setValue(int value)
{
if(value < 0 || value > (Short.MAX_VALUE + 1)*2 )
throw new IllegalArgumentException("Unsigned short is out of range: " + value);
public IntPtg(int value) {
if(!isInRange(value)) {
throw new IllegalArgumentException("value is out of range: " + value);
}
field_1_value = value;
}
/**
* Returns the value as a short, which may have
* been wrapped into negative numbers
*/
public int getValue()
{
public int getValue() {
return field_1_value;
}
/**
* Returns the value as an unsigned positive int.
*/
public int getValueAsInt()
{
if(field_1_value < 0) {
return (Short.MAX_VALUE + 1)*2 + field_1_value;
}
return field_1_value;
}
public void writeBytes(byte [] array, int offset)
{
@ -94,20 +68,25 @@ public class IntPtg
LittleEndian.putUShort(array, offset + 1, getValue());
}
public int getSize()
{
public int getSize() {
return SIZE;
}
public String toFormulaString(Workbook book)
{
return "" + getValue();
public String toFormulaString(Workbook book) {
return String.valueOf(getValue());
}
public byte getDefaultOperandClass() {
return Ptg.CLASS_VALUE;
}
public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
public Object clone() {
IntPtg ptg = new IntPtg();
ptg.field_1_value = field_1_value;
return ptg;
}
public Object clone() {
return new IntPtg(field_1_value);
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(field_1_value);
sb.append("]");
return sb.toString();
}
}

View File

@ -33,6 +33,7 @@ public class NamePtg
{
public final static short sid = 0x23;
private final static int SIZE = 5;
/** one-based index to defined name record */
private short field_1_label_index;
private short field_2_zero; // reserved must be 0
boolean xtra=false;
@ -42,24 +43,32 @@ public class NamePtg
//Required for clone methods
}
/** Creates new NamePtg */
public NamePtg(String name, Workbook book)
{
final short n = (short) (book.getNumNames() + 1);
/**
* Creates new NamePtg and sets its name index to that of the corresponding defined name record
* in the workbook. The search for the name record is case insensitive. If it is not found,
* it gets created.
*/
public NamePtg(String name, Workbook book) {
field_1_label_index = (short)(1+getOrCreateNameRecord(book, name)); // convert to 1-based
}
/**
* @return zero based index of the found or newly created defined name record.
*/
private static final int getOrCreateNameRecord(Workbook book, String name) {
// perhaps this logic belongs in Workbook
int countNames = book.getNumNames();
NameRecord rec;
for (short i = 1; i < n; i++) {
rec = book.getNameRecord(i - 1);
if (name.equals(rec.getNameText())) {
field_1_label_index = i;
return;
for (int i = 0; i < countNames; i++) {
rec = book.getNameRecord(i);
if (name.equalsIgnoreCase(rec.getNameText())) {
return i;
}
}
rec = new NameRecord();
rec.setNameText(name);
rec.setNameTextLength((byte) name.length());
book.addName(rec);
field_1_label_index = n;
return countNames;
}
/** Creates new NamePtg */
@ -71,6 +80,13 @@ public class NamePtg
field_2_zero = in.readShort();
//if (data[offset+6]==0) xtra=true;
}
/**
* @return zero based index to a defined name record in the LinkTable
*/
public int getIndex() {
return field_1_label_index-1; // convert to zero based
}
public void writeBytes(byte [] array, int offset)
{

View File

@ -25,13 +25,11 @@ import org.apache.poi.hssf.record.RecordInputStream;
*
* @author aviks
*/
public class NameXPtg extends Ptg
{
public final class NameXPtg extends Ptg {
public final static short sid = 0x39;
private final static int SIZE = 7;
private short field_1_ixals; // index to externsheet record
private short field_2_ilbl; //index to name or externname table(1 based)
private short field_1_ixals; // index to REF entry in externsheet record
private short field_2_ilbl; //index to defined name or externname table(1 based)
private short field_3_reserved; // reserved must be 0
@ -41,13 +39,6 @@ public class NameXPtg extends Ptg
/** Creates new NamePtg */
public NameXPtg(String name)
{
//TODO
}
/** Creates new NamePtg */
public NameXPtg(RecordInputStream in)
{
field_1_ixals = in.readShort();
@ -72,7 +63,8 @@ public class NameXPtg extends Ptg
public String toFormulaString(Workbook book)
{
return "NO IDEA - NAME";
// -1 to convert definedNameIndex from 1-based to zero-based
return book.resolveNameXText(field_1_ixals, field_2_ilbl-1);
}
public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}

View File

@ -18,16 +18,14 @@
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.hssf.util.RangeAddress;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.SheetReferences;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.RangeAddress;
import org.apache.poi.hssf.util.SheetReferences;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian;
/**
* Title: Reference 3D Ptg <P>
@ -42,8 +40,14 @@ public class Ref3DPtg extends Ptg {
public final static byte sid = 0x3a;
private final static int SIZE = 7; // 6 + 1 for Ptg
private short field_1_index_extern_sheet;
private short field_2_row;
private short field_3_column;
/** The row index - zero based unsigned 16 bit value */
private int field_2_row;
/** Field 2
* - lower 8 bits is the zero based unsigned byte column index
* - bit 16 - isRowRelative
* - bit 15 - isColumnRelative
*/
private int field_3_column;
private BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private BitField colRelative = BitFieldFactory.getInstance(0x4000);
@ -58,8 +62,8 @@ public class Ref3DPtg extends Ptg {
public Ref3DPtg(String cellref, short externIdx ) {
CellReference c= new CellReference(cellref);
setRow((short) c.getRow());
setColumn((short) c.getCol());
setRow(c.getRow());
setColumn(c.getCol());
setColRelative(!c.isColAbsolute());
setRowRelative(!c.isRowAbsolute());
setExternSheetIndex(externIdx);
@ -81,8 +85,8 @@ public class Ref3DPtg extends Ptg {
public void writeBytes(byte [] array, int offset) {
array[ 0 + offset ] = (byte) (sid + ptgClass);
LittleEndian.putShort(array, 1 + offset , getExternSheetIndex());
LittleEndian.putShort(array, 3 + offset , getRow());
LittleEndian.putShort(array, 5 + offset , getColumnRaw());
LittleEndian.putShort(array, 3 + offset , (short)getRow());
LittleEndian.putShort(array, 5 + offset , (short)getColumnRaw());
}
public int getSize() {
@ -97,19 +101,19 @@ public class Ref3DPtg extends Ptg {
field_1_index_extern_sheet = index;
}
public short getRow() {
public int getRow() {
return field_2_row;
}
public void setRow(short row) {
public void setRow(int row) {
field_2_row = row;
}
public short getColumn() {
return ( short ) (field_3_column & 0xFF);
public int getColumn() {
return field_3_column & 0xFF;
}
public short getColumnRaw() {
public int getColumnRaw() {
return field_3_column;
}
@ -119,7 +123,7 @@ public class Ref3DPtg extends Ptg {
}
public void setRowRelative(boolean rel) {
field_3_column=rowRelative.setShortBoolean(field_3_column,rel);
field_3_column=rowRelative.setBoolean(field_3_column,rel);
}
public boolean isColRelative()
@ -128,7 +132,7 @@ public class Ref3DPtg extends Ptg {
}
public void setColRelative(boolean rel) {
field_3_column=colRelative.setShortBoolean(field_3_column,rel);
field_3_column=colRelative.setBoolean(field_3_column,rel);
}
public void setColumn(short column) {
field_3_column &= 0xFF00;
@ -183,7 +187,7 @@ public class Ref3DPtg extends Ptg {
SheetNameFormatter.appendFormat(retval, sheetName);
retval.append( '!' );
}
retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString());
retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString());
return retval.toString();
}
@ -197,5 +201,4 @@ public class Ref3DPtg extends Ptg {
ptg.setClass(ptgClass);
return ptg;
}
}

View File

@ -1,4 +1,3 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -16,34 +15,23 @@
limitations under the License.
==================================================================== */
/*
* ValueReferencePtg.java
*
* Created on November 21, 2001, 5:27 PM
*/
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.BitField;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.model.Workbook;
/**
* RefNAPtg
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class RefAPtg extends ReferencePtg
{
public final class RefAPtg extends ReferencePtg {
public final static byte sid = 0x64;
protected RefAPtg() {
super();
}
public RefAPtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) {
public RefAPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
super(row, column, isRowRelative, isColumnRelative);
}

View File

@ -1,4 +1,3 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -18,27 +17,20 @@
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.BitField;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.model.Workbook;
/**
* RefVPtg
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class RefVPtg extends ReferencePtg
{
public final class RefVPtg extends ReferencePtg {
public final static byte sid = 0x44;
protected RefVPtg() {
super();
}
public RefVPtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) {
public RefVPtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
super(row, column, isRowRelative, isColumnRelative);
}

View File

@ -31,26 +31,22 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Jason Height (jheight at chariot dot net dot au)
*/
public class ReferencePtg extends Ptg
{
public class ReferencePtg extends Ptg {
private final static int SIZE = 5;
public final static byte sid = 0x24;
private final static int MAX_ROW_NUMBER = 65536;
//public final static byte sid = 0x44;
/**
* The row number, between 0 and 65535, but stored as a signed
* short between -32767 and 32768.
* Take care about which version you fetch back!
/** The row index - zero based unsigned 16 bit value */
private int field_1_row;
/** Field 2
* - lower 8 bits is the zero based unsigned byte column index
* - bit 16 - isRowRelative
* - bit 15 - isColumnRelative
*/
private short field_1_row;
/**
* The column number, between 0 and ??
*/
private short field_2_col;
private BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private BitField colRelative = BitFieldFactory.getInstance(0x4000);
private BitField column = BitFieldFactory.getInstance(0x3FFF);
private int field_2_col;
private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000);
private static final BitField colRelative = BitFieldFactory.getInstance(0x4000);
private static final BitField column = BitFieldFactory.getInstance(0x00FF);
protected ReferencePtg() {
//Required for clone methods
@ -62,13 +58,13 @@ public class ReferencePtg extends Ptg
*/
public ReferencePtg(String cellref) {
CellReference c= new CellReference(cellref);
setRow((short) c.getRow());
setColumn((short) c.getCol());
setRow(c.getRow());
setColumn(c.getCol());
setColRelative(!c.isColAbsolute());
setRowRelative(!c.isRowAbsolute());
}
public ReferencePtg(short row, short column, boolean isRowRelative, boolean isColumnRelative) {
public ReferencePtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
setRow(row);
setColumn(column);
setRowRelative(isRowRelative);
@ -79,8 +75,8 @@ public class ReferencePtg extends Ptg
public ReferencePtg(RecordInputStream in)
{
field_1_row = in.readShort();
field_2_col = in.readShort();
field_1_row = in.readUShort();
field_2_col = in.readUShort();
}
public String getRefPtgName() {
@ -104,33 +100,23 @@ public class ReferencePtg extends Ptg
{
array[offset] = (byte) (sid + ptgClass);
LittleEndian.putShort(array,offset+1,field_1_row);
LittleEndian.putShort(array,offset+3,field_2_col);
LittleEndian.putShort(array, offset+1, (short)field_1_row);
LittleEndian.putShort(array, offset+3, (short)field_2_col);
}
public void setRow(short row)
{
field_1_row = row;
}
public void setRow(int row)
{
if(row < 0 || row >= MAX_ROW_NUMBER) {
throw new IllegalArgumentException("The row number, when specified as an integer, must be between 0 and " + MAX_ROW_NUMBER);
}
// Save, wrapping as needed
if(row > Short.MAX_VALUE) {
field_1_row = (short)(row - MAX_ROW_NUMBER);
} else {
field_1_row = (short)row;
}
field_1_row = row;
}
/**
* Returns the row number as a short, which will be
* wrapped (negative) for values between 32769 and 65535
*/
public short getRow()
public int getRow()
{
return field_1_row;
}
@ -151,7 +137,7 @@ public class ReferencePtg extends Ptg
}
public void setRowRelative(boolean rel) {
field_2_col=rowRelative.setShortBoolean(field_2_col,rel);
field_2_col=rowRelative.setBoolean(field_2_col,rel);
}
public boolean isColRelative()
@ -160,27 +146,29 @@ public class ReferencePtg extends Ptg
}
public void setColRelative(boolean rel) {
field_2_col=colRelative.setShortBoolean(field_2_col,rel);
field_2_col=colRelative.setBoolean(field_2_col,rel);
}
public void setColumnRaw(short col)
public void setColumnRaw(int col)
{
field_2_col = col;
}
public short getColumnRaw()
public int getColumnRaw()
{
return field_2_col;
}
public void setColumn(short col)
public void setColumn(int col)
{
field_2_col = column.setShortValue(field_2_col, col);
if(col < 0 || col > 0x100) {
throw new IllegalArgumentException("Specified colIx (" + col + ") is out of range");
}
field_2_col = column.setValue(field_2_col, col);
}
public short getColumn()
{
return column.getShortValue(field_2_col);
public int getColumn() {
return column.getValue(field_2_col);
}
public int getSize()
@ -191,7 +179,7 @@ public class ReferencePtg extends Ptg
public String toFormulaString(Workbook book)
{
//TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe!
return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).toString();
return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString();
}
public byte getDefaultOperandClass() {

View File

@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*
* @author Josh Micich
*/
final class SheetNameFormatter {
public final class SheetNameFormatter {
private static final String BIFF8_LAST_COLUMN = "IV";
private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length();

View File

@ -456,7 +456,7 @@ public class HSSFCell implements Cell
boolRec.setColumn(col);
if (setValue)
{
boolRec.setValue(getBooleanCellValue());
boolRec.setValue(convertCellValueToBoolean());
}
boolRec.setXFIndex(styleIndex);
boolRec.setRow(row);
@ -644,7 +644,7 @@ public class HSSFCell implements Cell
//only set to default if there is no extended format index already set
if (rec.getXFIndex() == (short)0) rec.setXFIndex(( short ) 0x0f);
FormulaParser fp = new FormulaParser(formula+";",book);
FormulaParser fp = new FormulaParser(formula, book);
fp.parse();
Ptg[] ptg = fp.getRPNPtg();
int size = 0;
@ -830,6 +830,34 @@ public class HSSFCell implements Cell
}
(( BoolErrRecord ) record).setValue(value);
}
/**
* Chooses a new boolean value for the cell when its type is changing.<p/>
*
* Usually the caller is calling setCellType() with the intention of calling
* setCellValue(boolean) straight afterwards. This method only exists to give
* the cell a somewhat reasonable value until the setCellValue() call (if at all).
* TODO - perhaps a method like setCellTypeAndValue(int, Object) should be introduced to avoid this
*/
private boolean convertCellValueToBoolean() {
switch (cellType) {
case CELL_TYPE_BOOLEAN:
return (( BoolErrRecord ) record).getBooleanValue();
case CELL_TYPE_STRING:
return Boolean.valueOf(((StringRecord)record).getString()).booleanValue();
case CELL_TYPE_NUMERIC:
return ((NumberRecord)record).getValue() != 0;
// All other cases convert to false
// These choices are not well justified.
case CELL_TYPE_FORMULA:
// should really evaluate, but HSSFCell can't call HSSFFormulaEvaluator
case CELL_TYPE_ERROR:
case CELL_TYPE_BLANK:
return false;
}
throw new RuntimeException("Unexpected cell type (" + cellType + ")");
}
/**
* get the value of the cell as a boolean. For strings, numbers, and errors, we throw an exception.

View File

@ -15,26 +15,68 @@
limitations under the License.
==================================================================== */
/*
* HSSFErrorConstants.java
*
* Created on January 19, 2002, 9:30 AM
*/
package org.apache.poi.hssf.usermodel;
/**
* contains constants representing Excel error codes.
* Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6)
*
* @author Michael Harhen
*/
public interface HSSFErrorConstants
{
public static final byte ERROR_NULL = 0x00; // #NULL!
public static final byte ERROR_DIV_0 = 0x07; // #DIV/0!
public static final byte ERROR_VALUE = 0x0f; // #VALUE!
public static final byte ERROR_REF = 0x17; // #REF!
public static final byte ERROR_NAME = 0x1d; // #NAME?
public static final byte ERROR_NUM = 0x24; // #NUM!
public static final byte ERROR_NA = 0x2a; // #N/A
public final class HSSFErrorConstants {
private HSSFErrorConstants() {
// no instances of this class
}
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
public static final int ERROR_NULL = 0x00;
/** <b>#DIV/0!</b> - Division by zero */
public static final int ERROR_DIV_0 = 0x07;
/** <b>#VALUE!</b> - Wrong type of operand */
public static final int ERROR_VALUE = 0x0F;
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static final int ERROR_REF = 0x17;
/** <b>#NAME?</b> - Wrong function or range name */
public static final int ERROR_NAME = 0x1D;
/** <b>#NUM!</b> - Value range overflow */
public static final int ERROR_NUM = 0x24;
/** <b>#N/A</b> - Argument or function not available */
public static final int ERROR_NA = 0x2A;
/**
* @return Standard Excel error literal for the specified error code.
* @throws IllegalArgumentException if the specified error code is not one of the 7
* standard error codes
*/
public static final String getText(int errorCode) {
switch(errorCode) {
case ERROR_NULL: return "#NULL!";
case ERROR_DIV_0: return "#DIV/0!";
case ERROR_VALUE: return "#VALUE!";
case ERROR_REF: return "#REF!";
case ERROR_NAME: return "#NAME?";
case ERROR_NUM: return "#NUM!";
case ERROR_NA: return "#N/A";
}
throw new IllegalArgumentException("Bad error code (" + errorCode + ")");
}
/**
* @return <code>true</code> if the specified error code is a standard Excel error code.
*/
public static final boolean isValidCode(int errorCode) {
// This method exists because it would be bad to force clients to catch
// IllegalArgumentException if there were potential for passing an invalid error code.
switch(errorCode) {
case ERROR_NULL:
case ERROR_DIV_0:
case ERROR_VALUE:
case ERROR_REF:
case ERROR_NAME:
case ERROR_NUM:
case ERROR_NA:
return true;
}
return false;
}
}

View File

@ -100,9 +100,11 @@ public class HSSFPalette implements Palette
for (short i = (short) PaletteRecord.FIRST_COLOR_INDEX; b != null;
b = palette.getColor(++i))
{
int colorDistance = red - b[0] + green - b[1] + blue - b[2];
int colorDistance = Math.abs(red - b[0]) +
Math.abs(green - b[1]) + Math.abs(blue - b[2]);
if (colorDistance < minColorDistance)
{
minColorDistance = colorDistance;
result = getColor(i);
}
}

View File

@ -159,28 +159,25 @@ public class HSSFRow
* @param cell to remove
*/
public void removeCell(Cell cell) {
removeCell(cell, true);
removeCell((HSSFCell) cell, true);
}
private void removeCell(Cell cell, boolean alsoRemoveRecords) {
HSSFCell hcell = (HSSFCell) cell;
private void removeCell(HSSFCell cell, boolean alsoRemoveRecords) {
if(alsoRemoveRecords) {
CellValueRecordInterface cval = hcell.getCellValueRecord();
CellValueRecordInterface cval = cell.getCellValueRecord();
sheet.removeValueRecord(getRowNum(), cval);
}
short column=hcell.getCellNum();
if(hcell!=null && column<cells.length)
short column=cell.getCellNum();
if(cell!=null && column<cells.length)
{
cells[column]=null;
}
if (hcell.getCellNum() == row.getLastCol())
if (cell.getCellNum() == row.getLastCol())
{
row.setLastCol(findLastCell(row.getLastCol()));
}
if (hcell.getCellNum() == row.getFirstCol())
if (cell.getCellNum() == row.getFirstCol())
{
row.setFirstCol(findFirstCell(row.getFirstCol()));
}
@ -471,6 +468,7 @@ public class HSSFRow
* @return cell iterator of the physically defined cells. Note element 4 may
* actually be row cell depending on how many are defined!
*/
public Iterator cellIterator()
{
return new CellIterator();

View File

@ -414,7 +414,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
//formula fields ( size and data )
String str_formula = obj_validation.getFirstFormula();
FormulaParser fp = new FormulaParser(str_formula+";",book);
FormulaParser fp = new FormulaParser(str_formula, book);
fp.parse();
Stack ptg_arr = new Stack();
Ptg[] ptg = fp.getRPNPtg();
@ -438,7 +438,7 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
if ( obj_validation.getSecondFormula() != null )
{
str_formula = obj_validation.getSecondFormula();
fp = new FormulaParser(str_formula+";",book);
fp = new FormulaParser(str_formula, book);
fp.parse();
ptg_arr = new Stack();
ptg = fp.getRPNPtg();
@ -641,11 +641,18 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet
record.setVCenter(value);
}
/**
* TODO: Boolean not needed, remove after next release
* @deprecated use getVerticallyCenter() instead
*/
public boolean getVerticallyCenter(boolean value) {
return getVerticallyCenter();
}
/**
* Determine whether printed output for this sheet will be vertically centered.
*/
public boolean getVerticallyCenter(boolean value)
public boolean getVerticallyCenter()
{
VCenterRecord record =
(VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid);

View File

@ -180,10 +180,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
throws IOException
{
this.preserveNodes = preserveNodes;
// Read in the HPSF properties
this.filesystem = fs;
readProperties();
// If we're not preserving nodes, don't track the
// POIFS any more
@ -1134,12 +1131,12 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
public void setPrintArea(int sheetIndex, int startColumn, int endColumn,
int startRow, int endRow) {
//using absolute references because they dont get copied and pasted anyway
//using absolute references because they don't get copied and pasted anyway
CellReference cell = new CellReference(startRow, startColumn, true, true);
String reference = cell.toString();
String reference = cell.formatAsString();
cell = new CellReference(endRow, endColumn, true, true);
reference = reference+":"+cell.toString();
reference = reference+":"+cell.formatAsString();
setPrintArea(sheetIndex, reference);
}
@ -1397,6 +1394,13 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm
}
}
/**
* Is the workbook protected with a password (not encrypted)?
*/
public boolean isWriteProtected() {
return this.workbook.isWriteProtected();
}
/**
* protect a workbook with a password (not encypted, just sets writeprotect
* flags and the password.

View File

@ -21,27 +21,62 @@ package org.apache.poi.hssf.util;
import java.util.ArrayList;
import java.util.StringTokenizer;
public class AreaReference {
public final class AreaReference {
private CellReference [] cells;
private int dim;
/** The character (!) that separates sheet names from cell references */
private static final char SHEET_NAME_DELIMITER = '!';
/** The character (:) that separates the two cell references in a multi-cell area reference */
private static final char CELL_DELIMITER = ':';
/** The character (') used to quote sheet names when they contain special characters */
private static final char SPECIAL_NAME_DELIMITER = '\'';
private final CellReference _firstCell;
private final CellReference _lastCell;
private final boolean _isSingleCell;
/**
* Create an area ref from a string representation.
* The area reference must be contiguous
* Create an area ref from a string representation. Sheet names containing special characters should be
* delimited and escaped as per normal syntax rules for formulas.<br/>
* The area reference must be contiguous (i.e. represent a single rectangle, not a union of rectangles)
*/
public AreaReference(String reference) {
if(! isContiguous(reference)) {
throw new IllegalArgumentException("References passed to the AreaReference must be contiguous, use generateContiguous(ref) if you have non-contiguous references");
throw new IllegalArgumentException(
"References passed to the AreaReference must be contiguous, " +
"use generateContiguous(ref) if you have non-contiguous references");
}
String[] refs = seperateAreaRefs(reference);
dim = refs.length;
cells = new CellReference[dim];
for (int i=0;i<dim;i++) {
cells[i]=new CellReference(refs[i]);
String[] parts = separateAreaRefs(reference);
// Special handling for whole-column references
if(parts.length == 2 && parts[0].length() == 1 &&
parts[1].length() == 1 &&
parts[0].charAt(0) >= 'A' && parts[0].charAt(0) <= 'Z' &&
parts[1].charAt(0) >= 'A' && parts[1].charAt(0) <= 'Z') {
// Represented internally as x$1 to x$65536
// which is the maximum range of rows
parts[0] = parts[0] + "$1";
parts[1] = parts[1] + "$65536";
}
_firstCell = new CellReference(parts[0]);
if(parts.length == 2) {
_lastCell = new CellReference(parts[1]);
_isSingleCell = false;
} else {
_lastCell = _firstCell;
_isSingleCell = true;
}
}
/**
* Creates an area ref from a pair of Cell References.
*/
public AreaReference(CellReference topLeft, CellReference botRight) {
_firstCell = topLeft;
_lastCell = botRight;
_isSingleCell = false;
}
/**
@ -57,6 +92,24 @@ private int dim;
}
return false;
}
/**
* Is the reference for a whole-column reference,
* such as C:C or D:G ?
*/
public static boolean isWholeColumnReference(CellReference topLeft, CellReference botRight) {
// These are represented as something like
// C$1:C$65535 or D$1:F$0
// i.e. absolute from 1st row to 0th one
if(topLeft.getRow() == 0 && topLeft.isRowAbsolute() &&
botRight.getRow() == 65535 && botRight.isRowAbsolute()) {
return true;
}
return false;
}
public boolean isWholeColumnReference() {
return isWholeColumnReference(_firstCell, _lastCell);
}
/**
* Takes a non-contiguous area reference, and
@ -73,75 +126,171 @@ private int dim;
return (AreaReference[])refs.toArray(new AreaReference[refs.size()]);
}
//not sure if we need to be flexible here!
/** return the dimensions of this area
**/
public int getDim() {
return dim;
}
/**
* Return the cell references that define this area
* (i.e. the two corners)
/**
* @return <code>false</code> if this area reference involves more than one cell
*/
public CellReference[] getCells() {
return cells;
public boolean isSingleCell() {
return _isSingleCell;
}
/**
* @return the first cell reference which defines this area. Usually this cell is in the upper
* left corner of the area (but this is not a requirement).
*/
public CellReference getFirstCell() {
return _firstCell;
}
/**
* Note - if this area reference refers to a single cell, the return value of this method will
* be identical to that of <tt>getFirstCell()</tt>
* @return the second cell reference which defines this area. For multi-cell areas, this is
* cell diagonally opposite the 'first cell'. Usually this cell is in the lower right corner
* of the area (but this is not a requirement).
*/
public CellReference getLastCell() {
return _lastCell;
}
/**
* Returns a reference to every cell covered by this area
*/
public CellReference[] getAllReferencedCells() {
// Special case for single cell reference
if(cells.length == 1) {
return cells;
if(_isSingleCell) {
return new CellReference[] { _firstCell, };
}
// Interpolate between the two
int minRow = Math.min(cells[0].getRow(), cells[1].getRow());
int maxRow = Math.max(cells[0].getRow(), cells[1].getRow());
int minCol = Math.min(cells[0].getCol(), cells[1].getCol());
int maxCol = Math.max(cells[0].getCol(), cells[1].getCol());
int minRow = Math.min(_firstCell.getRow(), _lastCell.getRow());
int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow());
int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol());
int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol());
String sheetName = _firstCell.getSheetName();
ArrayList refs = new ArrayList();
for(int row=minRow; row<=maxRow; row++) {
for(int col=minCol; col<=maxCol; col++) {
CellReference ref = new CellReference(row, col, cells[0].isRowAbsolute(), cells[0].isColAbsolute());
ref.setSheetName(cells[0].getSheetName());
CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute());
refs.add(ref);
}
}
return (CellReference[])refs.toArray(new CellReference[refs.size()]);
}
public String toString() {
StringBuffer retval = new StringBuffer();
for (int i=0;i<dim;i++){
retval.append(':');
retval.append(cells[i].toString());
/**
* Example return values:
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
* <tr><td>A1:A1</td><td>Single cell area reference without sheet</td></tr>
* <tr><td>A1:$C$1</td><td>Multi-cell area reference without sheet</td></tr>
* <tr><td>Sheet1!A$1:B4</td><td>Standard sheet name</td></tr>
* <tr><td>'O''Brien''s Sales'!B5:C6'&nbsp;</td><td>Sheet name with special characters</td></tr>
* </table>
* @return the text representation of this area reference as it would appear in a formula.
*/
public String formatAsString() {
// Special handling for whole-column references
if(isWholeColumnReference()) {
return
CellReference.convertNumToColString(_firstCell.getCol())
+ ":" +
CellReference.convertNumToColString(_lastCell.getCol());
}
StringBuffer sb = new StringBuffer(32);
sb.append(_firstCell.formatAsString());
if(!_isSingleCell) {
sb.append(CELL_DELIMITER);
if(_lastCell.getSheetName() == null) {
sb.append(_lastCell.formatAsString());
} else {
// don't want to include the sheet name twice
_lastCell.appendCellReference(sb);
}
}
retval.deleteCharAt(0);
return retval.toString();
return sb.toString();
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
/**
* seperates Area refs in two parts and returns them as seperate elements in a
* String array
* Separates Area refs in two parts and returns them as separate elements in a String array,
* each qualified with the sheet name (if present)
*
* @return array with one or two elements. never <code>null</code>
*/
private String[] seperateAreaRefs(String reference) {
String[] retval = null;
int length = reference.length();
int loc = reference.indexOf(':',0);
if(loc == -1){
retval = new String[1];
retval[0] = reference;
private static String[] separateAreaRefs(String reference) {
// TODO - refactor cell reference parsing logic to one place.
// Current known incarnations:
// FormulaParser.GetName()
// CellReference.separateRefParts()
// AreaReference.separateAreaRefs() (here)
// SheetNameFormatter.format() (inverse)
int len = reference.length();
int delimiterPos = -1;
boolean insideDelimitedName = false;
for(int i=0; i<len; i++) {
switch(reference.charAt(i)) {
case CELL_DELIMITER:
if(!insideDelimitedName) {
if(delimiterPos >=0) {
throw new IllegalArgumentException("More than one cell delimiter '"
+ CELL_DELIMITER + "' appears in area reference '" + reference + "'");
}
delimiterPos = i;
}
default:
continue;
case SPECIAL_NAME_DELIMITER:
// fall through
}
if(!insideDelimitedName) {
insideDelimitedName = true;
continue;
}
if(i >= len-1) {
// reference ends with the delimited name.
// Assume names like: "Sheet1!'A1'" are never legal.
throw new IllegalArgumentException("Area reference '" + reference
+ "' ends with special name delimiter '" + SPECIAL_NAME_DELIMITER + "'");
}
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
// two consecutive quotes is the escape sequence for a single one
i++; // skip this and keep parsing the special name
} else {
// this is the end of the delimited name
insideDelimitedName = false;
}
}
else{
retval = new String[2];
int sheetStart = reference.indexOf("!");
retval[0] = reference.substring(0, sheetStart+1) + reference.substring(sheetStart + 1,loc);
retval[1] = reference.substring(0, sheetStart+1) + reference.substring(loc+1);
if(delimiterPos < 0) {
return new String[] { reference, };
}
return retval;
String partA = reference.substring(0, delimiterPos);
String partB = reference.substring(delimiterPos+1);
if(partB.indexOf(SHEET_NAME_DELIMITER) >=0) {
// TODO - are references like "Sheet1!A1:Sheet1:B2" ever valid?
// FormulaParser has code to handle that.
throw new RuntimeException("Unexpected " + SHEET_NAME_DELIMITER
+ " in second cell reference of '" + reference + "'");
}
int plingPos = partA.lastIndexOf(SHEET_NAME_DELIMITER);
if(plingPos < 0) {
return new String [] { partA, partB, };
}
String sheetName = partA.substring(0, plingPos + 1); // +1 to include delimiter
return new String [] { partA, sheetName + partB, };
}
}

View File

@ -15,75 +15,99 @@
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.util;
import org.apache.poi.hssf.record.formula.SheetNameFormatter;
/**
*
* @author Avik Sengupta
* @author Dennis Doubleday (patch to seperateRowColumns())
*/
public class CellReference {
public final class CellReference {
/** The character ($) that signifies a row or column value is absolute instead of relative */
private static final char ABSOLUTE_REFERENCE_MARKER = '$';
/** The character (!) that separates sheet names from cell references */
private static final char SHEET_NAME_DELIMITER = '!';
/** The character (') used to quote sheet names when they contain special characters */
private static final char SPECIAL_NAME_DELIMITER = '\'';
/** Creates new CellReference */
private int row;
private int col;
private String sheetName;
private boolean rowAbs;
private boolean colAbs;
private final int _rowIndex;
private final int _colIndex;
private final String _sheetName;
private final boolean _isRowAbs;
private final boolean _isColAbs;
/**
* Create an cell ref from a string representation. Sheet names containing special characters should be
* delimited and escaped as per normal syntax rules for formulas.
*/
public CellReference(String cellRef) {
String[] parts = separateRefParts(cellRef);
sheetName = parts[0];
String ref = parts[1];
if ((ref == null)||("".equals(ref)))
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
if (ref.charAt(0) == '$') {
colAbs=true;
ref=ref.substring(1);
_sheetName = parts[0];
String colRef = parts[1];
if (colRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
}
col = convertColStringToNum(ref);
ref=parts[2];
if ((ref == null)||("".equals(ref)))
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
if (ref.charAt(0) == '$') {
rowAbs=true;
ref=ref.substring(1);
_isColAbs = colRef.charAt(0) == '$';
if (_isColAbs) {
colRef=colRef.substring(1);
}
row = Integer.parseInt(ref)-1;
}
public CellReference(int pRow, int pCol) {
this(pRow,pCol,false,false);
_colIndex = convertColStringToNum(colRef);
String rowRef=parts[2];
if (rowRef.length() < 1) {
throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'");
}
_isRowAbs = rowRef.charAt(0) == '$';
if (_isRowAbs) {
rowRef=rowRef.substring(1);
}
_rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based
}
public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
row=pRow;col=pCol;
rowAbs = pAbsRow;
colAbs=pAbsCol;
this(null, pRow, pCol, pAbsRow, pAbsCol);
}
public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) {
// TODO - "-1" is a special value being temporarily used for whole row and whole column area references.
// so these checks are currently N.Q.R.
if(pRow < -1) {
throw new IllegalArgumentException("row index may not be negative");
}
if(pCol < -1) {
throw new IllegalArgumentException("column index may not be negative");
}
_sheetName = pSheetName;
_rowIndex=pRow;
_colIndex=pCol;
_isRowAbs = pAbsRow;
_isColAbs=pAbsCol;
}
public int getRow(){return row;}
public short getCol(){return (short) col;}
public boolean isRowAbsolute(){return rowAbs;}
public boolean isColAbsolute(){return colAbs;}
public String getSheetName(){return sheetName;}
public int getRow(){return _rowIndex;}
public short getCol(){return (short) _colIndex;}
public boolean isRowAbsolute(){return _isRowAbs;}
public boolean isColAbsolute(){return _isColAbs;}
/**
* @return possibly <code>null</code> if this is a 2D reference. Special characters are not
* escaped or delimited
*/
public String getSheetName(){
return _sheetName;
}
protected void setSheetName(String sheetName) {
this.sheetName = sheetName;
}
/**
* takes in a column reference portion of a CellRef and converts it from
* ALPHA-26 number format to 0-based base 10.
*/
private int convertColStringToNum(String ref) {
int len = ref.length();
int lastIx = ref.length()-1;
int retval=0;
int pos = 0;
for (int k = ref.length()-1; k > -1; k--) {
for (int k = lastIx; k > -1; k--) {
char thechar = ref.charAt(k);
if ( pos == 0) {
retval += (Character.getNumericValue(thechar)-9);
@ -97,42 +121,86 @@ public class CellReference {
/**
* Seperates the row from the columns and returns an array. Element in
* position one is the substring containing the columns still in ALPHA-26
* number format.
* Separates the row from the columns and returns an array of three Strings. The first element
* is the sheet name. Only the first element may be null. The second element in is the column
* name still in ALPHA-26 number format. The third element is the row.
*/
private String[] separateRefParts(String reference) {
// Look for end of sheet name. This will either set
// start to 0 (if no sheet name present) or the
// index after the sheet reference ends.
String retval[] = new String[3];
int start = reference.indexOf("!");
if (start != -1) retval[0] = reference.substring(0, start);
start += 1;
private static String[] separateRefParts(String reference) {
int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER);
String sheetName = parseSheetName(reference, plingPos);
int start = plingPos+1;
int length = reference.length();
char[] chars = reference.toCharArray();
int loc = start;
if (chars[loc]=='$') loc++;
for (; loc < chars.length; loc++) {
if (Character.isDigit(chars[loc]) || chars[loc] == '$') {
// skip initial dollars
if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) {
loc++;
}
// step over column name chars until first digit (or dollars) for row number.
for (; loc < length; loc++) {
char ch = reference.charAt(loc);
if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) {
break;
}
}
return new String[] {
sheetName,
reference.substring(start,loc),
reference.substring(loc),
};
}
retval[1] = reference.substring(start,loc);
retval[2] = reference.substring(loc);
return retval;
private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) {
if(indexOfSheetNameDelimiter < 0) {
return null;
}
boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER;
if(!isQuoted) {
return reference.substring(0, indexOfSheetNameDelimiter);
}
int lastQuotePos = indexOfSheetNameDelimiter-1;
if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) {
throw new RuntimeException("Mismatched quotes: (" + reference + ")");
}
// TODO - refactor cell reference parsing logic to one place.
// Current known incarnations:
// FormulaParser.GetName()
// CellReference.parseSheetName() (here)
// AreaReference.separateAreaRefs()
// SheetNameFormatter.format() (inverse)
StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter);
for(int i=1; i<lastQuotePos; i++) { // Note boundaries - skip outer quotes
char ch = reference.charAt(i);
if(ch != SPECIAL_NAME_DELIMITER) {
sb.append(ch);
continue;
}
if(i < lastQuotePos) {
if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) {
// two consecutive quotes is the escape sequence for a single one
i++; // skip this and keep parsing the special name
sb.append(ch);
continue;
}
}
throw new RuntimeException("Bad sheet name quote escaping: (" + reference + ")");
}
return sb.toString();
}
/**
* takes in a 0-based base-10 column and returns a ALPHA-26 representation
* Takes in a 0-based base-10 column and returns a ALPHA-26
* representation.
* eg column #3 -> D
*/
private static String convertNumToColString(int col) {
protected static String convertNumToColString(int col) {
String retval = null;
int mod = col % 26;
int div = col / 26;
@ -148,14 +216,46 @@ public class CellReference {
return retval;
}
/**
* Example return values:
* <table border="0" cellpadding="1" cellspacing="0" summary="Example return values">
* <tr><th align='left'>Result</th><th align='left'>Comment</th></tr>
* <tr><td>A1</td><td>Cell reference without sheet</td></tr>
* <tr><td>Sheet1!A1</td><td>Standard sheet name</td></tr>
* <tr><td>'O''Brien''s Sales'!A1'&nbsp;</td><td>Sheet name with special characters</td></tr>
* </table>
* @return the text representation of this cell reference as it would appear in a formula.
*/
public String formatAsString() {
StringBuffer sb = new StringBuffer(32);
if(_sheetName != null) {
SheetNameFormatter.appendFormat(sb, _sheetName);
sb.append(SHEET_NAME_DELIMITER);
}
appendCellReference(sb);
return sb.toString();
}
public String toString() {
StringBuffer retval = new StringBuffer();
retval.append( (colAbs)?"$":"");
retval.append( convertNumToColString(col));
retval.append((rowAbs)?"$":"");
retval.append(row+1);
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
return retval.toString();
/**
* Appends cell reference with '$' markers for absolute values as required.
* Sheet name is not included.
*/
/* package */ void appendCellReference(StringBuffer sb) {
if(_isColAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}
sb.append( convertNumToColString(_colIndex));
if(_isRowAbs) {
sb.append(ABSOLUTE_REFERENCE_MARKER);
}
sb.append(_rowIndex+1);
}
}

View File

@ -19,6 +19,7 @@
package org.apache.poi.poifs.filesystem;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -30,6 +31,8 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.poifs.dev.POIFSViewable;
import org.apache.poi.poifs.property.DirectoryProperty;
import org.apache.poi.poifs.property.Property;
@ -58,6 +61,33 @@ import org.apache.poi.util.LongField;
public class POIFSFileSystem
implements POIFSViewable
{
private static final Log _logger = LogFactory.getLog(POIFSFileSystem.class);
private static final class CloseIgnoringInputStream extends InputStream {
private final InputStream _is;
public CloseIgnoringInputStream(InputStream is) {
_is = is;
}
public int read() throws IOException {
return _is.read();
}
public int read(byte[] b, int off, int len) throws IOException {
return _is.read(b, off, len);
}
public void close() {
// do nothing
}
}
/**
* Convenience method for clients that want to avoid the auto-close behaviour of the constructor.
*/
public static InputStream createNonClosingInputStream(InputStream is) {
return new CloseIgnoringInputStream(is);
}
private PropertyTable _property_table;
private List _documents;
private DirectoryNode _root;
@ -74,23 +104,52 @@ public class POIFSFileSystem
}
/**
* Create a POIFSFileSystem from an InputStream
* Create a POIFSFileSystem from an <tt>InputStream</tt>. Normally the stream is read until
* EOF. The stream is always closed.<p/>
*
* Some streams are usable after reaching EOF (typically those that return <code>true</code>
* for <tt>markSupported()</tt>). In the unlikely case that the caller has such a stream
* <i>and</i> needs to use it after this constructor completes, a work around is to wrap the
* stream in order to trap the <tt>close()</tt> call. A convenience method (
* <tt>createNonClosingInputStream()</tt>) has been provided for this purpose:
* <pre>
* InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is);
* HSSFWorkbook wb = new HSSFWorkbook(wrappedStream);
* is.reset();
* doSomethingElse(is);
* </pre>
* Note also the special case of <tt>ByteArrayInputStream</tt> for which the <tt>close()</tt>
* method does nothing.
* <pre>
* ByteArrayInputStream bais = ...
* HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() !
* bais.reset(); // no problem
* doSomethingElse(bais);
* </pre>
*
* @param stream the InputStream from which to read the data
*
* @exception IOException on errors reading, or on invalid data
*/
public POIFSFileSystem(final InputStream stream)
public POIFSFileSystem(InputStream stream)
throws IOException
{
this();
boolean success = false;
// read the header block from the stream
HeaderBlockReader header_block_reader = new HeaderBlockReader(stream);
HeaderBlockReader header_block_reader;
// read the rest of the stream into blocks
RawDataBlockList data_blocks = new RawDataBlockList(stream);
RawDataBlockList data_blocks;
try {
header_block_reader = new HeaderBlockReader(stream);
data_blocks = new RawDataBlockList(stream);
success = true;
} finally {
closeInputStream(stream, success);
}
// set up the block allocation table (necessary for the
// data_blocks to be manageable
@ -112,7 +171,32 @@ public class POIFSFileSystem
.getSBATStart()), data_blocks, properties.getRoot()
.getChildren(), null);
}
/**
* @param stream the stream to be closed
* @param success <code>false</code> if an exception is currently being thrown in the calling method
*/
private void closeInputStream(InputStream stream, boolean success) {
if(stream.markSupported() && !(stream instanceof ByteArrayInputStream)) {
String msg = "POIFS is closing the supplied input stream of type ("
+ stream.getClass().getName() + ") which supports mark/reset. "
+ "This will be a problem for the caller if the stream will still be used. "
+ "If that is the case the caller should wrap the input stream to avoid this close logic. "
+ "This warning is only temporary and will not be present in future versions of POI.";
_logger.warn(msg);
}
try {
stream.close();
} catch (IOException e) {
if(success) {
throw new RuntimeException(e);
}
// else not success? Try block did not complete normally
// just print stack trace and leave original ex to be thrown
e.printStackTrace();
}
}
/**
* Checks that the supplied InputStream (which MUST
* support mark and reset, or be a PushbackInputStream)
@ -123,23 +207,23 @@ public class POIFSFileSystem
* @param inp An InputStream which supports either mark/reset, or is a PushbackInputStream
*/
public static boolean hasPOIFSHeader(InputStream inp) throws IOException {
// We want to peek at the first 8 bytes
inp.mark(8);
// We want to peek at the first 8 bytes
inp.mark(8);
byte[] header = new byte[8];
IOUtils.readFully(inp, header);
byte[] header = new byte[8];
IOUtils.readFully(inp, header);
LongField signature = new LongField(HeaderBlockConstants._signature_offset, header);
// Wind back those 8 bytes
if(inp instanceof PushbackInputStream) {
PushbackInputStream pin = (PushbackInputStream)inp;
pin.unread(header);
PushbackInputStream pin = (PushbackInputStream)inp;
pin.unread(header);
} else {
inp.reset();
inp.reset();
}
// Did it match the signature?
return (signature.get() == HeaderBlockConstants._signature);
// Did it match the signature?
return (signature.get() == HeaderBlockConstants._signature);
}
/**

View File

@ -21,6 +21,8 @@ package org.apache.poi.poifs.storage;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import java.io.*;
@ -35,6 +37,7 @@ public class RawDataBlock
{
private byte[] _data;
private boolean _eof;
private static POILogger log = POILogFactory.getLogger(RawDataBlock.class);
/**
* Constructor RawDataBlock
@ -75,9 +78,12 @@ public class RawDataBlock
String type = " byte" + ((count == 1) ? ("")
: ("s"));
throw new IOException("Unable to read entire block; " + count
+ type + " read before EOF; expected "
+ blockSize + " bytes");
log.log(POILogger.ERROR,
"Unable to read entire block; " + count
+ type + " read before EOF; expected "
+ blockSize + " bytes. Your document"
+ " has probably been truncated!"
);
}
else {
_eof = false;

View File

@ -245,6 +245,16 @@ public class LittleEndian
putNumber(data, offset, value, SHORT_SIZE);
}
/**
* executes:<p/>
* <code>
* data[offset] = (byte)value;
* </code></p>
* Added for consistency with other put~() methods
*/
public static void putByte(byte[] data, int offset, int value) {
putNumber(data, offset, value, LittleEndianConsts.BYTE_SIZE);
}
/**
* put a array of shorts into a byte array

View File

@ -43,8 +43,6 @@ public class AddEval extends NumericOperationEval {
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@ -59,33 +57,31 @@ public class AddEval extends NumericOperationEval {
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
Eval retval = null;
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
double d = 0;
switch (operands.length) {
default: // will rarely happen. currently the parser itself fails.
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 2:
for (int i = 0, iSize = 2; retval==null && i < iSize; i++) {
ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d += ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
} // end for inside case
} // end switch
if (retval == null) {
retval = Double.isNaN(d) ? (ValueEval) ErrorEval.VALUE_INVALID : new NumberEval(d);
for (int i = 0; i < 2; i++) {
ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol);
if(ve instanceof ErrorEval) {
return ve;
}
if (ve instanceof NumericValueEval) {
d += ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
return ErrorEval.VALUE_INVALID;
}
}
return retval;
if(Double.isNaN(d) || Double.isInfinite(d)) {
return ErrorEval.NUM_ERROR;
}
return new NumberEval(d);
}
public int getNumberOfOperands() {

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.AreaPtg;
@ -27,48 +24,60 @@ import org.apache.poi.hssf.record.formula.Ptg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class Area2DEval implements AreaEval {
public final class Area2DEval implements AreaEval {
// TODO -refactor with Area3DEval
private final AreaPtg _delegate;
private AreaPtg delegate;
private ValueEval[] values;
private final ValueEval[] _values;
public Area2DEval(Ptg ptg, ValueEval[] values) {
this.delegate = (AreaPtg) ptg;
this.values = values;
if(ptg == null) {
throw new IllegalArgumentException("ptg must not be null");
}
if(values == null) {
throw new IllegalArgumentException("values must not be null");
}
for(int i=values.length-1; i>=0; i--) {
if(values[i] == null) {
throw new IllegalArgumentException("value array elements must not be null");
}
}
// TODO - check size of array vs size of AreaPtg
_delegate = (AreaPtg) ptg;
_values = values;
}
public short getFirstColumn() {
return delegate.getFirstColumn();
public int getFirstColumn() {
return _delegate.getFirstColumn();
}
public int getFirstRow() {
return delegate.getFirstRow();
return _delegate.getFirstRow();
}
public short getLastColumn() {
return delegate.getLastColumn();
public int getLastColumn() {
return _delegate.getLastColumn();
}
public int getLastRow() {
return delegate.getLastRow();
return _delegate.getLastRow();
}
public ValueEval[] getValues() {
return values;
return _values;
}
public ValueEval getValueAt(int row, short col) {
public ValueEval getValueAt(int row, int col) {
ValueEval retval;
int index = ((row-getFirstRow())*(getLastColumn()-getFirstColumn()+1))+(col-getFirstColumn());
if (index <0 || index >= values.length)
if (index <0 || index >= _values.length)
retval = ErrorEval.VALUE_INVALID;
else
retval = values[index];
retval = _values[index];
return retval;
}
public boolean contains(int row, short col) {
public boolean contains(int row, int col) {
return (getFirstRow() <= row) && (getLastRow() >= row)
&& (getFirstColumn() <= col) && (getLastColumn() >= col);
}
@ -82,10 +91,10 @@ public class Area2DEval implements AreaEval {
}
public boolean isColumn() {
return delegate.getFirstColumn() == delegate.getLastColumn();
return _delegate.getFirstColumn() == _delegate.getLastColumn();
}
public boolean isRow() {
return delegate.getFirstRow() == delegate.getLastRow();
return _delegate.getFirstRow() == _delegate.getLastRow();
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Area3DPtg;
@ -27,48 +24,60 @@ import org.apache.poi.hssf.record.formula.Ptg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class Area3DEval implements AreaEval {
public final class Area3DEval implements AreaEval {
// TODO -refactor with Area3DEval
private final Area3DPtg _delegate;
private Area3DPtg delegate;
private ValueEval[] values;
private final ValueEval[] _values;
public Area3DEval(Ptg ptg, ValueEval[] values) {
this.values = values;
this.delegate = (Area3DPtg) ptg;
if(ptg == null) {
throw new IllegalArgumentException("ptg must not be null");
}
if(values == null) {
throw new IllegalArgumentException("values must not be null");
}
for(int i=values.length-1; i>=0; i--) {
if(values[i] == null) {
throw new IllegalArgumentException("value array elements must not be null");
}
}
// TODO - check size of array vs size of AreaPtg
_values = values;
_delegate = (Area3DPtg) ptg;
}
public short getFirstColumn() {
return delegate.getFirstColumn();
public int getFirstColumn() {
return _delegate.getFirstColumn();
}
public int getFirstRow() {
return delegate.getFirstRow();
return _delegate.getFirstRow();
}
public short getLastColumn() {
return delegate.getLastColumn();
public int getLastColumn() {
return (short) _delegate.getLastColumn();
}
public int getLastRow() {
return delegate.getLastRow();
return _delegate.getLastRow();
}
public ValueEval[] getValues() {
return values;
return _values;
}
public ValueEval getValueAt(int row, short col) {
public ValueEval getValueAt(int row, int col) {
ValueEval retval;
int index = (row-getFirstRow())*(col-getFirstColumn());
if (index <0 || index >= values.length)
if (index <0 || index >= _values.length)
retval = ErrorEval.VALUE_INVALID;
else
retval = values[index];
retval = _values[index];
return retval;
}
public boolean contains(int row, short col) {
public boolean contains(int row, int col) {
return (getFirstRow() <= row) && (getLastRow() >= row)
&& (getFirstColumn() <= col) && (getLastColumn() >= col);
}
@ -83,11 +92,14 @@ public class Area3DEval implements AreaEval {
public boolean isColumn() {
return delegate.getFirstColumn() == delegate.getLastColumn();
return _delegate.getFirstColumn() == _delegate.getLastColumn();
}
public boolean isRow() {
return delegate.getFirstRow() == delegate.getLastRow();
return _delegate.getFirstRow() == _delegate.getLastRow();
}
public int getExternSheetIndex() {
return _delegate.getExternSheetIndex();
}
}

View File

@ -42,13 +42,13 @@ public interface AreaEval extends ValueEval {
* returns the 0-based index of the first col in
* this area.
*/
public short getFirstColumn();
public int getFirstColumn();
/**
* returns the 0-based index of the last col in
* this area.
*/
public short getLastColumn();
public int getLastColumn();
/**
* returns true if the Area's start and end row indexes
@ -80,7 +80,7 @@ public interface AreaEval extends ValueEval {
* @param row
* @param col
*/
public ValueEval getValueAt(int row, short col);
public ValueEval getValueAt(int row, int col);
/**
* returns true if the cell at row and col specified
@ -89,7 +89,7 @@ public interface AreaEval extends ValueEval {
* @param row
* @param col
*/
public boolean contains(int row, short col);
public boolean contains(int row, int col);
/**
* returns true if the specified col is in range

View File

@ -34,6 +34,16 @@ public class BoolEval implements NumericValueEval, StringValueEval {
public static final BoolEval FALSE = new BoolEval(false);
public static final BoolEval TRUE = new BoolEval(true);
/**
* Convenience method for the following:<br/>
* <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
* @return a <tt>BoolEval</tt> instance representing <tt>b</tt>.
*/
public static final BoolEval valueOf(boolean b) {
// TODO - find / replace all occurrences
return b ? TRUE : FALSE;
}
public BoolEval(Ptg ptg) {
this.value = ((BoolPtg) ptg).getValue();
@ -48,10 +58,17 @@ public class BoolEval implements NumericValueEval, StringValueEval {
}
public double getNumberValue() {
return value ? (short) 1 : (short) 0;
return value ? 1 : 0;
}
public String getStringValue() {
return value ? "TRUE" : "FALSE";
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(getStringValue());
sb.append("]");
return sb.toString();
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.ConcatPtg;
@ -27,7 +24,7 @@ import org.apache.poi.hssf.record.formula.Ptg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class ConcatEval extends StringOperationEval {
public final class ConcatEval extends StringOperationEval {
private ConcatPtg delegate;
@ -35,36 +32,27 @@ public class ConcatEval extends StringOperationEval {
this.delegate = (ConcatPtg) ptg;
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
Eval retval = null;
StringBuffer sb = null;
switch (operands.length) {
default: // paranoid check :)
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 2:
sb = new StringBuffer();
for (int i = 0, iSize = 2; retval == null && i < iSize; i++) {
ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol);
if (ve instanceof StringValueEval) {
StringValueEval sve = (StringValueEval) ve;
sb.append(sve.getStringValue());
}
else if (ve instanceof BlankEval) {
// do nothing
}
else { // must be an error eval
retval = ve;
}
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 2; i++) {
ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol);
if (ve instanceof StringValueEval) {
StringValueEval sve = (StringValueEval) ve;
sb.append(sve.getStringValue());
}
else if (ve instanceof BlankEval) {
// do nothing
}
else { // must be an error eval
return ve;
}
}
if (retval == null) {
retval = new StringEval(sb.toString());
}
return retval;
return new StringEval(sb.toString());
}
public int getNumberOfOperands() {

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.DividePtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class DivideEval extends NumericOperationEval {
public final class DivideEval extends NumericOperationEval {
private DividePtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@ -49,18 +44,28 @@ public class DivideEval extends NumericOperationEval {
return NUM_XLATOR;
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
Eval retval = null;
double d0 = 0;
double d1 = 0;
switch (operands.length) {
default: // will rarely happen. currently the parser itself fails.
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
if (retval == null) { // no error yet
ve = singleOperandEvaluate(args[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
@ -68,20 +73,7 @@ public class DivideEval extends NumericOperationEval {
else {
retval = ErrorEval.VALUE_INVALID;
}
if (retval == null) { // no error yet
ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
} // end switch
}
if (retval == null) {
retval = (d1 == 0)

View File

@ -14,51 +14,100 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*
*/
public class ErrorEval implements ValueEval {
public final class ErrorEval implements ValueEval {
private int errorCode;
// convenient access to namespace
private static final HSSFErrorConstants EC = null;
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL);
/** <b>#DIV/0!</b> - Division by zero */
public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0);
/** <b>#VALUE!</b> - Wrong type of operand */
public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE);
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF);
/** <b>#NAME?</b> - Wrong function or range name */
public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME);
/** <b>#NUM!</b> - Value range overflow */
public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM);
/** <b>#N/A</b> - Argument or function not available */
public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA);
public static final ErrorEval NAME_INVALID = new ErrorEval(525);
// POI internal error codes
private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
public static final ErrorEval VALUE_INVALID = new ErrorEval(519);
public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(FUNCTION_NOT_IMPLEMENTED_CODE);
// Note - Excel does not seem to represent this condition with an error code
public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE);
// Non std error codes
public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
/**
* Translates an Excel internal error code into the corresponding POI ErrorEval instance
* @param errorCode
*/
public static ErrorEval valueOf(int errorCode) {
switch(errorCode) {
case HSSFErrorConstants.ERROR_NULL: return NULL_INTERSECTION;
case HSSFErrorConstants.ERROR_DIV_0: return DIV_ZERO;
case HSSFErrorConstants.ERROR_VALUE: return VALUE_INVALID;
case HSSFErrorConstants.ERROR_REF: return REF_INVALID;
case HSSFErrorConstants.ERROR_NAME: return NAME_INVALID;
case HSSFErrorConstants.ERROR_NUM: return NUM_ERROR;
case HSSFErrorConstants.ERROR_NA: return NA;
// non-std errors (conditions modeled as errors by POI)
case CIRCULAR_REF_ERROR_CODE: return CIRCULAR_REF_ERROR;
case FUNCTION_NOT_IMPLEMENTED_CODE: return FUNCTION_NOT_IMPLEMENTED;
}
throw new RuntimeException("Unexpected error code (" + errorCode + ")");
}
public static final ErrorEval REF_INVALID = new ErrorEval(-40);
public static final ErrorEval NA = new ErrorEval(-50);
public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60);
public static final ErrorEval DIV_ZERO = new ErrorEval(-70);
public static final ErrorEval NUM_ERROR = new ErrorEval(-80);
/**
* Converts error codes to text. Handles non-standard error codes OK.
* For debug/test purposes (and for formatting error messages).
* @return the String representation of the specified Excel error code.
*/
public static String getText(int errorCode) {
if(HSSFErrorConstants.isValidCode(errorCode)) {
return HSSFErrorConstants.getText(errorCode);
}
// It is desirable to make these (arbitrary) strings look clearly different from any other
// value expression that might appear in a formula. In addition these error strings should
// look unlike the standard Excel errors. Hence tilde ('~') was used.
switch(errorCode) {
case CIRCULAR_REF_ERROR_CODE: return "~CIRCULAR~REF~";
case FUNCTION_NOT_IMPLEMENTED_CODE: return "~FUNCTION~NOT~IMPLEMENTED~";
}
return "~non~std~err(" + errorCode + ")~";
}
private int _errorCode;
/**
* @param errorCode an 8-bit value
*/
private ErrorEval(int errorCode) {
this.errorCode = errorCode;
_errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
return _errorCode;
}
public String getStringValue() {
return "Err:" + Integer.toString(errorCode);
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(getText(_errorCode));
sb.append("]");
return sb.toString();
}
}

View File

@ -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.hssf.record.formula.eval;
/**
* This class is used to simplify error handling logic <i>within</i> operator and function
* implementations. Note - <tt>OperationEval.evaluate()</tt> and <tt>Function.evaluate()</tt>
* method signatures do not throw this exception so it cannot propagate outside.<p/>
*
* Here is an example coded without <tt>EvaluationException</tt>, to show how it can help:
* <pre>
* public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
* // ...
* Eval arg0 = args[0];
* if(arg0 instanceof ErrorEval) {
* return arg0;
* }
* if(!(arg0 instanceof AreaEval)) {
* return ErrorEval.VALUE_INVALID;
* }
* double temp = 0;
* AreaEval area = (AreaEval)arg0;
* ValueEval[] values = area.getValues();
* for (int i = 0; i < values.length; i++) {
* ValueEval ve = values[i];
* if(ve instanceof ErrorEval) {
* return ve;
* }
* if(!(ve instanceof NumericValueEval)) {
* return ErrorEval.VALUE_INVALID;
* }
* temp += ((NumericValueEval)ve).getNumberValue();
* }
* // ...
* }
* </pre>
* In this example, if any error is encountered while processing the arguments, an error is
* returned immediately. This code is difficult to refactor due to all the points where errors
* are returned.<br/>
* Using <tt>EvaluationException</tt> allows the error returning code to be consolidated to one
* place.<p/>
* <pre>
* public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
* try {
* // ...
* AreaEval area = getAreaArg(args[0]);
* double temp = sumValues(area.getValues());
* // ...
* } catch (EvaluationException e) {
* return e.getErrorEval();
* }
*}
*
*private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
* if (arg0 instanceof ErrorEval) {
* throw new EvaluationException((ErrorEval) arg0);
* }
* if (arg0 instanceof AreaEval) {
* return (AreaEval) arg0;
* }
* throw EvaluationException.invalidValue();
*}
*
*private double sumValues(ValueEval[] values) throws EvaluationException {
* double temp = 0;
* for (int i = 0; i < values.length; i++) {
* ValueEval ve = values[i];
* if (ve instanceof ErrorEval) {
* throw new EvaluationException((ErrorEval) ve);
* }
* if (!(ve instanceof NumericValueEval)) {
* throw EvaluationException.invalidValue();
* }
* temp += ((NumericValueEval) ve).getNumberValue();
* }
* return temp;
*}
* </pre>
* It is not mandatory to use EvaluationException, doing so might give the following advantages:<br/>
* - Methods can more easily be extracted, allowing for re-use.<br/>
* - Type management (typecasting etc) is simpler because error conditions have been separated from
* intermediate calculation values.<br/>
* - Fewer local variables are required. Local variables can have stronger types.<br/>
* - It is easier to mimic common Excel error handling behaviour (exit upon encountering first
* error), because exceptions conveniently propagate up the call stack regardless of execution
* points or the number of levels of nested calls.<p/>
*
* <b>Note</b> - Only standard evaluation errors are represented by <tt>EvaluationException</tt> (
* i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions
* that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should
* be taken to not translate any POI internal error into an Excel evaluation error code.
*
* @author Josh Micich
*/
public final class EvaluationException extends Exception {
private final ErrorEval _errorEval;
public EvaluationException(ErrorEval errorEval) {
_errorEval = errorEval;
}
// some convenience factory methods
/** <b>#VALUE!</b> - Wrong type of operand */
public static EvaluationException invalidValue() {
return new EvaluationException(ErrorEval.VALUE_INVALID);
}
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static EvaluationException invalidRef() {
return new EvaluationException(ErrorEval.REF_INVALID);
}
/** <b>#NUM!</b> - Value range overflow */
public static EvaluationException numberError() {
return new EvaluationException(ErrorEval.NUM_ERROR);
}
public ErrorEval getErrorEval() {
return _errorEval;
}
}

View File

@ -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.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
*
* Common entry point for all external functions (where
* <tt>AbstractFunctionPtg.field_2_fnc_index</tt> == 255)
*
* @author Josh Micich
*/
final class ExternalFunction implements FreeRefFunction {
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
int nIncomingArgs = args.length;
if(nIncomingArgs < 1) {
throw new RuntimeException("function name argument missing");
}
if (!(args[0] instanceof NameEval)) {
throw new RuntimeException("First argument should be a NameEval, but got ("
+ args[0].getClass().getName() + ")");
}
NameEval functionNameEval = (NameEval) args[0];
int nOutGoingArgs = nIncomingArgs -1;
Eval[] outGoingArgs = new Eval[nOutGoingArgs];
System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
FreeRefFunction targetFunc;
try {
targetFunc = findTargetFunction(workbook, functionNameEval);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet);
}
private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException {
int numberOfNames = workbook.getNumberOfNames();
int nameIndex = functionNameEval.getIndex();
if(nameIndex < 0 || nameIndex >= numberOfNames) {
throw new RuntimeException("Bad name index (" + nameIndex
+ "). Allowed range is (0.." + (numberOfNames-1) + ")");
}
String functionName = workbook.getNameName(nameIndex);
if(false) {
System.out.println("received call to external function index (" + functionName + ")");
}
// TODO - detect if the NameRecord corresponds to a named range, function, or something undefined
// throw the right errors in these cases
// TODO find the implementation for the external function e.g. "YEARFRAC" or "ISEVEN"
throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
}
}

View File

@ -20,6 +20,9 @@
*/
package org.apache.poi.hssf.record.formula.eval;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.hssf.record.formula.functions.*;
/**
@ -27,12 +30,49 @@ import org.apache.poi.hssf.record.formula.functions.*;
*
*/
public abstract class FunctionEval implements OperationEval {
/**
* Some function IDs that require special treatment
*/
private static final class FunctionID {
/** 78 */
public static final int OFFSET = 78;
/** 148 */
public static final int INDIRECT = 148;
/** 255 */
public static final int EXTERNAL_FUNC = 255;
}
// convenient access to namespace
private static final FunctionID ID = null;
protected static Function[] functions = produceFunctions();
private static Map freeRefFunctionsByIdMap;
static {
Map m = new HashMap();
addMapping(m, ID.OFFSET, new Offset());
addMapping(m, ID.INDIRECT, new Indirect());
addMapping(m, ID.EXTERNAL_FUNC, new ExternalFunction());
freeRefFunctionsByIdMap = m;
}
private static void addMapping(Map m, int offset, FreeRefFunction frf) {
m.put(createFRFKey(offset), frf);
}
private static Integer createFRFKey(int functionIndex) {
return new Integer(functionIndex);
}
public Function getFunction() {
short fidx = getFunctionIndex();
return functions[fidx];
}
public boolean isFreeRefFunction() {
return freeRefFunctionsByIdMap.containsKey(createFRFKey(getFunctionIndex()));
}
public FreeRefFunction getFreeRefFunction() {
return (FreeRefFunction) freeRefFunctionsByIdMap.get(createFRFKey(getFunctionIndex()));
}
public abstract short getFunctionIndex();
@ -115,7 +155,7 @@ public abstract class FunctionEval implements OperationEval {
retval[75] = new Areas(); // AREAS
retval[76] = new Rows(); // ROWS
retval[77] = new Columns(); // COLUMNS
retval[78] = new Offset(); // OFFSET
retval[ID.OFFSET] = null; // Offset.evaluate has a different signature
retval[79] = new Absref(); // ABSREF
retval[80] = new Relref(); // RELREF
retval[81] = new Argument(); // ARGUMENT
@ -185,7 +225,7 @@ public abstract class FunctionEval implements OperationEval {
retval[145] = new NotImplementedFunction(); // GETDEF
retval[146] = new Reftext(); // REFTEXT
retval[147] = new Textref(); // TEXTREF
retval[148] = new Indirect(); // INDIRECT
retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
retval[149] = new NotImplementedFunction(); // REGISTER
retval[150] = new Call(); // CALL
retval[151] = new NotImplementedFunction(); // ADDBAR
@ -278,7 +318,7 @@ public abstract class FunctionEval implements OperationEval {
retval[252] = new Frequency(); // FREQUENCY
retval[253] = new NotImplementedFunction(); // ADDTOOLBAR
retval[254] = new NotImplementedFunction(); // DELETETOOLBAR
retval[255] = new NotImplementedFunction(); // EXTERNALFLAG
retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction
retval[256] = new NotImplementedFunction(); // RESETTOOLBAR
retval[257] = new Evaluate(); // EVALUATE
retval[258] = new NotImplementedFunction(); // GETTOOLBAR

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.MultiplyPtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class MultiplyEval extends NumericOperationEval {
public final class MultiplyEval extends NumericOperationEval {
private MultiplyPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@ -49,46 +44,39 @@ public class MultiplyEval extends NumericOperationEval {
return NUM_XLATOR;
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
Eval retval = null;
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
double d0 = 0;
double d1 = 0;
switch (operands.length) {
default: // will rarely happen. currently the parser itself fails.
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
if (retval == null) { // no error yet
ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
} // end switch
if (retval == null) {
retval = (Double.isNaN(d0) || Double.isNaN(d1))
? (ValueEval) ErrorEval.VALUE_INVALID
: new NumberEval(d0 * d1);
ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
}
return retval;
else if (ve instanceof BlankEval) {
// do nothing
}
else {
return ErrorEval.VALUE_INVALID;
}
ve = singleOperandEvaluate(args[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
return ErrorEval.VALUE_INVALID;
}
if (Double.isNaN(d0) || Double.isNaN(d1)) {
return ErrorEval.NUM_ERROR;
}
return new NumberEval(d0 * d1);
}
public int getNumberOfOperands() {

View File

@ -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.record.formula.eval;
/**
* @author Josh Micich
*/
public final class NameEval implements Eval {
private final int _index;
/**
* @param index zero based index to a defined name record
*/
public NameEval(int index) {
_index = index;
}
/**
* @return zero based index to a defined name record
*/
public int getIndex() {
return _index;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(_index);
sb.append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,277 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.record.formula.eval;
/**
* Provides functionality for evaluating arguments to functions and operators.
*
* @author Josh Micich
*/
public final class OperandResolver {
private OperandResolver() {
// no instances of this class
}
/**
* Retrieves a single value from a variety of different argument types according to standard
* Excel rules. Does not perform any type conversion.
* @param arg the evaluated argument as passed to the function or operator.
* @param srcCellRow used when arg is a single column AreaRef
* @param srcCellCol used when arg is a single row AreaRef
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>.
* Never <code>null</code> or <tt>ErrorEval</tt>.
* @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
* an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding
* EvaluationException is thrown.
*/
public static ValueEval getSingleValue(Eval arg, int srcCellRow, short srcCellCol)
throws EvaluationException {
Eval result;
if (arg instanceof RefEval) {
result = ((RefEval) arg).getInnerValueEval();
} else if (arg instanceof AreaEval) {
result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
} else {
result = arg;
}
if (result instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) result);
}
if (result instanceof ValueEval) {
return (ValueEval) result;
}
throw new RuntimeException("Unexpected eval type (" + result.getClass().getName() + ")");
}
/**
* Implements (some perhaps not well known) Excel functionality to select a single cell from an
* area depending on the coordinates of the calling cell. Here is an example demonstrating
* both selection from a single row area and a single column area in the same formula.
*
* <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
* <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
* <tr><th>2</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>200</td></tr>
* <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>300</td></tr>
* <tr><th>3</th><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>400</td></tr>
* </table>
*
* If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
* will look like this:
*
* <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
* <tr><th>&nbsp;</th><th>&nbsp;A&nbsp;</th><th>&nbsp;B&nbsp;</th><th>&nbsp;C&nbsp;</th><th>&nbsp;D&nbsp;</th></tr>
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td>&nbsp;</td></tr>
* <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
* <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
* <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
* </table>
*
* Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does
* not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula
* as written, but in the 4 cells A2:B3, the row and column selection works ok.<p/>
*
* The same concept is extended to references across sheets, such that even multi-row,
* multi-column areas can be useful.<p/>
*
* Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
* hence this method <b>can</b> throw a 'circular reference' EvaluationException. Note that
* this method does not attempt to detect cycles. Every cell in the specified Area <tt>ae</tt>
* has already been evaluated prior to this method call. Any cell (or cell<b>s</b>) part of
* <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will
* already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon entry to this method. It
* is assumed logic exists elsewhere to produce this behaviour.
*
* @return whatever the selected cell's evaluated value is. Never <code>null</code>. Never
* <tt>ErrorEval</tt>.
* @throws EvaluationException if there is a problem with indexing into the area, or if the
* evaluated cell has an error.
*/
public static ValueEval chooseSingleElementFromArea(AreaEval ae,
int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
if(result == null) {
// This seems to be required because AreaEval.values() array may contain nulls.
// perhaps that should not be allowed.
result = BlankEval.INSTANCE;
}
if (result instanceof ErrorEval) {
throw new EvaluationException((ErrorEval) result);
}
return result;
}
/**
* @return possibly <tt>ErrorEval</tt>, and <code>null</code>
*/
private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
int srcCellRow, short srcCellCol) throws EvaluationException {
if(false) {
// this is too simplistic
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
}
/*
Circular references are not dealt with directly here, but it is worth noting some issues.
ANY one of the return statements in this method could return a cell that is identical
to the one immediately being evaluated. The evaluating cell is identified by srcCellRow,
srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's
one reason why circular references are not easy to detect here. (The sheet of the returned
cell can be obtained from ae if it is an Area3DEval.)
Another reason there's little value in attempting to detect circular references here is
that only direct circular references could be detected. If the cycle involved two or more
cells this method could not detect it.
Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
(and HSSFFormulaEvaluator).
*/
}
if (ae.isColumn()) {
if(ae.isRow()) {
return ae.getValues()[0];
}
if(!ae.containsRow(srcCellRow)) {
throw EvaluationException.invalidValue();
}
return ae.getValueAt(srcCellRow, ae.getFirstColumn());
}
if(!ae.isRow()) {
// multi-column, multi-row area
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
return ae.getValueAt(ae.getFirstRow(), ae.getFirstColumn());
}
throw EvaluationException.invalidValue();
}
if(!ae.containsColumn(srcCellCol)) {
throw EvaluationException.invalidValue();
}
return ae.getValueAt(ae.getFirstRow(), srcCellCol);
}
/**
* Applies some conversion rules if the supplied value is not already an integer.<br/>
* Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ).<p/>
*
* Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
* The equivalent java code is:<br/>
* &nbsp;&nbsp;<code>return (int)Math.floor(d);</code><br/>
* <b>not</b>:<br/>
* &nbsp;&nbsp;<code>return (int)d; // wrong - rounds toward zero</code>
*
*/
public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
double d = coerceValueToDouble(ev);
// Note - the standard java type conversion from double to int truncates toward zero.
// but Math.floor() truncates toward negative infinity
return (int)Math.floor(d);
}
/**
* Applies some conversion rules if the supplied value is not already a number.
* Note - <tt>BlankEval</tt> is not supported and must be handled by the caller.
* @param ev must be a <tt>NumberEval</tt>, <tt>StringEval</tt> or <tt>BoolEval</tt>
* @return actual, parsed or interpreted double value (respectively).
* @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
* as a double (See <tt>parseDouble()</tt> for allowable formats).
* @throws RuntimeException if the supplied parameter is not <tt>NumberEval</tt>,
* <tt>StringEval</tt> or <tt>BoolEval</tt>
*/
public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
if (ev instanceof NumericValueEval) {
// this also handles booleans
return ((NumericValueEval)ev).getNumberValue();
}
if (ev instanceof StringEval) {
Double dd = parseDouble(((StringEval) ev).getStringValue());
if (dd == null) {
throw EvaluationException.invalidValue();
}
return dd.doubleValue();
}
throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
}
/**
* Converts a string to a double using standard rules that Excel would use.<br/>
* Tolerates currency prefixes, commas, leading and trailing spaces.<p/>
*
* Some examples:<br/>
* " 123 " -&gt; 123.0<br/>
* ".123" -&gt; 0.123<br/>
* These not supported yet:<br/>
* " $ 1,000.00 " -&gt; 1000.0<br/>
* "$1.25E4" -&gt; 12500.0<br/>
* "5**2" -&gt; 500<br/>
* "250%" -&gt; 2.5<br/>
*
* @param text
* @return <code>null</code> if the specified text cannot be parsed as a number
*/
public static Double parseDouble(String pText) {
String text = pText.trim();
if(text.length() < 1) {
return null;
}
boolean isPositive = true;
if(text.charAt(0) == '-') {
isPositive = false;
text= text.substring(1).trim();
}
if(!Character.isDigit(text.charAt(0))) {
// avoid using NumberFormatException to tell when string is not a number
return null;
}
// TODO - support notation like '1E3' (==1000)
double val;
try {
val = Double.parseDouble(text);
} catch (NumberFormatException e) {
return null;
}
return new Double(isPositive ? +val : -val);
}
/**
* @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
* @return the converted string value. never <code>null</code>
*/
public static String coerceValueToString(ValueEval ve) {
if (ve instanceof StringValueEval) {
StringValueEval sve = (StringValueEval) ve;
return sve.getStringValue();
}
if (ve instanceof NumberEval) {
NumberEval neval = (NumberEval) ve;
return neval.getStringValue();
}
if (ve instanceof BlankEval) {
return "";
}
throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.PowerPtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class PowerEval extends NumericOperationEval {
public final class PowerEval extends NumericOperationEval {
private PowerPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@ -49,48 +44,40 @@ public class PowerEval extends NumericOperationEval {
return NUM_XLATOR;
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
Eval retval = null;
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
double d0 = 0;
double d1 = 0;
switch (operands.length) {
default: // will rarely happen. currently the parser itself fails.
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
if (retval == null) { // no error yet
ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
} // end switch
if (retval == null) {
double p = Math.pow(d0, d1);
retval = (Double.isNaN(p))
? (ValueEval) ErrorEval.VALUE_INVALID
: new NumberEval(p);
ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
}
return retval;
else if (ve instanceof BlankEval) {
// do nothing
}
else {
return ErrorEval.VALUE_INVALID;
}
ve = singleOperandEvaluate(args[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
return ErrorEval.VALUE_INVALID;
}
double p = Math.pow(d0, d1);
if (Double.isNaN(p)) {
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(p);
}
public int getNumberOfOperands() {

View File

@ -14,47 +14,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 9, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.ReferencePtg;
/**
* @author adeshmukh
*
*/
public class Ref2DEval implements RefEval {
public final class Ref2DEval implements RefEval {
private ValueEval value;
private ReferencePtg delegate;
private final ValueEval value;
private final ReferencePtg delegate;
private boolean evaluated;
public Ref2DEval(Ptg ptg, ValueEval value, boolean evaluated) {
this.value = value;
this.delegate = (ReferencePtg) ptg;
this.evaluated = evaluated;
public Ref2DEval(ReferencePtg ptg, ValueEval ve) {
if(ve == null) {
throw new IllegalArgumentException("ve must not be null");
}
if(false && ptg == null) { // TODO - fix dodgy code in MultiOperandNumericFunction
throw new IllegalArgumentException("ptg must not be null");
}
value = ve;
delegate = ptg;
}
public ValueEval getInnerValueEval() {
return value;
}
public short getRow() {
public int getRow() {
return delegate.getRow();
}
public short getColumn() {
public int getColumn() {
return delegate.getColumn();
}
public boolean isEvaluated() {
return evaluated;
}
}

View File

@ -14,47 +14,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 9, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
/**
* @author Amol S. Deshmukh
*
*/
public class Ref3DEval implements RefEval {
public final class Ref3DEval implements RefEval {
private ValueEval value;
private final ValueEval value;
private final Ref3DPtg delegate;
private Ref3DPtg delegate;
private boolean evaluated;
public Ref3DEval(Ptg ptg, ValueEval value, boolean evaluated) {
this.value = value;
this.delegate = (Ref3DPtg) ptg;
this.evaluated = evaluated;
public Ref3DEval(Ref3DPtg ptg, ValueEval ve) {
if(ve == null) {
throw new IllegalArgumentException("ve must not be null");
}
if(ptg == null) {
throw new IllegalArgumentException("ptg must not be null");
}
value = ve;
delegate = ptg;
}
public ValueEval getInnerValueEval() {
return value;
}
public short getRow() {
public int getRow() {
return delegate.getRow();
}
public short getColumn() {
public int getColumn() {
return delegate.getColumn();
}
public boolean isEvaluated() {
return evaluated;
public int getExternSheetIndex() {
return delegate.getExternSheetIndex();
}
}

View File

@ -14,11 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 9, 2005
*
*
*/
package org.apache.poi.hssf.record.formula.eval;
/**
@ -44,26 +40,12 @@ public interface RefEval extends ValueEval {
public ValueEval getInnerValueEval();
/**
* returns the column index.
* returns the zero based column index.
*/
public short getColumn();
public int getColumn();
/**
* returns the row index.
* returns the zero based row index.
*/
public short getRow();
/**
* returns true if this RefEval contains an
* evaluated value instead of a direct value.
* eg. say cell A1 has the value: ="test"
* Then the RefEval representing A1 will return
* isEvaluated() equal to false. On the other
* hand, say cell A1 has the value: =B1 and
* B1 has the value "test", then the RefEval
* representing A1 will return isEvaluated()
* equal to true.
*/
public boolean isEvaluated();
public int getRow();
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,21 +24,31 @@ import org.apache.poi.hssf.record.formula.StringPtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class StringEval implements StringValueEval {
public final class StringEval implements StringValueEval {
public static final StringEval EMPTY_INSTANCE = new StringEval("");
private String value;
private final String value;
public StringEval(Ptg ptg) {
this.value = ((StringPtg) ptg).getValue();
this(((StringPtg) ptg).getValue());
}
public StringEval(String value) {
if(value == null) {
throw new IllegalArgumentException("value must not be null");
}
this.value = value;
}
public String getStringValue() {
return value;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(value);
sb.append("]");
return sb.toString();
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
/**
@ -26,5 +23,8 @@ package org.apache.poi.hssf.record.formula.eval;
*/
public interface StringValueEval extends ValueEval {
public String getStringValue();
/**
* @return never <code>null</code>, possibly empty string.
*/
String getStringValue();
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.SubtractPtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class SubtractEval extends NumericOperationEval {
public final class SubtractEval extends NumericOperationEval {
private SubtractPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@ -49,18 +44,28 @@ public class SubtractEval extends NumericOperationEval {
return NUM_XLATOR;
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 2) {
return ErrorEval.VALUE_INVALID;
}
Eval retval = null;
double d0 = 0;
double d1 = 0;
switch (operands.length) {
default: // will rarely happen. currently the parser itself fails.
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
if (retval == null) { // no error yet
ve = singleOperandEvaluate(args[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d0 = ((NumericValueEval) ve).getNumberValue();
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
@ -68,21 +73,8 @@ public class SubtractEval extends NumericOperationEval {
else {
retval = ErrorEval.VALUE_INVALID;
}
if (retval == null) { // no error yet
ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d1 = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
} // end switch
}
if (retval == null) {
retval = (Double.isNaN(d0) || Double.isNaN(d1))
? (ValueEval) ErrorEval.VALUE_INVALID

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,14 +24,12 @@ import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class UnaryMinusEval extends NumericOperationEval {
public final class UnaryMinusEval extends NumericOperationEval {
private UnaryMinusPtg delegate;
private static final ValueEvalToNumericXlator NUM_XLATOR =
new ValueEvalToNumericXlator((short)
( ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@ -49,32 +44,24 @@ public class UnaryMinusEval extends NumericOperationEval {
return NUM_XLATOR;
}
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
ValueEval retval = null;
public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
if(args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
double d = 0;
switch (operands.length) {
default:
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 1:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else if (ve instanceof ErrorEval) {
retval = ve;
}
ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
if (ve instanceof NumericValueEval) {
d = ((NumericValueEval) ve).getNumberValue();
}
else if (ve instanceof BlankEval) {
// do nothing
}
else if (ve instanceof ErrorEval) {
return ve;
}
if (retval == null) {
retval = new NumberEval(-d);
}
return retval;
return new NumberEval(-d);
}
public int getNumberOfOperands() {

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 8, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.Ptg;
@ -27,111 +24,38 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class UnaryPlusEval implements OperationEval /*extends NumericOperationEval*/ {
public final class UnaryPlusEval implements OperationEval {
private UnaryPlusPtg delegate;
/*
* COMMENT FOR COMMENTED CODE IN THIS FILE
*
* In excel the programmer seems to not have cared to
* think about how strings were handled in other numeric
* operations when he/she was implementing this operation :P
*
* Here's what I mean:
*
* Q. If the formula -"hello" evaluates to #VALUE! in excel, what should
* the formula +"hello" evaluate to?
*
* A. +"hello" evaluates to "hello" (what the...?)
*
/**
* called by reflection
*/
// private static final ValueEvalToNumericXlator NUM_XLATOR =
// new ValueEvalToNumericXlator((short)
// ( ValueEvalToNumericXlator.BOOL_IS_PARSED
// | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
// | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
// | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
// | ValueEvalToNumericXlator.STRING_IS_PARSED
// ));
public UnaryPlusEval(Ptg ptg) {
this.delegate = (UnaryPlusPtg) ptg;
}
// protected ValueEvalToNumericXlator getXlator() {
// return NUM_XLATOR;
// }
public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
ValueEval retval = null;
switch (operands.length) {
default:
retval = ErrorEval.UNKNOWN_ERROR;
break;
case 1:
// ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
// if (ve instanceof NumericValueEval) {
// d = ((NumericValueEval) ve).getNumberValue();
// }
// else if (ve instanceof BlankEval) {
// // do nothing
// }
// else if (ve instanceof ErrorEval) {
// retval = ve;
// }
if (operands[0] instanceof RefEval) {
RefEval re = (RefEval) operands[0];
retval = re.getInnerValueEval();
}
else if (operands[0] instanceof AreaEval) {
AreaEval ae = (AreaEval) operands[0];
if (ae.contains(srcRow, srcCol)) { // circular ref!
retval = ErrorEval.CIRCULAR_REF_ERROR;
}
else if (ae.isRow()) {
if (ae.containsColumn(srcCol)) {
ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
if (ve instanceof RefEval) {
ve = ((RefEval) ve).getInnerValueEval();
}
retval = ve;
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else if (ae.isColumn()) {
if (ae.containsRow(srcRow)) {
ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
if (ve instanceof RefEval) {
ve = ((RefEval) ve).getInnerValueEval();
}
retval = ve;
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else {
retval = (ValueEval) operands[0];
}
}
if (retval instanceof BlankEval) {
retval = new NumberEval(0);
}
return retval;
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
if(args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
double d;
try {
ValueEval ve = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
if(ve instanceof BlankEval) {
return NumberEval.ZERO;
}
if(ve instanceof StringEval) {
// Note - asymmetric with UnaryMinus
// -"hello" evaluates to #VALUE!
// but +"hello" evaluates to "hello"
return ve;
}
d = OperandResolver.coerceValueToDouble(ve);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return new NumberEval(+d);
}
public int getNumberOfOperands() {
@ -141,5 +65,4 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
public int getType() {
return delegate.getType();
}
}

View File

@ -24,7 +24,7 @@ package org.apache.poi.hssf.record.formula.eval;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class ValueEvalToNumericXlator {
public final class ValueEvalToNumericXlator {
public static final int STRING_IS_PARSED = 0x0001;
public static final int BOOL_IS_PARSED = 0x0002;
@ -34,26 +34,18 @@ public class ValueEvalToNumericXlator {
public static final int REF_BOOL_IS_PARSED = 0x0010;
public static final int REF_BLANK_IS_PARSED = 0x0020;
public static final int EVALUATED_REF_STRING_IS_PARSED = 0x0040;
public static final int EVALUATED_REF_BOOL_IS_PARSED = 0x0080;
public static final int EVALUATED_REF_BLANK_IS_PARSED = 0x0100;
public static final int STRING_TO_BOOL_IS_PARSED = 0x0200;
public static final int REF_STRING_TO_BOOL_IS_PARSED = 0x0400;
public static final int STRING_IS_INVALID_VALUE = 0x0800;
public static final int REF_STRING_IS_INVALID_VALUE = 0x1000;
// public static final int BOOL_IS_BLANK = 0x2000;
// public static final int REF_BOOL_IS_BLANK = 0x4000;
// public static final int STRING_IS_BLANK = 0x8000;
// public static final int REF_STRING_IS_BLANK = 0x10000;
private final int flags;
public ValueEvalToNumericXlator(int flags) {
this.flags = flags;
if (false) { // uncomment to see who is using this class
System.err.println(new Throwable().getStackTrace()[1].getClassName() + "\t0x"
+ Integer.toHexString(flags).toUpperCase());
}
this.flags = flags;
}
/**
@ -71,7 +63,7 @@ public class ValueEvalToNumericXlator {
// most common case - least worries :)
else if (eval instanceof NumberEval) {
retval = (NumberEval) eval;
retval = eval;
}
// booleval
@ -125,50 +117,33 @@ public class ValueEvalToNumericXlator {
* @param eval
*/
private ValueEval xlateRefEval(RefEval reval) {
ValueEval retval = null;
ValueEval eval = (ValueEval) reval.getInnerValueEval();
ValueEval eval = reval.getInnerValueEval();
// most common case - least worries :)
if (eval instanceof NumberEval) {
retval = (NumberEval) eval;
return eval;
}
// booleval
else if (eval instanceof BoolEval) {
retval = ((flags & REF_BOOL_IS_PARSED) > 0)
if (eval instanceof BoolEval) {
return ((flags & REF_BOOL_IS_PARSED) > 0)
? (ValueEval) eval
: BlankEval.INSTANCE;
}
// stringeval
else if (eval instanceof StringEval) {
retval = xlateRefStringEval((StringEval) eval);
if (eval instanceof StringEval) {
return xlateRefStringEval((StringEval) eval);
}
// erroreval
else if (eval instanceof ErrorEval) {
retval = eval;
if (eval instanceof ErrorEval) {
return eval;
}
// refeval
else if (eval instanceof RefEval) {
RefEval re = (RefEval) eval;
retval = xlateRefEval(re);
if (eval instanceof BlankEval) {
return xlateBlankEval(REF_BLANK_IS_PARSED);
}
else if (eval instanceof BlankEval) {
retval = xlateBlankEval(reval.isEvaluated() ? EVALUATED_REF_BLANK_IS_PARSED : REF_BLANK_IS_PARSED);
}
// probably AreaEval ? then not acceptable.
else {
throw new RuntimeException("Invalid ValueEval type passed for conversion: " + eval.getClass());
}
return retval;
throw new RuntimeException("Invalid ValueEval type passed for conversion: ("
+ eval.getClass().getName() + ")");
}
/**
@ -176,93 +151,38 @@ public class ValueEvalToNumericXlator {
* @param eval
*/
private ValueEval xlateStringEval(StringEval eval) {
ValueEval retval = null;
if ((flags & STRING_IS_PARSED) > 0) {
String s = eval.getStringValue();
try {
double d = Double.parseDouble(s);
retval = new NumberEval(d);
}
catch (Exception e) {
if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) {
try {
boolean b = Boolean.getBoolean(s);
retval = b ? BoolEval.TRUE : BoolEval.FALSE;
}
catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; }
}
else {
retval = ErrorEval.VALUE_INVALID;
}
Double d = OperandResolver.parseDouble(s);
if(d == null) {
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(d.doubleValue());
}
else if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) {
String s = eval.getStringValue();
try {
boolean b = Boolean.getBoolean(s);
retval = b ? BoolEval.TRUE : BoolEval.FALSE;
}
catch (Exception e) { retval = ErrorEval.VALUE_INVALID; }
}
// strings are errors?
else if ((flags & STRING_IS_INVALID_VALUE) > 0) {
retval = ErrorEval.VALUE_INVALID;
if ((flags & STRING_IS_INVALID_VALUE) > 0) {
return ErrorEval.VALUE_INVALID;
}
// ignore strings
else {
retval = xlateBlankEval(BLANK_IS_PARSED);
}
return retval;
return xlateBlankEval(BLANK_IS_PARSED);
}
/**
* uses the relevant flags to decode the StringEval
* @param eval
*/
private ValueEval xlateRefStringEval(StringEval eval) {
ValueEval retval = null;
private ValueEval xlateRefStringEval(StringEval sve) {
if ((flags & REF_STRING_IS_PARSED) > 0) {
StringEval sve = (StringEval) eval;
String s = sve.getStringValue();
try {
double d = Double.parseDouble(s);
retval = new NumberEval(d);
}
catch (Exception e) {
if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) {
try {
boolean b = Boolean.getBoolean(s);
retval = b ? BoolEval.TRUE : BoolEval.FALSE;
}
catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; }
}
else {
retval = ErrorEval.VALUE_INVALID;
}
Double d = OperandResolver.parseDouble(s);
if(d == null) {
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(d.doubleValue());
}
else if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) {
StringEval sve = (StringEval) eval;
String s = sve.getStringValue();
try {
boolean b = Boolean.getBoolean(s);
retval = b ? BoolEval.TRUE : BoolEval.FALSE;;
}
catch (Exception e) { retval = ErrorEval.VALUE_INVALID; }
}
// strings are errors?
else if ((flags & REF_STRING_IS_INVALID_VALUE) > 0) {
retval = ErrorEval.VALUE_INVALID;
}
// strings are blanks
else {
retval = BlankEval.INSTANCE;
}
return retval;
return BlankEval.INSTANCE;
}
}

View File

@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
public class Avedev extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (0
// ValueEvalToNumericXlator.BOOL_IS_PARSED
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
@ -44,7 +44,6 @@ public class Avedev extends MultiOperandNumericFunction {
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
| ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**

View File

@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
public class Average extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (0
// ValueEvalToNumericXlator.BOOL_IS_PARSED
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
@ -44,7 +44,6 @@ public class Average extends MultiOperandNumericFunction {
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
| ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
));
/**

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on Jun 20, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
@ -38,13 +35,10 @@ public abstract class FinanceFunction extends NumericFunction {
new ValueEvalToNumericXlator((short) (0
| ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.BLANK_IS_PARSED
| ValueEvalToNumericXlator.REF_BLANK_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
@ -56,11 +50,11 @@ public abstract class FinanceFunction extends NumericFunction {
* if they desire to return a different ValueEvalToNumericXlator instance
* than the default.
*/
protected ValueEvalToNumericXlator getXlator() {
protected final ValueEvalToNumericXlator getXlator() {
return DEFAULT_NUM_XLATOR;
}
protected ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) {
protected final ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) {
ValueEval retval = null;
retval = singleOperandEvaluate(eval, srcRow, srcCol);
if (retval instanceof NumericValueEval) {
@ -74,5 +68,4 @@ public abstract class FinanceFunction extends NumericFunction {
}
return retval;
}
}

View File

@ -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.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* For most Excel functions, involving references ((cell, area), (2d, 3d)), the references are
* passed in as arguments, and the exact location remains fixed. However, a select few Excel
* functions have the ability to access cells that were not part of any reference passed as an
* argument.<br/>
* Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/>
*
* In POI, the <tt>HSSFFormulaEvaluator</tt> evaluates every cell in each reference argument before
* calling the function. This means that functions using fixed references do not need access to
* the rest of the workbook to execute. Hence the <tt>evaluate()</tt> method on the common
* interface <tt>Function</tt> does not take a workbook parameter.<p>
*
* This interface recognises the requirement of some functions to freely create and evaluate
* references beyond those passed in as arguments.
*
* @author Josh Micich
*/
public interface FreeRefFunction {
/**
*
* @param args the pre-evaluated arguments for this function. args is never <code>null</code>,
* nor are any of its elements.
* @param srcCellRow zero based row index of the cell containing the currently evaluating formula
* @param srcCellCol zero based column index of the cell containing the currently evaluating formula
* @param workbook is the workbook containing the formula/cell being evaluated
* @param sheet is the sheet containing the formula/cell being evaluated
* @return never <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> in the case of
* a specified Excel error (Exceptions are never thrown to represent Excel errors).
*
*/
ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet);
}

View File

@ -1,25 +1,123 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.record.formula.functions;
public class Hlookup extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
/**
* Implementation of the VLOOKUP() function.<p/>
*
* HLOOKUP finds a column in a lookup table by the first row value and returns the value from another row.
*
* <b>Syntax</b>:<br/>
* <b>HLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>row_index_num</b>, range_lookup)<p/>
*
* <b>lookup_value</b> The value to be found in the first column of the table array.<br/>
* <b>table_array</> An area reference for the lookup data. <br/>
* <b>row_index_num</b> a 1 based index specifying which row value of the lookup data will be returned.<br/>
* <b>range_lookup</b> If TRUE (default), HLOOKUP finds the largest value less than or equal to
* the lookup_value. If FALSE, only exact matches will be considered<br/>
*
* @author Josh Micich
*/
public final class Hlookup implements Function {
private static final class RowVector implements ValueVector {
private final AreaEval _tableArray;
private final int _size;
private final int _rowAbsoluteIndex;
private final int _firstColumnAbsoluteIndex;
public RowVector(AreaEval tableArray, int rowIndex) {
_rowAbsoluteIndex = tableArray.getFirstRow() + rowIndex;
if(!tableArray.containsRow(_rowAbsoluteIndex)) {
int lastRowIx = tableArray.getLastRow() - tableArray.getFirstRow();
throw new IllegalArgumentException("Specified row index (" + rowIndex
+ ") is outside the allowed range (0.." + lastRowIx + ")");
}
_tableArray = tableArray;
_size = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1;
if(_size < 1) {
throw new RuntimeException("bad table array size zero");
}
_firstColumnAbsoluteIndex = tableArray.getFirstColumn();
}
public ValueEval getItem(int index) {
if(index>_size) {
throw new ArrayIndexOutOfBoundsException("Specified index (" + index
+ ") is outside the allowed range (0.." + (_size-1) + ")");
}
return _tableArray.getValueAt(_rowAbsoluteIndex, (short) (_firstColumnAbsoluteIndex + index));
}
public int getSize() {
return _size;
}
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
Eval arg3 = null;
switch(args.length) {
case 4:
arg3 = args[3]; // important: assumed array element is never null
case 3:
break;
default:
// wrong number of arguments
return ErrorEval.VALUE_INVALID;
}
try {
// Evaluation order:
// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 row_index, fetch result
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]);
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol);
int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, new RowVector(tableArray, 0), isRangeLookup);
ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol);
int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex);
ValueVector resultCol = createResultColumnVector(tableArray, rowIndex);
return resultCol.getItem(colIndex);
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
/**
* Returns one column from an <tt>AreaEval</tt>
*
* @throws EvaluationException (#VALUE!) if colIndex is negative, (#REF!) if colIndex is too high
*/
private ValueVector createResultColumnVector(AreaEval tableArray, int colIndex) throws EvaluationException {
if(colIndex < 0) {
throw EvaluationException.invalidValue();
}
int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1;
if(colIndex >= nCols) {
throw EvaluationException.invalidRef();
}
return new RowVector(tableArray, colIndex);
}
}

View File

@ -28,28 +28,22 @@ import org.apache.poi.hssf.record.formula.eval.Eval;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class If implements Function {
public final class If implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
public Eval evaluate(Eval[] evals, int srcCellRow, short srcCellCol) {
Eval retval = null;
Eval evalWhenFalse = BoolEval.FALSE;
switch (evals.length) {
switch (args.length) {
case 3:
evalWhenFalse = evals[2];
evalWhenFalse = args[2];
case 2:
BoolEval beval = (BoolEval) evals[0];
BoolEval beval = (BoolEval) args[0]; // TODO - class cast exception
if (beval.getBooleanValue()) {
retval = evals[1];
return args[1];
}
else {
retval = evalWhenFalse;
}
break;
return evalWhenFalse;
default:
retval = ErrorEval.UNKNOWN_ERROR;
return ErrorEval.VALUE_INVALID;
}
return retval;
}
}

View File

@ -14,12 +14,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
public class Indirect extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Implementation for Excel function INDIRECT<p/>
*
* INDIRECT() returns the cell or area reference denoted by the text argument.<p/>
*
* <b>Syntax</b>:</br>
* <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/>
*
* <b>ref_text</b> a string representation of the desired reference as it would normally be written
* in a cell formula.<br/>
* <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be interpreted as A1-style
* or R1C1-style.
*
*
* @author Josh Micich
*/
public final class Indirect implements FreeRefFunction {
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
// TODO - implement INDIRECT()
return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
}
}

View File

@ -14,79 +14,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class Isblank implements Function {
public final class Isblank implements Function {
public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
ValueEval retval = null;
boolean b = false;
switch (operands.length) {
default:
retval = ErrorEval.VALUE_INVALID;
break;
case 1:
if (operands[0] instanceof BlankEval) {
b = true;
}
else if (operands[0] instanceof AreaEval) {
AreaEval ae = (AreaEval) operands[0];
if (ae.contains(srcCellRow, srcCellCol)) { // circular ref!
retval = ErrorEval.CIRCULAR_REF_ERROR;
}
else if (ae.isRow()) {
if (ae.containsColumn(srcCellCol)) {
ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCellCol);
b = (ve instanceof BlankEval);
}
else {
b = false;
}
}
else if (ae.isColumn()) {
if (ae.containsRow(srcCellRow)) {
ValueEval ve = ae.getValueAt(srcCellRow, ae.getFirstColumn());
b = (ve instanceof BlankEval);
}
else {
b = false;
}
}
else {
b = false;
}
}
else if (operands[0] instanceof RefEval) {
RefEval re = (RefEval) operands[0];
b = (!re.isEvaluated()) && re.getInnerValueEval() instanceof BlankEval;
}
else {
b = false;
}
}
if (retval == null) {
retval = b
? BoolEval.TRUE
: BoolEval.FALSE;
}
return retval;
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
if(args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
Eval arg = args[0];
ValueEval singleCellValue;
try {
singleCellValue = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
} catch (EvaluationException e) {
return BoolEval.FALSE;
}
return BoolEval.valueOf(singleCellValue instanceof BlankEval);
}
}

View File

@ -14,125 +14,36 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringValueEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class Len extends TextFunction {
public final class Len extends TextFunction {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
if(args.length != 1) {
return ErrorEval.VALUE_INVALID;
}
try {
ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
ValueEval retval = null;
String s = null;
switch (operands.length) {
default:
retval = ErrorEval.VALUE_INVALID;
break;
case 1:
ValueEval ve = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
if (ve instanceof StringValueEval) {
StringValueEval sve = (StringValueEval) ve;
s = sve.getStringValue();
}
else if (ve instanceof RefEval) {
RefEval re = (RefEval) ve;
ValueEval ive = re.getInnerValueEval();
if (ive instanceof BlankEval) {
s = re.isEvaluated() ? "0" : null;
}
else if (ive instanceof StringValueEval) {
s = ((StringValueEval) ive).getStringValue();
}
else if (ive instanceof BlankEval) {}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else if (ve instanceof BlankEval) {}
else {
retval = ErrorEval.VALUE_INVALID;
break;
}
}
if (retval == null) {
s = (s == null) ? EMPTY_STRING : s;
retval = new NumberEval(s.length());
}
return retval;
}
protected ValueEval singleOperandEvaluate(Eval eval, int srcRow, short srcCol) {
ValueEval retval;
if (eval instanceof AreaEval) {
AreaEval ae = (AreaEval) eval;
if (ae.contains(srcRow, srcCol)) { // circular ref!
retval = ErrorEval.CIRCULAR_REF_ERROR;
}
else if (ae.isRow()) {
if (ae.containsColumn(srcCol)) {
ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
retval = attemptXlateToText(ve);
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else if (ae.isColumn()) {
if (ae.containsRow(srcRow)) {
ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn());
retval = attemptXlateToText(ve);
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else {
retval = ErrorEval.VALUE_INVALID;
}
}
else {
retval = attemptXlateToText((ValueEval) eval);
}
return retval;
}
/**
* converts from Different ValueEval types to StringEval.
* Note: AreaEvals are not handled, if arg is an AreaEval,
* the returned value is ErrorEval.VALUE_INVALID
* @param ve
*/
protected ValueEval attemptXlateToText(ValueEval ve) {
ValueEval retval;
if (ve instanceof StringValueEval || ve instanceof RefEval) {
retval = ve;
}
else if (ve instanceof BlankEval) {
retval = ve;
}
else {
retval = ErrorEval.VALUE_INVALID;
}
return retval;
}
String str = OperandResolver.coerceValueToString(veval);
return new NumberEval(str.length());
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
}

View File

@ -1,25 +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.
*/
/*
* Created on May 15, 2005
*
*/
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.record.formula.functions;
public class Lookup extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector;
/**
* Implementation of Excel function LOOKUP.<p/>
*
* LOOKUP finds an index row in a lookup table by the first column value and returns the value from another column.
*
* <b>Syntax</b>:<br/>
* <b>VLOOKUP</b>(<b>lookup_value</b>, <b>lookup_vector</b>, result_vector)<p/>
*
* <b>lookup_value</b> The value to be found in the lookup vector.<br/>
* <b>lookup_vector</> An area reference for the lookup data. <br/>
* <b>result_vector</b> Single row or single column area reference from which the result value is chosen.<br/>
*
* @author Josh Micich
*/
public final class Lookup implements Function {
private static final class SimpleValueVector implements ValueVector {
private final ValueEval[] _values;
public SimpleValueVector(ValueEval[] values) {
_values = values;
}
public ValueEval getItem(int index) {
return _values[index];
}
public int getSize() {
return _values.length;
}
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
switch(args.length) {
case 3:
break;
case 2:
// complex rules to choose lookupVector and resultVector from the single area ref
throw new RuntimeException("Two arg version of LOOKUP not supported yet");
default:
return ErrorEval.VALUE_INVALID;
}
try {
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
AreaEval aeLookupVector = LookupUtils.resolveTableArrayArg(args[1]);
AreaEval aeResultVector = LookupUtils.resolveTableArrayArg(args[2]);
ValueVector lookupVector = createVector(aeLookupVector);
ValueVector resultVector = createVector(aeResultVector);
if(lookupVector.getSize() > resultVector.getSize()) {
// Excel seems to handle this by accessing past the end of the result vector.
throw new RuntimeException("Lookup vector and result vector of differing sizes not supported yet");
}
int index = LookupUtils.lookupIndexOfValue(lookupValue, lookupVector, true);
return resultVector.getItem(index);
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
private static ValueVector createVector(AreaEval ae) {
if(!ae.isRow() && !ae.isColumn()) {
// extra complexity required to emulate the way LOOKUP can handles these abnormal cases.
throw new RuntimeException("non-vector lookup or result areas not supported yet");
}
return new SimpleValueVector(ae.getValues());
}
}

View File

@ -0,0 +1,530 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF 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.record.formula.functions;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH
*
* @author Josh Micich
*/
final class LookupUtils {
/**
* Represents a single row or column within an <tt>AreaEval</tt>.
*/
public interface ValueVector {
ValueEval getItem(int index);
int getSize();
}
/**
* Enumeration to support <b>4</b> valued comparison results.<p/>
* Excel lookup functions have complex behaviour in the case where the lookup array has mixed
* types, and/or is unordered. Contrary to suggestions in some Excel documentation, there
* does not appear to be a universal ordering across types. The binary search algorithm used
* changes behaviour when the evaluated 'mid' value has a different type to the lookup value.<p/>
*
* A simple int might have done the same job, but there is risk in confusion with the well
* known <tt>Comparable.compareTo()</tt> and <tt>Comparator.compare()</tt> which both use
* a ubiquitous 3 value result encoding.
*/
public static final class CompareResult {
private final boolean _isTypeMismatch;
private final boolean _isLessThan;
private final boolean _isEqual;
private final boolean _isGreaterThan;
private CompareResult(boolean isTypeMismatch, int simpleCompareResult) {
if(isTypeMismatch) {
_isTypeMismatch = true;
_isLessThan = false;
_isEqual = false;
_isGreaterThan = false;
} else {
_isTypeMismatch = false;
_isLessThan = simpleCompareResult < 0;
_isEqual = simpleCompareResult == 0;
_isGreaterThan = simpleCompareResult > 0;
}
}
public static final CompareResult TYPE_MISMATCH = new CompareResult(true, 0);
public static final CompareResult LESS_THAN = new CompareResult(false, -1);
public static final CompareResult EQUAL = new CompareResult(false, 0);
public static final CompareResult GREATER_THAN = new CompareResult(false, +1);
public static final CompareResult valueOf(int simpleCompareResult) {
if(simpleCompareResult < 0) {
return LESS_THAN;
}
if(simpleCompareResult > 0) {
return GREATER_THAN;
}
return EQUAL;
}
public boolean isTypeMismatch() {
return _isTypeMismatch;
}
public boolean isLessThan() {
return _isLessThan;
}
public boolean isEqual() {
return _isEqual;
}
public boolean isGreaterThan() {
return _isGreaterThan;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
private String formatAsString() {
if(_isTypeMismatch) {
return "TYPE_MISMATCH";
}
if(_isLessThan) {
return "LESS_THAN";
}
if(_isEqual) {
return "EQUAL";
}
if(_isGreaterThan) {
return "GREATER_THAN";
}
// toString must be reliable
return "??error??";
}
}
public interface LookupValueComparer {
/**
* @return one of 4 instances or <tt>CompareResult</tt>: <tt>LESS_THAN</tt>, <tt>EQUAL</tt>,
* <tt>GREATER_THAN</tt> or <tt>TYPE_MISMATCH</tt>
*/
CompareResult compareTo(ValueEval other);
}
private static abstract class LookupValueComparerBase implements LookupValueComparer {
private final Class _targetClass;
protected LookupValueComparerBase(ValueEval targetValue) {
if(targetValue == null) {
throw new RuntimeException("targetValue cannot be null");
}
_targetClass = targetValue.getClass();
}
public final CompareResult compareTo(ValueEval other) {
if (other == null) {
throw new RuntimeException("compare to value cannot be null");
}
if (_targetClass != other.getClass()) {
return CompareResult.TYPE_MISMATCH;
}
if (_targetClass == StringEval.class) {
}
return compareSameType(other);
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(getValueAsString());
sb.append("]");
return sb.toString();
}
protected abstract CompareResult compareSameType(ValueEval other);
/** used only for debug purposes */
protected abstract String getValueAsString();
}
private static final class StringLookupComparer extends LookupValueComparerBase {
private String _value;
protected StringLookupComparer(StringEval se) {
super(se);
_value = se.getStringValue();
}
protected CompareResult compareSameType(ValueEval other) {
StringEval se = (StringEval) other;
return CompareResult.valueOf(_value.compareToIgnoreCase(se.getStringValue()));
}
protected String getValueAsString() {
return _value;
}
}
private static final class NumberLookupComparer extends LookupValueComparerBase {
private double _value;
protected NumberLookupComparer(NumberEval ne) {
super(ne);
_value = ne.getNumberValue();
}
protected CompareResult compareSameType(ValueEval other) {
NumberEval ne = (NumberEval) other;
return CompareResult.valueOf(Double.compare(_value, ne.getNumberValue()));
}
protected String getValueAsString() {
return String.valueOf(_value);
}
}
private static final class BooleanLookupComparer extends LookupValueComparerBase {
private boolean _value;
protected BooleanLookupComparer(BoolEval be) {
super(be);
_value = be.getBooleanValue();
}
protected CompareResult compareSameType(ValueEval other) {
BoolEval be = (BoolEval) other;
boolean otherVal = be.getBooleanValue();
if(_value == otherVal) {
return CompareResult.EQUAL;
}
// TRUE > FALSE
if(_value) {
return CompareResult.GREATER_THAN;
}
return CompareResult.LESS_THAN;
}
protected String getValueAsString() {
return String.valueOf(_value);
}
}
/**
* Processes the third argument to VLOOKUP, or HLOOKUP (<b>col_index_num</b>
* or <b>row_index_num</b> respectively).<br>
* Sample behaviour:
* <table border="0" cellpadding="1" cellspacing="2" summary="Sample behaviour">
* <tr><th>Input&nbsp;&nbsp;&nbsp;Return</th><th>Value&nbsp;&nbsp;</th><th>Thrown Error</th></tr>
* <tr><td>5</td><td>4</td><td>&nbsp;</td></tr>
* <tr><td>2.9</td><td>2</td><td>&nbsp;</td></tr>
* <tr><td>"5"</td><td>4</td><td>&nbsp;</td></tr>
* <tr><td>"2.18e1"</td><td>21</td><td>&nbsp;</td></tr>
* <tr><td>"-$2"</td><td>-3</td><td>*</td></tr>
* <tr><td>FALSE</td><td>-1</td><td>*</td></tr>
* <tr><td>TRUE</td><td>0</td><td>&nbsp;</td></tr>
* <tr><td>"TRUE"</td><td>&nbsp;</td><td>#REF!</td></tr>
* <tr><td>"abc"</td><td>&nbsp;</td><td>#REF!</td></tr>
* <tr><td>""</td><td>&nbsp;</td><td>#REF!</td></tr>
* <tr><td>&lt;blank&gt;</td><td>&nbsp;</td><td>#VALUE!</td></tr>
* </table><br/>
*
* * Note - out of range errors (both too high and too low) are handled by the caller.
* @return column or row index as a zero-based value
*
*/
public static int resolveRowOrColIndexArg(ValueEval veRowColIndexArg) throws EvaluationException {
if(veRowColIndexArg == null) {
throw new IllegalArgumentException("argument must not be null");
}
if(veRowColIndexArg instanceof BlankEval) {
throw EvaluationException.invalidValue();
}
if(veRowColIndexArg instanceof StringEval) {
StringEval se = (StringEval) veRowColIndexArg;
String strVal = se.getStringValue();
Double dVal = OperandResolver.parseDouble(strVal);
if(dVal == null) {
// String does not resolve to a number. Raise #VALUE! error.
throw EvaluationException.invalidRef();
// This includes text booleans "TRUE" and "FALSE". They are not valid.
}
// else - numeric value parses OK
}
// actual BoolEval values get interpreted as FALSE->0 and TRUE->1
return OperandResolver.coerceValueToInt(veRowColIndexArg) - 1;
}
/**
* The second argument (table_array) should be an area ref, but can actually be a cell ref, in
* which case it is interpreted as a 1x1 area ref. Other scalar values cause #VALUE! error.
*/
public static AreaEval resolveTableArrayArg(Eval eval) throws EvaluationException {
if (eval instanceof AreaEval) {
return (AreaEval) eval;
}
if(eval instanceof RefEval) {
RefEval refEval = (RefEval) eval;
// Make this cell ref look like a 1x1 area ref.
// It doesn't matter if eval is a 2D or 3D ref, because that detail is never asked of AreaEval.
// This code only requires the value array item.
// anything would be ok for rowIx and colIx, but may as well get it right.
int rowIx = refEval.getRow();
int colIx = refEval.getColumn();
AreaPtg ap = new AreaPtg(rowIx, rowIx, colIx, colIx, false, false, false, false);
ValueEval value = refEval.getInnerValueEval();
return new Area2DEval(ap, new ValueEval[] { value, });
}
throw EvaluationException.invalidValue();
}
/**
* Resolves the last (optional) parameter (<b>range_lookup</b>) to the VLOOKUP and HLOOKUP functions.
* @param rangeLookupArg
* @param srcCellRow
* @param srcCellCol
* @return
* @throws EvaluationException
*/
public static boolean resolveRangeLookupArg(Eval rangeLookupArg, int srcCellRow, short srcCellCol) throws EvaluationException {
if(rangeLookupArg == null) {
// range_lookup arg not provided
return true; // default is TRUE
}
ValueEval valEval = OperandResolver.getSingleValue(rangeLookupArg, srcCellRow, srcCellCol);
if(valEval instanceof BlankEval) {
// Tricky:
// fourth arg supplied but evaluates to blank
// this does not get the default value
return false;
}
if(valEval instanceof BoolEval) {
// Happy day flow
BoolEval boolEval = (BoolEval) valEval;
return boolEval.getBooleanValue();
}
if (valEval instanceof StringEval) {
String stringValue = ((StringEval) valEval).getStringValue();
if(stringValue.length() < 1) {
// More trickiness:
// Empty string is not the same as BlankEval. It causes #VALUE! error
throw EvaluationException.invalidValue();
}
// TODO move parseBoolean to OperandResolver
Boolean b = Countif.parseBoolean(stringValue);
if(b != null) {
// string converted to boolean OK
return b.booleanValue();
}
// Even more trickiness:
// Note - even if the StringEval represents a number value (for example "1"),
// Excel does not resolve it to a boolean.
throw EvaluationException.invalidValue();
// This is in contrast to the code below,, where NumberEvals values (for
// example 0.01) *do* resolve to equivalent boolean values.
}
if (valEval instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) valEval;
// zero is FALSE, everything else is TRUE
return 0.0 != nve.getNumberValue();
}
throw new RuntimeException("Unexpected eval type (" + valEval.getClass().getName() + ")");
}
public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException {
LookupValueComparer lookupComparer = createLookupComparer(lookupValue);
int result;
if(isRangeLookup) {
result = performBinarySearch(vector, lookupComparer);
} else {
result = lookupIndexOfExactValue(lookupComparer, vector);
}
if(result < 0) {
throw new EvaluationException(ErrorEval.NA);
}
return result;
}
/**
* Finds first (lowest index) exact occurrence of specified value.
* @param lookupValue the value to be found in column or row vector
* @param vector the values to be searched. For VLOOKUP this is the first column of the
* tableArray. For HLOOKUP this is the first row of the tableArray.
* @return zero based index into the vector, -1 if value cannot be found
*/
private static int lookupIndexOfExactValue(LookupValueComparer lookupComparer, ValueVector vector) {
// find first occurrence of lookup value
int size = vector.getSize();
for (int i = 0; i < size; i++) {
if(lookupComparer.compareTo(vector.getItem(i)).isEqual()) {
return i;
}
}
return -1;
}
/**
* Encapsulates some standard binary search functionality so the unusual Excel behaviour can
* be clearly distinguished.
*/
private static final class BinarySearchIndexes {
private int _lowIx;
private int _highIx;
public BinarySearchIndexes(int highIx) {
_lowIx = -1;
_highIx = highIx;
}
/**
* @return -1 if the search range is empty
*/
public int getMidIx() {
int ixDiff = _highIx - _lowIx;
if(ixDiff < 2) {
return -1;
}
return _lowIx + (ixDiff / 2);
}
public int getLowIx() {
return _lowIx;
}
public int getHighIx() {
return _highIx;
}
public void narrowSearch(int midIx, boolean isLessThan) {
if(isLessThan) {
_highIx = midIx;
} else {
_lowIx = midIx;
}
}
}
/**
* Excel has funny behaviour when the some elements in the search vector are the wrong type.
*
*/
private static int performBinarySearch(ValueVector vector, LookupValueComparer lookupComparer) {
// both low and high indexes point to values assumed too low and too high.
BinarySearchIndexes bsi = new BinarySearchIndexes(vector.getSize());
while(true) {
int midIx = bsi.getMidIx();
if(midIx < 0) {
return bsi.getLowIx();
}
CompareResult cr = lookupComparer.compareTo(vector.getItem(midIx));
if(cr.isTypeMismatch()) {
int newMidIx = handleMidValueTypeMismatch(lookupComparer, vector, bsi, midIx);
if(newMidIx < 0) {
continue;
}
midIx = newMidIx;
cr = lookupComparer.compareTo(vector.getItem(midIx));
}
if(cr.isEqual()) {
return findLastIndexInRunOfEqualValues(lookupComparer, vector, midIx, bsi.getHighIx());
}
bsi.narrowSearch(midIx, cr.isLessThan());
}
}
/**
* Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the
* first compatible value.
* @param midIx 'mid' index (value which has the wrong type)
* @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid
* index. Zero or greater signifies that an exact match for the lookup value was found
*/
private static int handleMidValueTypeMismatch(LookupValueComparer lookupComparer, ValueVector vector,
BinarySearchIndexes bsi, int midIx) {
int newMid = midIx;
int highIx = bsi.getHighIx();
while(true) {
newMid++;
if(newMid == highIx) {
// every element from midIx to highIx was the wrong type
// move highIx down to the low end of the mid values
bsi.narrowSearch(midIx, true);
return -1;
}
CompareResult cr = lookupComparer.compareTo(vector.getItem(newMid));
if(cr.isLessThan() && newMid == highIx-1) {
// move highIx down to the low end of the mid values
bsi.narrowSearch(midIx, true);
return -1;
// but only when "newMid == highIx-1"? slightly weird.
// It would seem more efficient to always do this.
}
if(cr.isTypeMismatch()) {
// keep stepping over values until the right type is found
continue;
}
if(cr.isEqual()) {
return newMid;
}
// Note - if moving highIx down (due to lookup<vector[newMid]),
// this execution path only moves highIx it down as far as newMid, not midIx,
// which would be more efficient.
bsi.narrowSearch(newMid, cr.isLessThan());
return -1;
}
}
/**
* Once the binary search has found a single match, (V/H)LOOKUP steps one by one over subsequent
* values to choose the last matching item.
*/
private static int findLastIndexInRunOfEqualValues(LookupValueComparer lookupComparer, ValueVector vector,
int firstFoundIndex, int maxIx) {
for(int i=firstFoundIndex+1; i<maxIx; i++) {
if(!lookupComparer.compareTo(vector.getItem(i)).isEqual()) {
return i-1;
}
}
return maxIx - 1;
}
public static LookupValueComparer createLookupComparer(ValueEval lookupValue) throws EvaluationException {
if (lookupValue instanceof BlankEval) {
// blank eval can never be found in a lookup array
throw new EvaluationException(ErrorEval.NA);
}
if (lookupValue instanceof StringEval) {
return new StringLookupComparer((StringEval) lookupValue);
}
if (lookupValue instanceof NumberEval) {
return new NumberLookupComparer((NumberEval) lookupValue);
}
if (lookupValue instanceof BoolEval) {
return new BooleanLookupComparer((BoolEval) lookupValue);
}
throw new IllegalArgumentException("Bad lookup value type (" + lookupValue.getClass().getName() + ")");
}
}

View File

@ -14,12 +14,213 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
public class Match extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.LookupUtils.CompareResult;
import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueComparer;
/**
* Implementation for the MATCH() Excel function.<p/>
*
* <b>Syntax:</b><br/>
* <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/>
*
* Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified
* <b>lookup_value</b> is found.<p/>
*
* Specific matching behaviour can be modified with the optional <b>match_type</b> parameter.
*
* <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description">
* <tr><th>Value</th><th>Matching Behaviour</th></tr>
* <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value.
* The lookup_array must be in ascending <i>order</i>*.</td></tr>
* <tr><td>0</td><td>find the first value that is exactly equal to lookup_value.
* The lookup_array can be in any order.</td></tr>
* <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value.
* The lookup_array must be in descending <i>order</i>*.</td></tr>
* </table>
*
* * Note regarding <i>order</i> - For the <b>match_type</b> cases that require the lookup_array to
* be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed
* behaviour in Excel is to return the lowest index value for which every item after that index
* breaks the match rule.<br>
* The (ascending) sort order expected by MATCH() is:<br/>
* numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/>
* MATCH() ignores all elements in the lookup_array with a different type to the lookup_value.
* Type conversion of the lookup_array elements is never performed.
*
*
* @author Josh Micich
*/
public final class Match implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
double match_type = 1; // default
switch(args.length) {
case 3:
try {
match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
} catch (EvaluationException e) {
// Excel/MATCH() seems to have slightly abnormal handling of errors with
// the last parameter. Errors do not propagate up. Every error gets
// translated into #REF!
return ErrorEval.REF_INVALID;
}
case 2:
break;
default:
return ErrorEval.VALUE_INVALID;
}
boolean matchExact = match_type == 0;
// Note - Excel does not strictly require -1 and +1
boolean findLargestLessThanOrEqual = match_type > 0;
try {
ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
ValueEval[] lookupRange = evaluateLookupRange(args[1]);
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
return new NumberEval(index + 1); // +1 to convert to 1-based
} catch (EvaluationException e) {
return e.getErrorEval();
}
}
private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException {
if (eval instanceof RefEval) {
RefEval re = (RefEval) eval;
return new ValueEval[] { re.getInnerValueEval(), };
}
if (eval instanceof AreaEval) {
AreaEval ae = (AreaEval) eval;
if(!ae.isColumn() && !ae.isRow()) {
throw new EvaluationException(ErrorEval.NA);
}
return ae.getValues();
}
// Error handling for lookup_range arg is also unusual
if(eval instanceof NumericValueEval) {
throw new EvaluationException(ErrorEval.NA);
}
if (eval instanceof StringEval) {
StringEval se = (StringEval) eval;
Double d = OperandResolver.parseDouble(se.getStringValue());
if(d == null) {
// plain string
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
// else looks like a number
throw new EvaluationException(ErrorEval.NA);
}
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
}
private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol)
throws EvaluationException {
Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
if(match_type instanceof ErrorEval) {
throw new EvaluationException((ErrorEval)match_type);
}
if(match_type instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) match_type;
return ne.getNumberValue();
}
if (match_type instanceof StringEval) {
StringEval se = (StringEval) match_type;
Double d = OperandResolver.parseDouble(se.getStringValue());
if(d == null) {
// plain string
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
// if the string parses as a number, it is OK
return d.doubleValue();
}
throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
}
/**
* @return zero based index
*/
private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException {
LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact);
if(matchExact) {
for (int i = 0; i < lookupRange.length; i++) {
if(lookupComparer.compareTo(lookupRange[i]).isEqual()) {
return i;
}
}
throw new EvaluationException(ErrorEval.NA);
}
if(findLargestLessThanOrEqual) {
// Note - backward iteration
for (int i = lookupRange.length - 1; i>=0; i--) {
CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
if(cmp.isTypeMismatch()) {
continue;
}
if(!cmp.isLessThan()) {
return i;
}
}
throw new EvaluationException(ErrorEval.NA);
}
// else - find smallest greater than or equal to
// TODO - is binary search used for (match_type==+1) ?
for (int i = 0; i<lookupRange.length; i++) {
CompareResult cmp = lookupComparer.compareTo(lookupRange[i]);
if(cmp.isEqual()) {
return i;
}
if(cmp.isGreaterThan()) {
if(i<1) {
throw new EvaluationException(ErrorEval.NA);
}
return i-1;
}
}
throw new EvaluationException(ErrorEval.NA);
}
private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) throws EvaluationException {
if (matchExact && lookupValue instanceof StringEval) {
String stringValue = ((StringEval) lookupValue).getStringValue();
if(isLookupValueWild(stringValue)) {
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet");
}
}
return LookupUtils.createLookupComparer(lookupValue);
}
private static boolean isLookupValueWild(String stringValue) {
if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) {
return true;
}
return false;
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
@ -30,12 +27,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class Maxa extends MultiOperandNumericFunction {
public final class Maxa extends MultiOperandNumericFunction {
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED

View File

@ -1,99 +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.
*/
/*
* Created on May 15, 2005
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.StringValueEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* An implementation of the MID function:
* Returns a specific number of characters from a text string,
* starting at the position you specify, based on the number
* of characters you specify.
* An implementation of the MID function<br/> MID returns a specific number of
* characters from a text string, starting at the specified position.<p/>
*
* <b>Syntax<b>:<br/> <b>MID</b>(<b>text</b>, <b>start_num</b>,
* <b>num_chars</b>)<br/>
*
* @author Manda Wilson &lt; wilson at c bio dot msk cc dot org &gt;
*/
public class Mid extends TextFunction {
public class Mid implements Function {
/**
* Returns a specific number of characters from a text string,
* starting at the position you specify, based on the number
* of characters you specify.
* Returns a specific number of characters from a text string, starting at
* the position you specify, based on the number of characters you specify.
*
* @see org.apache.poi.hssf.record.formula.eval.Eval
*/
public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
Eval retval = null;
String str = null;
int startNum = 0;
int numChars = 0;
switch (operands.length) {
default:
retval = ErrorEval.VALUE_INVALID;
case 3:
// first operand is text string containing characters to extract
// second operand is position of first character to extract
// third operand is the number of characters to return
ValueEval firstveval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
ValueEval secondveval = singleOperandEvaluate(operands[1], srcCellRow, srcCellCol);
ValueEval thirdveval = singleOperandEvaluate(operands[2], srcCellRow, srcCellCol);
if (firstveval instanceof StringValueEval
&& secondveval instanceof NumericValueEval
&& thirdveval instanceof NumericValueEval) {
StringValueEval strEval = (StringValueEval) firstveval;
str = strEval.getStringValue();
NumericValueEval startNumEval = (NumericValueEval) secondveval;
// NOTE: it is safe to cast to int here
// because in Excel =MID("test", 1, 1.7) returns t
// so 1.7 must be truncated to 1
// and =MID("test", 1.9, 2) returns te
// so 1.9 must be truncated to 1
startNum = (int) startNumEval.getNumberValue();
NumericValueEval numCharsEval = (NumericValueEval) thirdveval;
numChars = (int) numCharsEval.getNumberValue();
} else {
retval = ErrorEval.VALUE_INVALID;
}
}
if (retval == null) {
if (startNum < 1 || numChars < 0) {
retval = ErrorEval.VALUE_INVALID;
} else if (startNum > str.length() || numChars == 0) {
retval = BlankEval.INSTANCE;
} else if (startNum + numChars > str.length()) {
retval = new StringEval(str.substring(startNum - 1));
} else {
retval = new StringEval(str.substring(startNum - 1, numChars));
}
}
return retval;
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
if (args.length != 3) {
return ErrorEval.VALUE_INVALID;
}
}
String text;
int startIx; // zero based
int numChars;
try {
ValueEval evText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
text = OperandResolver.coerceValueToString(evText);
int startCharNum = evaluateNumberArg(args[1], srcCellRow, srcCellCol);
numChars = evaluateNumberArg(args[2], srcCellRow, srcCellCol);
startIx = startCharNum - 1; // convert to zero-based
} catch (EvaluationException e) {
return e.getErrorEval();
}
int len = text.length();
if (startIx < 0) {
return ErrorEval.VALUE_INVALID;
}
if (numChars < 0) {
return ErrorEval.VALUE_INVALID;
}
if (numChars < 0 || startIx > len) {
return new StringEval("");
}
int endIx = startIx + numChars;
if (endIx > len) {
endIx = len;
}
String result = text.substring(startIx, endIx);
return new StringEval(result);
}
private static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
if (ev instanceof BlankEval) {
// Note - for start_num arg, blank causes error(#VALUE!),
// but for num_chars causes empty string to be returned.
return 0;
}
return OperandResolver.coerceValueToInt(ev);
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
@ -35,7 +32,6 @@ public class Mina extends MultiOperandNumericFunction {
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 22, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
@ -36,32 +33,52 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
* where the order of operands does not matter
*/
public abstract class MultiOperandNumericFunction extends NumericFunction {
static final double[] EMPTY_DOUBLE_ARRAY = { };
private static class DoubleList {
private double[] _array;
private int _count;
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE
//| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
));
public DoubleList() {
_array = new double[8];
_count = 0;
}
public double[] toArray() {
if(_count < 1) {
return EMPTY_DOUBLE_ARRAY;
}
double[] result = new double[_count];
System.arraycopy(_array, 0, result, 0, _count);
return result;
}
public void add(double[] values) {
int addLen = values.length;
ensureCapacity(_count + addLen);
System.arraycopy(values, 0, _array, _count, addLen);
_count += addLen;
}
private void ensureCapacity(int reqSize) {
if(reqSize > _array.length) {
int newSize = reqSize * 3 / 2; // grow with 50% extra
double[] newArr = new double[newSize];
System.arraycopy(_array, 0, newArr, 0, _count);
_array = newArr;
}
}
public void add(double value) {
ensureCapacity(_count + 1);
_array[_count] = value;
_count++;
}
}
private static final int DEFAULT_MAX_NUM_OPERANDS = 30;
/**
* this is the default impl for the factory method getXlator
* of the super class NumericFunction. Subclasses can override this method
* if they desire to return a different ValueEvalToNumericXlator instance
* than the default.
*/
protected ValueEvalToNumericXlator getXlator() {
return DEFAULT_NUM_XLATOR;
}
protected abstract ValueEvalToNumericXlator getXlator();
/**
* Maximum number of operands accepted by this function.
@ -76,40 +93,26 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
* from among the list of operands. Blanks and Blank equivalent cells
* are ignored. Error operands or cells containing operands of type
* that are considered invalid and would result in #VALUE! error in
* excel cause this function to return null.
* excel cause this function to return <code>null</code>.
*
* @param operands
* @param srcRow
* @param srcCol
*/
protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) {
double[] retval = new double[30];
int count = 0;
outer: do { // goto simulator loop
if (operands.length > getMaxNumOperands()) {
break outer;
}
else {
for (int i=0, iSize=operands.length; i<iSize; i++) {
double[] temp = getNumberArray(operands[i], srcRow, srcCol);
if (temp == null) {
retval = null; // error occurred.
break;
}
retval = putInArray(retval, count, temp);
count += temp.length;
}
}
} while (false); // end goto simulator loop
if (retval != null) {
double[] temp = retval;
retval = new double[count];
System.arraycopy(temp, 0, retval, 0, count);
if (operands.length > getMaxNumOperands()) {
return null;
}
DoubleList retval = new DoubleList();
return retval;
for (int i=0, iSize=operands.length; i<iSize; i++) {
double[] temp = getNumberArray(operands[i], srcRow, srcCol);
if (temp == null) {
return null; // error occurred.
}
retval.add(temp);
}
return retval.toArray();
}
/**
@ -120,13 +123,11 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
* @param srcCol
*/
protected double[] getNumberArray(Eval operand, int srcRow, short srcCol) {
double[] retval;
int count = 0;
if (operand instanceof AreaEval) {
AreaEval ae = (AreaEval) operand;
ValueEval[] values = ae.getValues();
retval = new double[values.length];
DoubleList retval = new DoubleList();
for (int j=0, jSize=values.length; j<jSize; j++) {
/*
* TODO: For an AreaEval, we are constructing a RefEval
@ -136,98 +137,66 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
* HSSFFormulaEvaluator where we store an array
* of RefEvals as the "values" array.
*/
RefEval re = (values[j] instanceof RefEval)
? new Ref2DEval(null, ((RefEval) values[j]).getInnerValueEval(), true)
: new Ref2DEval(null, values[j], false);
RefEval re = new Ref2DEval(null, values[j]);
ValueEval ve = singleOperandEvaluate(re, srcRow, srcCol);
if (ve instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) ve;
retval = putInArray(retval, count++, nve.getNumberValue());
retval.add(nve.getNumberValue());
}
else if (ve instanceof BlankEval) {} // ignore operand
else if (ve instanceof BlankEval) {
// note - blanks are ignored, so returned array will be smaller.
}
else {
retval = null; // null => indicate to calling subclass that error occurred
break;
return null; // indicate to calling subclass that error occurred
}
}
}
else { // for ValueEvals other than AreaEval
retval = new double[1];
ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
if (ve instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) ve;
retval = putInArray(retval, count++, nve.getNumberValue());
}
else if (ve instanceof BlankEval) {} // ignore operand
else {
retval = null; // null => indicate to calling subclass that error occurred
}
return retval.toArray();
}
if (retval != null && retval.length >= 1) {
double[] temp = retval;
retval = new double[count];
System.arraycopy(temp, 0, retval, 0, count);
// for ValueEvals other than AreaEval
ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
if (ve instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) ve;
return new double[] { nve.getNumberValue(), };
}
return retval;
if (ve instanceof BlankEval) {
// ignore blanks
return EMPTY_DOUBLE_ARRAY;
}
return null;
}
/**
* puts d at position pos in array arr. If pos is greater than arr, the
* array is dynamically resized (using a simple doubling rule).
* @param arr
* @param pos
* @param d
* Ensures that a two dimensional array has all sub-arrays present and the same length
* @return <code>false</code> if any sub-array is missing, or is of different length
*/
private static double[] putInArray(double[] arr, int pos, double d) {
double[] tarr = arr;
while (pos >= arr.length) {
arr = new double[arr.length << 1];
}
if (tarr.length != arr.length) {
System.arraycopy(tarr, 0, arr, 0, tarr.length);
}
arr[pos] = d;
return arr;
}
private static double[] putInArray(double[] arr, int pos, double[] d) {
double[] tarr = arr;
while (pos+d.length >= arr.length) {
arr = new double[arr.length << 1];
}
if (tarr.length != arr.length) {
System.arraycopy(tarr, 0, arr, 0, tarr.length);
}
for (int i=0, iSize=d.length; i<iSize; i++) {
arr[pos+i] = d[i];
}
return arr;
}
protected static boolean areSubArraysConsistent(double[][] values) {
boolean retval = false;
protected static final boolean areSubArraysConsistent(double[][] values) {
outer: do {
if (values != null && values.length > 0) {
if (values[0] == null)
break outer;
int len = values[0].length;
for (int i=1, iSize=values.length; i<iSize; i++) {
if (values[i] == null)
break outer;
int tlen = values[i].length;
if (len != tlen) {
break outer;
}
}
if (values == null || values.length < 1) {
// TODO this doesn't seem right. Fix or add comment.
return true;
}
if (values[0] == null) {
return false;
}
int outerMax = values.length;
int innerMax = values[0].length;
for (int i=1; i<outerMax; i++) { // note - 'i=1' start at second sub-array
double[] subArr = values[i];
if (subArr == null) {
return false;
}
retval = true;
} while (false);
return retval;
if (innerMax != subArr.length) {
return false;
}
}
return true;
}
}

View File

@ -14,12 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
public class Na extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
/**
* Implementation of Excel function NA()
*
* @author Josh Micich
*/
public final class Na implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
return ErrorEval.NA;
}
}

View File

@ -39,10 +39,8 @@ public abstract class NumericFunction implements Function {
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
| ValueEvalToNumericXlator.STRING_IS_PARSED
| ValueEvalToNumericXlator.REF_STRING_IS_PARSED
| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
//| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED
//| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE

Some files were not shown because too many files have changed in this diff Show More