diff options
author | Nick Burch <nick@apache.org> | 2008-08-29 15:27:07 +0000 |
---|---|---|
committer | Nick Burch <nick@apache.org> | 2008-08-29 15:27:07 +0000 |
commit | add15697500f68770362f26ee5380397b8e8ddca (patch) | |
tree | 9097102db8c8bcbc3f4d0e4c4e7724bbb55c4e35 /src/java/org/apache/poi/hssf | |
parent | 9958668438ee7553982d44f9f9f7f07b89c51f70 (diff) | |
download | poi-add15697500f68770362f26ee5380397b8e8ddca.tar.gz poi-add15697500f68770362f26ee5380397b8e8ddca.zip |
Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-639241,639243-639253,639255-639486,639488-639601,639603-639835,639837-639917,639919-640056,640058-640710,640712-641156,641158-641184,641186-641795,641797-641798,641800-641933,641935-641963,641965-641966,641968-641995,641997-642230,642232-642562,642564-642565,642568-642570,642572-642573,642576-642736,642739-642877,642879,642881-642890,642892-642903,642905-642945,642947-643624,643626-643653,643655-643669,643671,643673-643830,643832-643833,643835-644342,644344-644472,644474-644508,644510-645347,645349-645351,645353-645559,645561-645565,645568-645951,645953-646193,646195-646311,646313-646404,646406-646665,646667-646853,646855-646869,646871-647151,647153-647185,647187-647277,647279-647566,647568-647573,647575,647578-647711,647714-647737,647739-647823,647825-648155,648157-648202,648204-648273,648275,648277-648302,648304-648333,648335-648588,648590-648622,648625-648673,648675-649141,649144,649146-649556,649558-649795,649799,649801-649910,649912-649913,649915-650128,650131-650132,650134-650137,650140-650914,650916-651991,651993-652284,652286-652287,652289,652291,652293-652297,652299-652328,652330-652425,652427-652445,652447-652560,652562-652933,652935,652937-652993,652995-653116,653118-653124,653126-653483,653487-653519,653522-653550,653552-653607,653609-653667,653669-653674,653676-653814,653817-653830,653832-653891,653893-653944,653946-654055,654057-654355,654357-654365,654367-654648,654651-655215,655217-655277,655279-655281,655283-655911,655913-656212,656214,656216-656251,656253-656698,656700-656756,656758-656892,656894-657135,657137-657165,657168-657179,657181-657354,657356-657357,657359-657701,657703-657874,657876-658032,658034-658284,658286,658288-658301,658303-658307,658309-658321,658323-658335,658337-658348,658351,658353-658832,658834-658983,658985,658987-659066,659068-659402,659404-659428,659430-659451,659453-659454,659456-659461,659463-659477,659479-659524,659526-659571,659574,659576-660255,660257-660262,660264-660279,660281-660343,660345-660473,660475-660827,660829-660833,660835-660888,660890-663321,663323-663435,663437-663764,663766-663854,663856-664219,664221-664489,664494-664514,664516-668013,668015-668142,668144-668152,668154,668156-668256,668258,668260-669139,669141-669455,669457-669657,669659-669808,669810-670189,670191-671321,671323-672229,672231-672549,672551-672552,672554-672561,672563-672566,672568,672571-673049,673051-673852,673854-673862,673864-673986,673988-673996,673998-674347,674349-674890,674892-674910,674912-674936,674938-674952,674954-675078,675080-675085,675087-675217,675219-675660,675662-675670,675672-675716,675718-675726,675728-675733,675735-675775,675777-675782,675784,675786-675791,675794-675852,675854-676200,676202,676204,676206-676220,676222-676309,676311-676456,676458-676994,676996-677027,677030-677040,677042-677056,677058-677375,677377-677968,677970-677971,677973,677975-677994,677996-678286,678288-678538,678540-680393,680395-680469,680471-680529,680531-680852,680854-681529,681531-681571,681573-682224,682226,682228,682231-682281,682283-682335,682337-682507,682509,682512-682517,682519-682532,682534-682619,682622-682777,682779-682998,683000-683019,683021-683022,683024-683080,683082-683092,683094-683095,683097-683127,683129-683131,683133-683166,683168-683698,683700-683705,683707-683757,683759-683787,683789-683870,683872-683879,683881-683900,683902-684066,684068-684074,684076-684222,684224-684254,684257-684281,684283-684286,684288-684292,684294-684298,684300-684301,684303-684308,684310-684317,684320,684323-684335,684337-684348,684350-684354,684356-684361,684363-684369,684371-684453,684455-684883,684885-684937,684940-684958,684960-684970,684972-684985,684987-685053,685055-685063,685065-685259,685261-685262,685264-685266,685268-685282,685285-686035,686037-686045,686047-686052,686054-686206,686208-686215,686217-686277,686279-686289,686291-686620,686622-686623,686626-686627,686629-686639,686641-686843,686845-686976,686978-687402,687404-687422,687424-687428,687430-687442,687444-688425,688427-688641,688643-688649,688651-688654,688656-688824,688826-688909,688911-689543,689545-689558,689560-689635,689637-689703,689705-689715,689717-689718,689720,689722-690280 via svnmerge from
https://svn.apache.org/repos/asf/poi/trunk
........
r689973 | josh | 2008-08-28 21:39:41 +0100 (Thu, 28 Aug 2008) | 1 line
Consolidated TableRecord inside FormulaRecordAggregate. Simplifications to FormulaRecord
........
r690091 | josh | 2008-08-29 04:25:23 +0100 (Fri, 29 Aug 2008) | 1 line
Changed FormulaRecord.getParsedExpression to return Ptg array
........
r690094 | josh | 2008-08-29 04:52:51 +0100 (Fri, 29 Aug 2008) | 1 line
Removing calls to AreaEval.getValues()
........
r690112 | josh | 2008-08-29 06:29:56 +0100 (Fri, 29 Aug 2008) | 1 line
Removing calls to AreaEval.getValues() from count and lookup functions
........
r690259 | yegor | 2008-08-29 14:58:56 +0100 (Fri, 29 Aug 2008) | 1 line
utility to dump POIFS into filesystem
........
r690262 | yegor | 2008-08-29 15:01:04 +0100 (Fri, 29 Aug 2008) | 1 line
initial support for embedded movies and controls.
........
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@690299 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/poi/hssf')
28 files changed, 1059 insertions, 1340 deletions
diff --git a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java index 91d342099b..36e527ae5b 100644 --- a/src/java/org/apache/poi/hssf/dev/FormulaViewer.java +++ b/src/java/org/apache/poi/hssf/dev/FormulaViewer.java @@ -15,25 +15,21 @@ limitations under the License. ==================================================================== */ - -/* - * FormulaViewer.java - finds formulas in a BIFF8 file and attempts to parse them and - * display info about them. - * - * Created on November 18, 2001, 7:58 AM - */ package org.apache.poi.hssf.dev; import java.io.FileInputStream; - -//import java.io.*; import java.util.List; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.hssf.record.*; -import org.apache.poi.hssf.record.formula.*; +import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordFactory; +import org.apache.poi.hssf.record.formula.ExpPtg; +import org.apache.poi.hssf.record.formula.FuncPtg; +import org.apache.poi.hssf.record.formula.OperationPtg; +import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.hssf.model.*; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** * FormulaViewer - finds formulas in a BIFF8 file and attempts to read them/display @@ -87,20 +83,21 @@ public class FormulaViewer private void listFormula(FormulaRecord record) { String sep="~"; - List tokens= record.getParsedExpression(); - int numptgs = record.getNumberOfExpressionTokens(); - Ptg token = null; - String name,numArg; - if (tokens != null) { - token = (Ptg) tokens.get(numptgs-1); + Ptg[] tokens= record.getParsedExpression(); + Ptg token; + int numptgs = tokens.length; + String numArg; + token = tokens[numptgs-1]; if (token instanceof FuncPtg) { numArg = String.valueOf(numptgs-1); - } else { numArg = String.valueOf(-1);} + } else { + numArg = String.valueOf(-1); + } StringBuffer buf = new StringBuffer(); if (token instanceof ExpPtg) return; - buf.append(name=((OperationPtg) token).toFormulaString((HSSFWorkbook)null)); + buf.append(((OperationPtg) token).toFormulaString((HSSFWorkbook)null)); buf.append(sep); switch (token.getPtgClass()) { case Ptg.CLASS_REF : @@ -116,7 +113,7 @@ public class FormulaViewer buf.append(sep); if (numptgs>1) { - token = (Ptg) tokens.get(numptgs-2); + token = tokens[numptgs-2]; switch (token.getPtgClass()) { case Ptg.CLASS_REF : buf.append("REF"); @@ -134,9 +131,6 @@ public class FormulaViewer buf.append(sep); buf.append(numArg); System.out.println(buf.toString()); - } else { - System.out.println("#NAME"); - } } /** @@ -155,20 +149,18 @@ public class FormulaViewer System.out.println("value = " + record.getValue()); System.out.print("xf = " + record.getXFIndex()); System.out.print(", number of ptgs = " - + record.getNumberOfExpressionTokens()); + + record.getParsedExpression().length); System.out.println(", options = " + record.getOptions()); System.out.println("RPN List = "+formulaString(record)); System.out.println("Formula text = "+ composeFormula(record)); } private String formulaString(FormulaRecord record) { - StringBuffer formula = new StringBuffer("="); - int numptgs = record.getNumberOfExpressionTokens(); - List tokens = record.getParsedExpression(); - Ptg token; + StringBuffer buf = new StringBuffer(); - for (int i=0;i<numptgs;i++) { - token = (Ptg) tokens.get(i); + Ptg[] tokens = record.getParsedExpression(); + for (int i = 0; i < tokens.length; i++) { + Ptg token = tokens[i]; buf.append( token.toFormulaString((HSSFWorkbook)null)); switch (token.getPtgClass()) { case Ptg.CLASS_REF : @@ -187,9 +179,9 @@ public class FormulaViewer } - private String composeFormula(FormulaRecord record) + private static String composeFormula(FormulaRecord record) { - return org.apache.poi.hssf.model.FormulaParser.toFormulaString((HSSFWorkbook)null,record.getParsedExpression()); + return FormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression()); } /** diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java index 1a06873954..8869a9cf03 100755 --- a/src/java/org/apache/poi/hssf/model/RecordStream.java +++ b/src/java/org/apache/poi/hssf/model/RecordStream.java @@ -30,19 +30,28 @@ public final class RecordStream { private final List _list; private int _nextIndex; private int _countRead; + private final int _endIx; - public RecordStream(List inputList, int startIndex) { + /** + * Creates a RecordStream bounded by startIndex and endIndex + */ + public RecordStream(List inputList, int startIndex, int endIx) { _list = inputList; _nextIndex = startIndex; + _endIx = endIx; _countRead = 0; } + public RecordStream(List records, int startIx) { + this(records, startIx, records.size()); + } + public boolean hasNext() { - return _nextIndex < _list.size(); + return _nextIndex < _endIx; } public Record getNext() { - if(_nextIndex >= _list.size()) { + if(!hasNext()) { throw new RuntimeException("Attempt to read past end of record stream"); } _countRead ++; @@ -53,14 +62,17 @@ public final class RecordStream { * @return the {@link Class} of the next Record. <code>null</code> if this stream is exhausted. */ public Class peekNextClass() { - if(_nextIndex >= _list.size()) { + if(!hasNext()) { return null; } return _list.get(_nextIndex).getClass(); } + /** + * @return -1 if at end of records + */ public int peekNextSid() { - if(_nextIndex >= _list.size()) { + if(!hasNext()) { return -1; } return ((Record)_list.get(_nextIndex)).getSid(); diff --git a/src/java/org/apache/poi/hssf/record/FormulaRecord.java b/src/java/org/apache/poi/hssf/record/FormulaRecord.java index 9f6bb45866..4cf7d95a3e 100644 --- a/src/java/org/apache/poi/hssf/record/FormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/FormulaRecord.java @@ -17,12 +17,13 @@ package org.apache.poi.hssf.record; +import java.util.Arrays; import java.util.List; -import java.util.Stack; import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; /** @@ -33,69 +34,60 @@ import org.apache.poi.util.LittleEndian; * @version 2.0-pre */ public final class FormulaRecord extends Record implements CellValueRecordInterface { - + public static final short sid = 0x0006; // docs say 406...because of a bug Microsoft support site article #Q184647) + private static int FIXED_SIZE = 22; private static final BitField alwaysCalc = BitFieldFactory.getInstance(0x0001); private static final BitField calcOnLoad = BitFieldFactory.getInstance(0x0002); - private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008); + private static final BitField sharedFormula = BitFieldFactory.getInstance(0x0008); - private int field_1_row; + private int field_1_row; private short field_2_column; private short field_3_xf; private double field_4_value; private short field_5_options; private int field_6_zero; - private short field_7_expression_len; - private Stack field_8_parsed_expr; - + private Ptg[] field_8_parsed_expr; + /** * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly */ - private byte[] value_data; - private byte[] all_data; //if formula support is not enabled then - //we'll just store/reserialize + private byte[] value_data; /** Creates new FormulaRecord */ - public FormulaRecord() - { - field_8_parsed_expr = new Stack(); + public FormulaRecord() { + field_8_parsed_expr = Ptg.EMPTY_PTG_ARRAY; } /** * Constructs a Formula record and sets its fields appropriately. - * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an + * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an * "explanation of this bug in the documentation) or an exception * will be throw upon validation * * @param in the RecordInputstream to read the record from */ - public FormulaRecord(RecordInputStream in) - { + public FormulaRecord(RecordInputStream in) { super(in); } - protected void fillFields(RecordInputStream in) - { - try { + protected void fillFields(RecordInputStream in) { field_1_row = in.readUShort(); field_2_column = in.readShort(); field_3_xf = in.readShort(); field_4_value = in.readDouble(); field_5_options = in.readShort(); - + if (Double.isNaN(field_4_value)) { value_data = in.getNANData(); } - + field_6_zero = in.readInt(); - field_7_expression_len = in.readShort(); - field_8_parsed_expr = Ptg.createParsedExpressionTokens(field_7_expression_len, in); - } catch (java.lang.UnsupportedOperationException uoe) { - throw new RecordFormatException(uoe); - } + int field_7_expression_len = in.readShort(); // this length does not include any extra array data + field_8_parsed_expr = Ptg.readTokens(field_7_expression_len, in); if (in.remaining() == 10) { // TODO - this seems to occur when IntersectionPtg is present // 10 extra bytes are just 0x01 and 0x00 @@ -103,19 +95,15 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf } } - //public void setRow(short row) - public void setRow(int row) - { + public void setRow(int row) { field_1_row = row; } - public void setColumn(short column) - { + public void setColumn(short column) { field_2_column = column; } - public void setXFIndex(short xf) - { + public void setXFIndex(short xf) { field_3_xf = xf; } @@ -124,9 +112,7 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @param value calculated value */ - - public void setValue(double value) - { + public void setValue(double value) { field_4_value = value; } @@ -135,35 +121,19 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @param options bitmask */ - - public void setOptions(short options) - { + public void setOptions(short options) { field_5_options = options; } - /** - * set the length (in number of tokens) of the expression - * @param len length - */ - - public void setExpressionLength(short len) - { - field_7_expression_len = len; - } - - //public short getRow() - public int getRow() - { + public int getRow() { return field_1_row; } - public short getColumn() - { + public short getColumn() { return field_2_column; } - public short getXFIndex() - { + public short getXFIndex() { return field_3_xf; } @@ -172,8 +142,7 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @return calculated value */ - public double getValue() - { + public double getValue() { return field_4_value; } @@ -182,108 +151,43 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @return bitmask */ - public short getOptions() - { + public short getOptions() { return field_5_options; - } - + } + public boolean isSharedFormula() { return sharedFormula.isSet(field_5_options); } public void setSharedFormula(boolean flag) { - field_5_options = - sharedFormula.setShortBoolean(field_5_options, flag); + field_5_options = + sharedFormula.setShortBoolean(field_5_options, flag); } - + public boolean isAlwaysCalc() { - return alwaysCalc.isSet(field_5_options); + return alwaysCalc.isSet(field_5_options); } public void setAlwaysCalc(boolean flag) { - field_5_options = - alwaysCalc.setShortBoolean(field_5_options, flag); + field_5_options = + alwaysCalc.setShortBoolean(field_5_options, flag); } - + public boolean isCalcOnLoad() { - return calcOnLoad.isSet(field_5_options); + return calcOnLoad.isSet(field_5_options); } public void setCalcOnLoad(boolean flag) { - field_5_options = - calcOnLoad.setShortBoolean(field_5_options, flag); - } - - /** - * get the length (in number of tokens) of the expression - * @return expression length - */ - - public short getExpressionLength() - { - return field_7_expression_len; + field_5_options = + calcOnLoad.setShortBoolean(field_5_options, flag); } /** - * push a token onto the stack - * - * @param ptg the token - */ - - public void pushExpressionToken(Ptg ptg) - { - field_8_parsed_expr.push(ptg); - } - - /** - * pop a token off of the stack - * - * @return Ptg - the token + * @return the formula tokens. never <code>null</code> */ - - public Ptg popExpressionToken() - { - return ( Ptg ) field_8_parsed_expr.pop(); + public Ptg[] getParsedExpression() { + return (Ptg[]) field_8_parsed_expr.clone(); } - /** - * peek at the token on the top of stack - * - * @return Ptg - the token - */ - - public Ptg peekExpressionToken() - { - return ( Ptg ) field_8_parsed_expr.peek(); - } - - /** - * get the size of the stack - * @return size of the stack - */ - - public int getNumberOfExpressionTokens() - { - if (this.field_8_parsed_expr == null) { - return 0; - } else { - return field_8_parsed_expr.size(); - } - } - - /** - * get the stack as a list - * - * @return list of tokens (casts stack to a list and returns it!) - * this method can return null is we are unable to create Ptgs from - * existing excel file - * callers should check for null! - */ - - public List getParsedExpression() - { - return field_8_parsed_expr; - } - - public void setParsedExpression(Stack ptgs) { - field_8_parsed_expr = ptgs; + public void setParsedExpression(Ptg[] ptgs) { + field_8_parsed_expr = ptgs; } /** @@ -292,156 +196,86 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf * * @param id alleged id for this record */ - - protected void validateSid(short id) - { - if (id != sid) - { + protected void validateSid(short id) { + if (id != sid) { throw new RecordFormatException("NOT A FORMULA RECORD"); } } - public short getSid() - { + public short getSid() { return sid; } - /** - * 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. - * - * @return byte array containing instance data - */ + private int getDataSize() { + return FIXED_SIZE + Ptg.getEncodedSize(field_8_parsed_expr); + } + public int serialize(int offset, byte [] data) { - public int serialize(int offset, byte [] data) - { - if (this.field_8_parsed_expr != null) { - int ptgSize = getTotalPtgSize(); + int dataSize = getDataSize(); LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, ( short ) (22 + ptgSize)); - //LittleEndian.putShort(data, 4 + offset, getRow()); - LittleEndian.putShort(data, 4 + offset, ( short ) getRow()); + LittleEndian.putUShort(data, 2 + offset, dataSize); + LittleEndian.putUShort(data, 4 + offset, getRow()); LittleEndian.putShort(data, 6 + offset, getColumn()); LittleEndian.putShort(data, 8 + offset, getXFIndex()); - + //only reserialize if the value is still NaN and we have old nan data - if (Double.isNaN(this.getValue()) && value_data != null) { - System.arraycopy(value_data,0,data,10 + offset,value_data.length); + if (Double.isNaN(getValue()) && value_data != null) { + System.arraycopy(value_data,0,data,10 + offset,value_data.length); } else { - LittleEndian.putDouble(data, 10 + offset, field_4_value); + LittleEndian.putDouble(data, 10 + offset, field_4_value); } - + LittleEndian.putShort(data, 18 + offset, getOptions()); - + //when writing the chn field (offset 20), it's supposed to be 0 but ignored on read //Microsoft Excel Developer's Kit Page 318 LittleEndian.putInt(data, 20 + offset, 0); - LittleEndian.putShort(data, 24 + offset, getExpressionLength()); - Ptg.serializePtgStack(field_8_parsed_expr, data, 26+offset); - } else { - System.arraycopy(all_data,0,data,offset,all_data.length); - } - return getRecordSize(); + int formulaTokensSize = Ptg.getEncodedSizeWithoutArrayData(field_8_parsed_expr); + LittleEndian.putUShort(data, 24 + offset, formulaTokensSize); + Ptg.serializePtgs(field_8_parsed_expr, data, 26+offset); + return 4 + dataSize; } - - - - - public int getRecordSize() - { - int retval =0; - - if (this.field_8_parsed_expr != null) { - retval = getTotalPtgSize() + 26; - } else { - retval =all_data.length; - } - return retval; - // return getTotalPtgSize() + 28; - } - - private int getTotalPtgSize() - { - List list = getParsedExpression(); - int retval = 0; - - for (int k = 0; k < list.size(); k++) - { - Ptg ptg = ( Ptg ) list.get(k); - - retval += ptg.getSize(); - } - return retval; + public int getRecordSize() { + return 4 + getDataSize(); } - public boolean isInValueSection() - { + public boolean isInValueSection() { return true; } - public boolean isValue() - { + public boolean isValue() { return true; } - - public String toString() - { - StringBuffer buffer = new StringBuffer(); - buffer.append("[FORMULA]\n"); - buffer.append(" .row = ") - .append(Integer.toHexString(getRow())).append("\n"); - buffer.append(" .column = ") - .append(Integer.toHexString(getColumn())) - .append("\n"); - buffer.append(" .xf = ") - .append(Integer.toHexString(getXFIndex())).append("\n"); - if (Double.isNaN(this.getValue()) && value_data != null) - buffer.append(" .value (NaN) = ") - .append(org.apache.poi.util.HexDump.dump(value_data,0,0)) - .append("\n"); - else - buffer.append(" .value = ").append(getValue()) - .append("\n"); - buffer.append(" .options = ").append(getOptions()) - .append("\n"); - buffer.append(" .alwaysCalc = ").append(alwaysCalc.isSet(getOptions())) - .append("\n"); - buffer.append(" .calcOnLoad = ").append(calcOnLoad.isSet(getOptions())) - .append("\n"); - buffer.append(" .sharedFormula = ").append(sharedFormula.isSet(getOptions())) - .append("\n"); - buffer.append(" .zero = ").append(field_6_zero) - .append("\n"); - buffer.append(" .expressionlength= ").append(getExpressionLength()) - .append("\n"); - - if (field_8_parsed_expr != null) { - buffer.append(" .numptgsinarray = ").append(field_8_parsed_expr.size()) - .append("\n"); - - - for (int k = 0; k < field_8_parsed_expr.size(); k++ ) { - buffer.append(" Ptg(") - .append(k) - .append(")=") - .append(field_8_parsed_expr.get(k).toString()) - .append("\n") - .append(((Ptg)field_8_parsed_expr.get(k)).toDebugString()) - .append("\n"); - } - }else { - buffer.append("Formula full data \n") - .append(org.apache.poi.util.HexDump.dump(this.all_data,0,0)); - } - - - buffer.append("[/FORMULA]\n"); - return buffer.toString(); + + public String toString() { + + StringBuffer sb = new StringBuffer(); + sb.append("[FORMULA]\n"); + sb.append(" .row = ").append(HexDump.shortToHex(getRow())).append("\n"); + sb.append(" .column = ").append(HexDump.shortToHex(getColumn())).append("\n"); + sb.append(" .xf = ").append(HexDump.shortToHex(getXFIndex())).append("\n"); + sb.append(" .value = "); + if (Double.isNaN(this.getValue()) && value_data != null) { + sb.append("(NaN)").append(HexDump.dump(value_data,0,0)).append("\n"); + } else { + sb.append(getValue()).append("\n"); + } + sb.append(" .options = ").append(HexDump.shortToHex(getOptions())).append("\n"); + sb.append(" .alwaysCalc= ").append(alwaysCalc.isSet(getOptions())).append("\n"); + sb.append(" .calcOnLoad= ").append(calcOnLoad.isSet(getOptions())).append("\n"); + sb.append(" .shared = ").append(sharedFormula.isSet(getOptions())).append("\n"); + sb.append(" .zero = ").append(HexDump.intToHex(field_6_zero)).append("\n"); + + for (int k = 0; k < field_8_parsed_expr.length; k++ ) { + sb.append(" Ptg[").append(k).append("]="); + sb.append(field_8_parsed_expr[k].toString()).append("\n"); + } + sb.append("[/FORMULA]\n"); + return sb.toString(); } - + public Object clone() { FormulaRecord rec = new FormulaRecord(); rec.field_1_row = field_1_row; @@ -450,18 +284,14 @@ public final class FormulaRecord extends Record implements CellValueRecordInterf rec.field_4_value = field_4_value; rec.field_5_options = field_5_options; rec.field_6_zero = field_6_zero; - rec.field_7_expression_len = field_7_expression_len; - rec.field_8_parsed_expr = new Stack(); - int size = 0; - if (field_8_parsed_expr != null) - size = field_8_parsed_expr.size(); - for (int i=0; i< size; i++) { - Ptg ptg = ((Ptg)field_8_parsed_expr.get(i)).copy(); - rec.field_8_parsed_expr.add(i, ptg); + int nTokens = field_8_parsed_expr.length; + Ptg[] ptgs = new Ptg[nTokens]; + for (int i=0; i< nTokens; i++) { + ptgs[i] = field_8_parsed_expr[i].copy(); } + rec.field_8_parsed_expr = ptgs; rec.value_data = value_data; - rec.all_data = all_data; return rec; } - } + diff --git a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java index a8aeed0dae..3f3a047e64 100755 --- a/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java +++ b/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java @@ -14,17 +14,22 @@ 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.*; +import org.apache.poi.hssf.record.formula.AreaNPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.RefNPtg; +import org.apache.poi.hssf.record.formula.RefPtg; /** * Title: SharedFormulaRecord * Description: Primarily used as an excel optimization so that multiple similar formulas - * are not written out too many times. We should recognize this record and + * are not written out too many times. We should recognize this record and * serialize as is since this is used when reading templates. * <p> * Note: the documentation says that the SID is BC where biffviewer reports 4BC. The hex dump shows @@ -33,15 +38,15 @@ import org.apache.poi.hssf.record.formula.*; * @author Danny Mui at apache dot org */ public final class SharedFormulaRecord extends Record { - public final static short sid = 0x4BC; - + public final static short sid = 0x04BC; + private int field_1_first_row; private int field_2_last_row; private short field_3_first_column; private short field_4_last_column; private int field_5_reserved; private short field_6_expression_len; - private Stack field_7_parsed_expr; + private Stack field_7_parsed_expr; public SharedFormulaRecord() { @@ -55,15 +60,15 @@ public final class SharedFormulaRecord extends Record { { super(in); } - + protected void validateSid(short id) { if (id != this.sid) { throw new RecordFormatException("Not a valid SharedFormula"); - } - } - + } + } + public int getFirstRow() { return field_1_first_row; } @@ -139,7 +144,7 @@ public final class SharedFormulaRecord extends Record { .append(field_7_parsed_expr.get(k).toString()) .append("\n"); } - + buffer.append("[/SHARED FORMULA RECORD]\n"); return buffer.toString(); } @@ -163,7 +168,7 @@ public final class SharedFormulaRecord extends Record { private Stack getParsedExpressionTokens(RecordInputStream in) { Stack stack = new Stack(); - + while (in.remaining() != 0) { Ptg ptg = Ptg.createPtg(in); stack.push(ptg); @@ -180,15 +185,15 @@ public final class SharedFormulaRecord extends Record { return ((getFirstRow() <= formulaRow) && (getLastRow() >= formulaRow) && (getFirstColumn() <= formulaColumn) && (getLastColumn() >= formulaColumn)); } - + /** - * Creates a non shared formula from the shared formula + * Creates a non shared formula from the shared formula * counter part */ protected static Stack convertSharedFormulas(Stack ptgs, int formulaRow, int formulaColumn) { if(false) { /* - * TODO - (May-2008) Stop converting relative ref Ptgs in shared formula records. + * TODO - (May-2008) Stop converting relative ref Ptgs in shared formula records. * If/when POI writes out the workbook, this conversion makes an unnecessary diff in the BIFF records. * Disabling this code breaks one existing junit. * Some fix-up will be required to make Ptg.toFormulaString(HSSFWorkbook) work properly. @@ -225,31 +230,30 @@ public final class SharedFormulaRecord extends Record { if (!ptg.isBaseToken()) { ptg.setClass(originalOperandClass); } - + newPtgStack.add(ptg); } return newPtgStack; } - /** - * Creates a non shared formula from the shared formula + /** + * Creates a non shared formula from the shared formula * counter part */ public void convertSharedFormulaRecord(FormulaRecord formula) { //Sanity checks - final int formulaRow = formula.getRow(); - final int formulaColumn = formula.getColumn(); - if (isFormulaInShared(formula)) { - formula.setExpressionLength(getExpressionLength()); - - Stack newPtgStack = - convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); - formula.setParsedExpression(newPtgStack); + if (!isFormulaInShared(formula)) { + throw new RuntimeException("Shared Formula Conversion: Coding Error"); + } + final int formulaRow = formula.getRow(); + final int formulaColumn = formula.getColumn(); + + List ptgList = convertSharedFormulas(field_7_parsed_expr, formulaRow, formulaColumn); + Ptg[] ptgs = new Ptg[ptgList.size()]; + ptgList.toArray(ptgs); + formula.setParsedExpression(ptgs); //Now its not shared! formula.setSharedFormula(false); - } else { - throw new RuntimeException("Shared Formula Conversion: Coding Error"); - } } private static int fixupRelativeColumn(int currentcolumn, int column, boolean relative) { diff --git a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java index 3359ca55a4..393f1c0c05 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/FormulaRecordAggregate.java @@ -17,9 +17,12 @@ package org.apache.poi.hssf.record.aggregates; +import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.StringRecord; +import org.apache.poi.hssf.record.TableRecord; /** * The formula record aggregate is used to join together the formula record and it's @@ -29,61 +32,67 @@ import org.apache.poi.hssf.record.StringRecord; */ public final class FormulaRecordAggregate extends RecordAggregate implements CellValueRecordInterface { - private FormulaRecord _formulaRecord; + private final FormulaRecord _formulaRecord; + /** caches the calculated result of the formula */ private StringRecord _stringRecord; + private TableRecord _tableRecord; - public FormulaRecordAggregate( FormulaRecord formulaRecord, StringRecord stringRecord ) - { + public FormulaRecordAggregate(FormulaRecord formulaRecord) { _formulaRecord = formulaRecord; - _stringRecord = stringRecord; + _stringRecord = null; } - - public void setStringRecord( StringRecord stringRecord ) { - _stringRecord = stringRecord; + public FormulaRecordAggregate(FormulaRecord formulaRecord, RecordStream rs) { + _formulaRecord = formulaRecord; + Class nextClass = rs.peekNextClass(); + if (nextClass == SharedFormulaRecord.class) { + // For (text) shared formulas, the SharedFormulaRecord comes before the StringRecord. + // In any case it is OK to skip SharedFormulaRecords because they were collected + // before constructing the ValueRecordsAggregate. + rs.getNext(); // skip the shared formula record + nextClass = rs.peekNextClass(); + } + if (nextClass == StringRecord.class) { + _stringRecord = (StringRecord) rs.getNext(); + } else if (nextClass == TableRecord.class) { + _tableRecord = (TableRecord) rs.getNext(); + } } - public void setFormulaRecord( FormulaRecord formulaRecord ) - { - _formulaRecord = formulaRecord; + public void setStringRecord(StringRecord stringRecord) { + _stringRecord = stringRecord; + _tableRecord = null; // probably can't have both present at the same time + // TODO - establish rules governing when each of these sub records may exist } - public FormulaRecord getFormulaRecord() - { + public FormulaRecord getFormulaRecord() { return _formulaRecord; } - public StringRecord getStringRecord() - { + public StringRecord getStringRecord() { return _stringRecord; } - public short getXFIndex() - { + public short getXFIndex() { return _formulaRecord.getXFIndex(); } - public void setXFIndex(short xf) - { - _formulaRecord.setXFIndex( xf ); + public void setXFIndex(short xf) { + _formulaRecord.setXFIndex(xf); } - public void setColumn(short col) - { - _formulaRecord.setColumn( col ); + public void setColumn(short col) { + _formulaRecord.setColumn(col); } - public void setRow(int row) - { - _formulaRecord.setRow( row ); + public void setRow(int row) { + _formulaRecord.setRow(row); } - public short getColumn() - { + public short getColumn() { return _formulaRecord.getColumn(); } - public int getRow() - { + public int getRow() { return _formulaRecord.getRow(); } @@ -94,8 +103,11 @@ public final class FormulaRecordAggregate extends RecordAggregate implements Cel public void visitContainedRecords(RecordVisitor rv) { rv.visitRecord(_formulaRecord); if (_stringRecord != null) { - rv.visitRecord(_stringRecord); + rv.visitRecord(_stringRecord); } + if (_tableRecord != null) { + rv.visitRecord(_tableRecord); + } } public String getStringValue() { diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java index d839ecfab6..fcbc89f63b 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java @@ -82,7 +82,7 @@ public final class RowRecordsAggregate extends RecordAggregate { if (!rec.isValue()) { throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); } - i += _valuesAgg.construct(recs, i, endIx, sfh); + i += _valuesAgg.construct(recs, i, endIx, sfh)-1; } "".length(); } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java index 0db1201432..886bb617d5 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/ValueRecordsAggregate.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.DBCellRecord; import org.apache.poi.hssf.record.FormulaRecord; @@ -111,12 +112,12 @@ public final class ValueRecordsAggregate { public void removeAllCellsValuesForRow(int rowIndex) { if (rowIndex >= records.length) { - throw new IllegalArgumentException("Specified rowIndex " + rowIndex + throw new IllegalArgumentException("Specified rowIndex " + rowIndex + " is outside the allowable range (0.." +records.length + ")"); } records[rowIndex] = null; } - + public int getPhysicalNumberOfCells() { @@ -142,62 +143,48 @@ public final class ValueRecordsAggregate { } /** - * Processes a sequential group of cell value records. Stops at endIx or the first + * Processes a sequential group of cell value records. Stops at endIx or the first * non-value record encountered. * @param sfh used to resolve any shared formulas for the current sheet * @return the number of records consumed */ public int construct(List records, int offset, int endIx, SharedFormulaHolder sfh) { - int k = 0; + RecordStream rs = new RecordStream(records, offset, endIx); - FormulaRecordAggregate lastFormulaAggregate = null; - // Now do the main processing sweep - for (k = offset; k < endIx; k++) { - Record rec = ( Record ) records.get(k); - - if (rec instanceof StringRecord) { - if (lastFormulaAggregate == null) { - throw new RuntimeException("StringRecord found without preceding FormulaRecord"); - } - if (lastFormulaAggregate.getStringRecord() != null) { - throw new RuntimeException("Multiple StringRecords found after FormulaRecord"); - } - lastFormulaAggregate.setStringRecord((StringRecord)rec); - lastFormulaAggregate = null; - continue; - } - - if (rec instanceof TableRecord) { - // TODO - don't loose this record - // DATATABLE probably belongs in formula record aggregate - if (lastFormulaAggregate == null) { - throw new RuntimeException("No preceding formula record found"); - } - lastFormulaAggregate = null; - continue; + while (rs.hasNext()) { + Class recClass = rs.peekNextClass(); + if (recClass == StringRecord.class) { + throw new RuntimeException("Loose StringRecord found without preceding FormulaRecord"); } - - if (rec instanceof SharedFormulaRecord) { - // Already handled, not to worry - continue; + + if (recClass == TableRecord.class) { + throw new RuntimeException("Loose TableRecord found without preceding FormulaRecord"); } - if (rec instanceof UnknownRecord) { + if (recClass == UnknownRecord.class) { break; } - if (rec instanceof RowRecord) { - break; + if (recClass == RowRecord.class) { + break; } - if (rec instanceof DBCellRecord) { + if (recClass == DBCellRecord.class) { // end of 'Row Block'. This record is ignored by POI break; } - if (rec instanceof MergeCellsRecord) { + + Record rec = rs.getNext(); + + if (recClass == SharedFormulaRecord.class) { + // Already handled, not to worry + continue; + } + if (recClass == MergeCellsRecord.class) { // doesn't really belong here // can safely be ignored, because it has been processed in a higher method continue; } + if (!rec.isValue()) { throw new RuntimeException("bad record type"); } @@ -206,14 +193,13 @@ public final class ValueRecordsAggregate { if (formula.isSharedFormula()) { sfh.convertSharedFormulaRecord(formula); } - - lastFormulaAggregate = new FormulaRecordAggregate((FormulaRecord)rec, null); - insertCell( lastFormulaAggregate ); + + insertCell(new FormulaRecordAggregate((FormulaRecord)rec, rs)); continue; } insertCell(( CellValueRecordInterface ) rec); } - return k - offset - 1; + return rs.getCountRead(); } /** Tallies a count of the size of the cell records @@ -235,7 +221,7 @@ public final class ValueRecordsAggregate { /** Returns true if the row has cells attached to it */ public boolean rowHasCells(int row) { - if (row > records.length-1) //previously this said row > records.length which means if + if (row > records.length-1) //previously this said row > records.length which means if return false; // if records.length == 60 and I pass "60" here I get array out of bounds CellValueRecordInterface[] rowCells=records[row]; //because a 60 length array has the last index = 59 if(rowCells==null) return false; @@ -260,7 +246,7 @@ public final class ValueRecordsAggregate { } return pos - offset; } - + public int visitCellsForRow(int rowIndex, RecordVisitor rv) { int result = 0; CellValueRecordInterface[] cellRecs = records[rowIndex]; @@ -292,7 +278,7 @@ public final class ValueRecordsAggregate { public CellValueRecordInterface[] getValueRecords() { List temp = new ArrayList(); - + for (int i = 0; i < records.length; i++) { CellValueRecordInterface[] rowCells = records[i]; if (rowCells == null) { @@ -305,7 +291,7 @@ public final class ValueRecordsAggregate { } } } - + CellValueRecordInterface[] result = new CellValueRecordInterface[temp.size()]; temp.toArray(result); return result; @@ -314,7 +300,7 @@ public final class ValueRecordsAggregate { { return new MyIterator(); } - + private final class MyIterator implements Iterator { short nextColumn=-1; int nextRow,lastRow; @@ -325,7 +311,7 @@ public final class ValueRecordsAggregate { this.lastRow=records.length-1; findNext(); } - + public MyIterator(int firstRow,int lastRow) { this.nextRow=firstRow; diff --git a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java index b38bcd27de..5ea1061ba7 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java @@ -39,6 +39,11 @@ public final class ArrayPtg extends Ptg { public static final byte sid = 0x20; private static final int RESERVED_FIELD_LEN = 7; + /** + * The size of the plain tArray token written within the standard formula tokens + * (not including the data which comes after all formula tokens) + */ + public static final int PLAIN_TOKEN_SIZE = 1+RESERVED_FIELD_LEN; // TODO - fix up field visibility and subclasses private byte[] field_1_reserved; @@ -123,7 +128,7 @@ public final class ArrayPtg extends Ptg { public int writeTokenValueBytes(byte[] data, int offset) { LittleEndian.putByte(data, offset + 0, token_1_columns-1); - LittleEndian.putShort(data, offset + 1, (short)(token_2_rows-1)); + LittleEndian.putUShort(data, offset + 1, token_2_rows-1); ConstantValueParser.encode(data, offset + 3, token_3_arrayValues); return 3 + ConstantValueParser.getEncodedSize(token_3_arrayValues); } @@ -137,11 +142,11 @@ public final class ArrayPtg extends Ptg { } /** This size includes the size of the array Ptg plus the Array Ptg Token value size*/ - public int getSize() - { - int size = 1+7+1+2; - size += ConstantValueParser.getEncodedSize(token_3_arrayValues); - return size; + public int getSize() { + return PLAIN_TOKEN_SIZE + // data written after the all tokens: + + 1 + 2 // column, row + + ConstantValueParser.getEncodedSize(token_3_arrayValues); } public String toFormulaString(Workbook book) diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java index 50f3450d28..43c5e86ebe 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java @@ -222,13 +222,6 @@ public abstract class Ptg implements Cloneable { throw new RuntimeException("Unexpected base token id (" + id + ")"); } /** - * - * - */ - public static int getEncodedSize(Stack ptgs) { - return getEncodedSize(toPtgArray(ptgs)); - } - /** * @return a distinct copy of this <tt>Ptg</tt> if the class is mutable, or the same instance * if the class is immutable. */ @@ -265,6 +258,11 @@ public abstract class Ptg implements Cloneable { } return result; } + /** + * This method will return the same result as {@link #getEncodedSizeWithoutArrayData(Ptg[])} + * if there are no array tokens present. + * @return the full size taken to encode the specified <tt>Ptg</tt>s + */ // TODO - several duplicates of this code should be refactored here public static int getEncodedSize(Ptg[] ptgs) { int result = 0; @@ -274,6 +272,22 @@ public abstract class Ptg implements Cloneable { return result; } /** + * Used to calculate value that should be encoded at the start of the encoded Ptg token array; + * @return the size of the encoded Ptg tokens not including any trailing array data. + */ + public static int getEncodedSizeWithoutArrayData(Ptg[] ptgs) { + int result = 0; + for (int i = 0; i < ptgs.length; i++) { + Ptg ptg = ptgs[i]; + if (ptg instanceof ArrayPtg) { + result += ArrayPtg.PLAIN_TOKEN_SIZE; + } else { + result += ptg.getSize(); + } + } + return result; + } + /** * Writes the ptgs to the data buffer, starting at the specified offset. * * <br/> diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java index 82cc8a9b40..182b9b618b 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEval.java @@ -1,28 +1,25 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 8, 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.eval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > - * + * */ public interface AreaEval extends ValueEval { @@ -30,76 +27,81 @@ public interface AreaEval extends ValueEval { * returns the 0-based index of the first row in * this area. */ - public int getFirstRow(); + int getFirstRow(); /** * returns the 0-based index of the last row in * this area. */ - public int getLastRow(); + int getLastRow(); /** * returns the 0-based index of the first col in * this area. */ - public int getFirstColumn(); + int getFirstColumn(); /** * returns the 0-based index of the last col in * this area. */ - public int getLastColumn(); - + int getLastColumn(); + /** * returns true if the Area's start and end row indexes * are same. This result of this method should agree * with getFirstRow() == getLastRow(). */ - public boolean isRow(); - + boolean isRow(); + /** * returns true if the Area's start and end col indexes * are same. This result of this method should agree * with getFirstColumn() == getLastColumn(). */ - public boolean isColumn(); + boolean isColumn(); /** * The array of values in this area. Although the area * maybe 1D (ie. isRow() or isColumn() returns true) or 2D * the returned array is 1D. */ - public ValueEval[] getValues(); + ValueEval[] getValues(); /** - * returns the ValueEval from the values array at the specified - * row and col index. The specified indexes should be absolute indexes - * in the sheet and not relative indexes within the area. Also, - * if contains(row, col) evaluates to true, a null value will - * bre returned. - * @param row - * @param col + * @return the ValueEval from within this area at the specified row and col index. Never + * <code>null</code> (possibly {@link BlankEval}). The specified indexes should be absolute + * indexes in the sheet and not relative indexes within the area. */ - public ValueEval getValueAt(int row, int col); - + ValueEval getValueAt(int row, int col); + /** - * returns true if the cell at row and col specified - * as absolute indexes in the sheet is contained in + * returns true if the cell at row and col specified + * as absolute indexes in the sheet is contained in * this area. * @param row * @param col */ - public boolean contains(int row, int col); - + boolean contains(int row, int col); + /** * returns true if the specified col is in range * @param col */ - public boolean containsColumn(short col); - + boolean containsColumn(short col); + /** * returns true if the specified row is in range * @param row */ - public boolean containsRow(int row); + boolean containsRow(int row); + + int getWidth(); + int getHeight(); + /** + * @return the ValueEval from within this area at the specified relativeRowIndex and + * relativeColumnIndex. Never <code>null</code> (possibly {@link BlankEval}). The + * specified indexes should relative to the top left corner of this area. + */ + ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex); } diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java index 9436ae0aa9..1686e75f33 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/AreaEvalBase.java @@ -1,19 +1,19 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF 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; @@ -94,9 +94,7 @@ abstract class AreaEvalBase implements AreaEval { throw new IllegalArgumentException("Specified column index (" + col + ") is outside the allowed range (" + _firstColumn + ".." + col + ")"); } - - int index = rowOffsetIx * _nColumns + colOffsetIx; - return _values[index]; + return getRelativeValue(rowOffsetIx, colOffsetIx); } public final boolean contains(int row, int col) { @@ -119,4 +117,20 @@ abstract class AreaEvalBase implements AreaEval { public final boolean isRow() { return _firstRow == _lastRow; } + public int getHeight() { + return _lastRow-_firstRow+1; + } + + public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) { + int index = relativeRowIndex * _nColumns + relativeColumnIndex; + ValueEval result = _values[index]; + if (result == null) { + return BlankEval.INSTANCE; + } + return result; + } + + public int getWidth() { + return _lastColumn-_firstColumn+1; + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java index df671821fe..d1f28df008 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/BlankEval.java @@ -1,30 +1,27 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT 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 9, 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.eval; /** * @author Amol S. Deshmukh < amolweb at ya hoo dot com > This class is a * marker class. It is a special value for empty cells. */ -public class BlankEval implements ValueEval { +public final class BlankEval implements ValueEval { public static BlankEval INSTANCE = new BlankEval(); diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java b/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java index be1cda5f8e..627b269989 100755 --- a/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/OperandResolver.java @@ -27,7 +27,7 @@ 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. @@ -113,17 +113,17 @@ public final class OperandResolver { } 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)) { @@ -131,25 +131,25 @@ public final class OperandResolver { } /* 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]; + return ae.getRelativeValue(0, 0); } if(!ae.containsRow(srcCellRow)) { throw EvaluationException.invalidValue(); @@ -199,20 +199,20 @@ public final class OperandResolver { */ 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(); + 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/> @@ -245,7 +245,7 @@ public final class OperandResolver { return null; } // TODO - support notation like '1E3' (==1000) - + double val; try { val = Double.parseDouble(text); @@ -254,7 +254,7 @@ public final class OperandResolver { } 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> @@ -274,4 +274,51 @@ public final class OperandResolver { } throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")"); } + + /** + * @return <code>null</code> to represent blank values + * @throws EvaluationException if ve is an ErrorEval, or if a string value cannot be converted + */ + public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException { + + if (ve == null || ve instanceof BlankEval) { + // TODO - remove 've == null' condition once AreaEval is fixed + return null; + } + if (ve instanceof BoolEval) { + return Boolean.valueOf(((BoolEval) ve).getBooleanValue()); + } + + if (ve instanceof BlankEval) { + return null; + } + + if (ve instanceof StringEval) { + if (stringsAreBlanks) { + return null; + } + String str = ((StringEval) ve).getStringValue(); + if (str.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (str.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + // else - string cannot be converted to boolean + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + + if (ve instanceof NumericValueEval) { + NumericValueEval ne = (NumericValueEval) ve; + double d = ne.getNumberValue(); + if (Double.isNaN(d)) { + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + return Boolean.valueOf(d != 0); + } + if (ve instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) ve); + } + throw new RuntimeException("Unexpected eval (" + ve.getClass().getName() + ")"); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/And.java b/src/java/org/apache/poi/hssf/record/formula/functions/And.java index 455772f737..8ef99f8904 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/And.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/And.java @@ -1,85 +1,32 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 9, 2005 - * - */ -package org.apache.poi.hssf.record.formula.functions; +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 -import org.apache.poi.hssf.record.formula.eval.AreaEval; -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.StringEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ -public class And extends BooleanFunction { +package org.apache.poi.hssf.record.formula.functions; + +/** + * + */ +public final class And extends BooleanFunction { - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; - boolean b = true; - boolean atleastOneNonBlank = false; - - /* - * Note: do not abort the loop if b is false, since we could be - * dealing with errorevals later. - */ - outer: - for (int i=0, iSize=operands.length; i<iSize; i++) { - if (operands[i] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[i]; - ValueEval[] values = ae.getValues(); - for (int j=0, jSize=values.length; j<jSize; j++) { - ValueEval tempVe = singleOperandEvaluate(values[j], srcRow, srcCol, true); - if (tempVe instanceof BoolEval) { - b = b && ((BoolEval) tempVe).getBooleanValue(); - atleastOneNonBlank = true; - } - else if (tempVe instanceof ErrorEval) { - retval = tempVe; - break outer; - } - } - } - else { - ValueEval tempVe = singleOperandEvaluate(operands[i], srcRow, srcCol, false); - if (tempVe instanceof BoolEval) { - b = b && ((BoolEval) tempVe).getBooleanValue(); - atleastOneNonBlank = true; - } - else if (tempVe instanceof StringEval) { - retval = ErrorEval.VALUE_INVALID; - } - else if (tempVe instanceof ErrorEval) { - retval = tempVe; - break outer; - } - } - } - - if (!atleastOneNonBlank) { - retval = ErrorEval.VALUE_INVALID; - } - - if (retval == null) { // if no error - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - - return retval; - } + protected boolean getInitialResultValue() { + return true; + } + protected boolean partialEvaluate(boolean cumulativeResult, boolean currentValue) { + return cumulativeResult && currentValue; + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java b/src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java index b3e56cfdc6..9ec924e27a 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/BooleanFunction.java @@ -1,100 +1,108 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT 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.AreaEval; 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.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.RefEval; -import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; - /** - * @author Amol S. Deshmukh < amolweb at ya hoo dot com > * Here are the general rules concerning Boolean functions: * <ol> - * <li> Blanks are not either true or false - * <li> Strings are not either true or false (even strings "true" - * or "TRUE" or "0" etc.) - * <li> Numbers: 0 is false. Any other number is TRUE. - * <li> References are evaluated and above rules apply. - * <li> Areas: Individual cells in area are evaluated and checked to - * see if they are blanks, strings etc. + * <li> Blanks are ignored (not either true or false) </li> + * <li> Strings are ignored if part of an area ref or cell ref, otherwise they must be 'true' or 'false'</li> + * <li> Numbers: 0 is false. Any other number is TRUE </li> + * <li> Areas: *all* cells in area are evaluated according to the above rules</li> * </ol> + * + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > */ public abstract class BooleanFunction implements Function { - protected ValueEval singleOperandEvaluate(Eval eval, int srcRow, short srcCol, boolean stringsAreBlanks) { - ValueEval retval; - - if (eval instanceof RefEval) { - RefEval re = (RefEval) eval; - ValueEval ve = re.getInnerValueEval(); - retval = internalResolve(ve, true); - } - else { - retval = internalResolve(eval, stringsAreBlanks); - } - - return retval; - } - - private ValueEval internalResolve(Eval ve, boolean stringsAreBlanks) { - ValueEval retval = null; - - // blankeval is returned as is - if (ve instanceof BlankEval) { - retval = BlankEval.INSTANCE; - } - - // stringeval - else if (ve instanceof StringEval) { - retval = stringsAreBlanks ? (ValueEval) BlankEval.INSTANCE : (StringEval) ve; - } - - // bools are bools :) - else if (ve instanceof BoolEval) { - retval = (BoolEval) ve; - } - - // convert numbers to bool - else if (ve instanceof NumericValueEval) { - NumericValueEval ne = (NumericValueEval) ve; - double d = ne.getNumberValue(); - retval = Double.isNaN(d) - ? (ValueEval) ErrorEval.VALUE_INVALID - : (d != 0) - ? BoolEval.TRUE - : BoolEval.FALSE; - } - - // since refevals - else { - retval = ErrorEval.VALUE_INVALID; - } - - return retval; - - } + public final Eval evaluate(Eval[] args, int srcRow, short srcCol) { + if (args.length < 1) { + return ErrorEval.VALUE_INVALID; + } + boolean boolResult; + try { + boolResult = calculate(args); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + return BoolEval.valueOf(boolResult); + } + + private boolean calculate(Eval[] args) throws EvaluationException { + + boolean result = getInitialResultValue(); + boolean atleastOneNonBlank = false; + + /* + * Note: no short-circuit boolean loop exit because any ErrorEvals will override the result + */ + for (int i=0, iSize=args.length; i<iSize; i++) { + Eval arg = args[i]; + if (arg instanceof AreaEval) { + AreaEval ae = (AreaEval) arg; + int height = ae.getHeight(); + int width = ae.getWidth(); + for (int rrIx=0; rrIx<height; rrIx++) { + for (int rcIx=0; rcIx<width; rcIx++) { + ValueEval ve = ae.getRelativeValue(rrIx, rcIx); + Boolean tempVe = OperandResolver.coerceValueToBoolean(ve, true); + if (tempVe != null) { + result = partialEvaluate(result, tempVe.booleanValue()); + atleastOneNonBlank = true; + } + } + } + continue; + } + Boolean tempVe; + if (arg instanceof RefEval) { + ValueEval ve = ((RefEval) arg).getInnerValueEval(); + tempVe = OperandResolver.coerceValueToBoolean(ve, true); + } else if (arg instanceof ValueEval) { + ValueEval ve = (ValueEval) arg; + tempVe = OperandResolver.coerceValueToBoolean(ve, false); + } else { + throw new RuntimeException("Unexpected eval (" + arg.getClass().getName() + ")"); + } + + + if (tempVe != null) { + result = partialEvaluate(result, tempVe.booleanValue()); + atleastOneNonBlank = true; + } + } + + if (!atleastOneNonBlank) { + throw new EvaluationException(ErrorEval.VALUE_INVALID); + } + return result; + } + + + protected abstract boolean getInitialResultValue(); + protected abstract boolean partialEvaluate(boolean cumulativeResult, boolean currentValue); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Count.java b/src/java/org/apache/poi/hssf/record/formula/functions/Count.java index eb55fc4a42..fd5944e858 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Count.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Count.java @@ -1,32 +1,26 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT 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.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.NumberEval; -import org.apache.poi.hssf.record.formula.eval.RefEval; -import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate; /** * Counts the number of cells that contain numeric data within @@ -39,7 +33,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * TODO: Check this properly matches excel on edge cases * like formula cells, error cells etc */ -public class Count implements Function { +public final class Count implements Function { public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { int nArgs = args.length; @@ -56,63 +50,23 @@ public class Count implements Function { int temp = 0; for(int i=0; i<nArgs; i++) { - temp += countArg(args[i]); + temp += CountUtils.countArg(args[i], predicate); } return new NumberEval(temp); } - private static int countArg(Eval eval) { - if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - return countAreaEval(ae); - } - if (eval instanceof RefEval) { - RefEval refEval = (RefEval)eval; - return countValue(refEval.getInnerValueEval()); - } - if (eval instanceof NumberEval) { - return 1; - } - - throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); - } + private static final I_MatchPredicate predicate = new I_MatchPredicate() { - private static int countAreaEval(AreaEval ae) { - - int temp = 0; - ValueEval[] values = ae.getValues(); - for (int i = 0; i < values.length; i++) { - ValueEval val = values[i]; - if(val == null) { - // seems to occur. Really we would have expected BlankEval - continue; - } - temp += countValue(val); - - } - return temp; - } + public boolean matches(Eval valueEval) { - private static int countValue(ValueEval valueEval) { - - if(valueEval == BlankEval.INSTANCE) { - return 0; - } - - if(valueEval instanceof BlankEval) { - // wouldn't need this if BlankEval was final - return 0; - } + if(valueEval instanceof NumberEval) { + // only numbers are counted + return true; + } - if(valueEval instanceof ErrorEval) { - // note - error values not counted - return 0; + // error values and string values not counted + return false; } - - if(valueEval instanceof NumberEval) - return 1; - - return 0; - } + }; }
\ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/CountUtils.java b/src/java/org/apache/poi/hssf/record/formula/functions/CountUtils.java new file mode 100644 index 0000000000..f8d8883825 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/functions/CountUtils.java @@ -0,0 +1,78 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.eval.AreaEval; +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.ValueEval; + +/** + * Common logic for COUNT, COUNTA and COUNTIF + * + * @author Josh Micich + */ +final class CountUtils { + + private CountUtils() { + // no instances of this class + } + + /** + * Common interface for the matching criteria. + */ + public interface I_MatchPredicate { + boolean matches(Eval x); + } + /** + * @return the number of evaluated cells in the range that match the specified criteria + */ + public static int countMatchingCellsInArea(AreaEval areaEval, I_MatchPredicate criteriaPredicate) { + int result = 0; + + int height = areaEval.getHeight(); + int width = areaEval.getWidth(); + for (int rrIx=0; rrIx<height; rrIx++) { + for (int rcIx=0; rcIx<width; rcIx++) { + ValueEval ve = areaEval.getRelativeValue(rrIx, rcIx); + if(criteriaPredicate.matches(ve)) { + result++; + } + } + } + return result; + } + /** + * @return 1 if the evaluated cell matches the specified criteria + */ + public static int countMatchingCell(RefEval refEval, I_MatchPredicate criteriaPredicate) { + if(criteriaPredicate.matches(refEval.getInnerValueEval())) { + return 1; + } + return 0; + } + public static int countArg(Eval eval, I_MatchPredicate criteriaPredicate) { + if (eval instanceof AreaEval) { + return CountUtils.countMatchingCellsInArea((AreaEval) eval, criteriaPredicate); + } + if (eval instanceof RefEval) { + return CountUtils.countMatchingCell((RefEval) eval, criteriaPredicate); + } + return criteriaPredicate.matches(eval) ? 1 : 0; + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Counta.java b/src/java/org/apache/poi/hssf/record/formula/functions/Counta.java index 9061e77e5d..01fc0616d3 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Counta.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Counta.java @@ -1,31 +1,27 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF 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.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.NumberEval; -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.CountUtils.I_MatchPredicate; /** * Counts the number of cells that contain data within the list of arguments. @@ -51,70 +47,26 @@ public final class Counta implements Function { } int temp = 0; - // Note - observed behavior of Excel: - // Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error - // in fact, they seem to get counted for(int i=0; i<nArgs; i++) { - temp += countArg(args[i]); + temp += CountUtils.countArg(args[i], predicate); } return new NumberEval(temp); } - private static int countArg(Eval eval) { - if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - return countAreaEval(ae); - } - if (eval instanceof RefEval) { - RefEval refEval = (RefEval)eval; - return countValue(refEval.getInnerValueEval()); - } - if (eval instanceof NumberEval) { - return 1; - } - if (eval instanceof StringEval) { - return 1; - } - - - throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); - } + private static final I_MatchPredicate predicate = new I_MatchPredicate() { - private static int countAreaEval(AreaEval ae) { - - int temp = 0; - ValueEval[] values = ae.getValues(); - for (int i = 0; i < values.length; i++) { - ValueEval val = values[i]; - if(val == null) { - // seems to occur. Really we would have expected BlankEval - continue; - } - temp += countValue(val); - - } - return temp; - } + public boolean matches(Eval valueEval) { + // Note - observed behavior of Excel: + // Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error + // in fact, they seem to get counted - private static int countValue(ValueEval valueEval) { - - if(valueEval == BlankEval.INSTANCE) { - return 0; - } - - if(valueEval instanceof BlankEval) { - // wouldn't need this if BlankEval was final - return 0; - } - - if(valueEval instanceof ErrorEval) { - // note - error values are counted - return 1; + if(valueEval == BlankEval.INSTANCE) { + return false; + } + // Note - everything but BlankEval counts + return true; } - // also empty strings and zeros are counted too - - return 1; - } + }; } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java b/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java index 902a991b37..00eb86e943 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java @@ -1,19 +1,19 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF 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; @@ -28,7 +28,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval; 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.CountUtils.I_MatchPredicate; /** * Implementation for the function COUNTIF<p/> @@ -144,12 +144,6 @@ public final class Countif implements Function { } } - /** - * Common interface for the matching criteria. - */ - /* package */ interface I_MatchPredicate { - boolean matches(Eval x); - } private static final class NumberMatcher implements I_MatchPredicate { @@ -360,21 +354,12 @@ public final class Countif implements Function { * @return the number of evaluated cells in the range that match the specified criteria */ private Eval countMatchingCellsInArea(Eval rangeArg, I_MatchPredicate criteriaPredicate) { - int result = 0; + + int result; if (rangeArg instanceof RefEval) { - RefEval refEval = (RefEval) rangeArg; - if(criteriaPredicate.matches(refEval.getInnerValueEval())) { - result++; - } + result = CountUtils.countMatchingCell((RefEval) rangeArg, criteriaPredicate); } else if (rangeArg instanceof AreaEval) { - - AreaEval range = (AreaEval) rangeArg; - ValueEval[] values = range.getValues(); - for (int i = 0; i < values.length; i++) { - if(criteriaPredicate.matches(values[i])) { - result++; - } - } + result = CountUtils.countMatchingCellsInArea((AreaEval) rangeArg, criteriaPredicate); } else { throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")"); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java b/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java index d493cd5332..8604eadc37 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Hlookup.java @@ -42,40 +42,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; */ 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) { @@ -93,7 +59,7 @@ public final class Hlookup implements Function { 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); + int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createRowVector(tableArray, 0), isRangeLookup); ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol); int rowIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex); ValueVector resultCol = createResultColumnVector(tableArray, rowIndex); @@ -113,11 +79,9 @@ public final class Hlookup implements Function { if(colIndex < 0) { throw EvaluationException.invalidValue(); } - int nCols = tableArray.getLastColumn() - tableArray.getFirstRow() + 1; - - if(colIndex >= nCols) { + if(colIndex >= tableArray.getWidth()) { throw EvaluationException.invalidRef(); } - return new RowVector(tableArray, colIndex); + return LookupUtils.createRowVector(tableArray, colIndex); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java b/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java index be1d0d0f94..305d8fb0d7 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Lookup.java @@ -40,19 +40,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; * @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) { @@ -86,11 +73,11 @@ public final class Lookup implements Function { } 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"); + ValueVector result = LookupUtils.createVector(ae); + if (result != null) { + return result; } - return new SimpleValueVector(ae.getValues()); + // 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"); } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java b/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java index d6a8489623..e8c083dc5a 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/LookupUtils.java @@ -34,11 +34,11 @@ 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>. */ @@ -46,14 +46,95 @@ final class LookupUtils { ValueEval getItem(int index); int getSize(); } + + + private static final class RowVector implements ValueVector { + + private final AreaEval _tableArray; + private final int _size; + private final int _rowIndex; + + public RowVector(AreaEval tableArray, int rowIndex) { + _rowIndex = rowIndex; + int _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.getWidth(); + } + + public ValueEval getItem(int index) { + if(index > _size) { + throw new ArrayIndexOutOfBoundsException("Specified index (" + index + + ") is outside the allowed range (0.." + (_size-1) + ")"); + } + return _tableArray.getRelativeValue(_rowIndex, index); + } + public int getSize() { + return _size; + } + } + + private static final class ColumnVector implements ValueVector { + + private final AreaEval _tableArray; + private final int _size; + private final int _columnIndex; + + public ColumnVector(AreaEval tableArray, int columnIndex) { + _columnIndex = columnIndex; + int _columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex; + if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) { + int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn(); + throw new IllegalArgumentException("Specified column index (" + columnIndex + + ") is outside the allowed range (0.." + lastColIx + ")"); + } + _tableArray = tableArray; + _size = _tableArray.getHeight(); + } + + public ValueEval getItem(int index) { + if(index > _size) { + throw new ArrayIndexOutOfBoundsException("Specified index (" + index + + ") is outside the allowed range (0.." + (_size-1) + ")"); + } + return _tableArray.getRelativeValue(index, _columnIndex); + } + public int getSize() { + return _size; + } + } + + public static ValueVector createRowVector(AreaEval tableArray, int relativeRowIndex) { + return new RowVector(tableArray, relativeRowIndex); + } + public static ValueVector createColumnVector(AreaEval tableArray, int relativeColumnIndex) { + return new ColumnVector(tableArray, relativeColumnIndex); + } + /** + * @return <code>null</code> if the supplied area is neither a single row nor a single colum + */ + public static ValueVector createVector(AreaEval ae) { + if (ae.isColumn()) { + return createColumnVector(ae, 0); + } + if (ae.isRow()) { + return createRowVector(ae, 0); + } + return null; + } + /** * 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 + * 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 + * + * 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. */ @@ -80,7 +161,7 @@ final class LookupUtils { 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; @@ -90,7 +171,7 @@ final class LookupUtils { } return EQUAL; } - + public boolean isTypeMismatch() { return _isTypeMismatch; } @@ -128,17 +209,17 @@ final class LookupUtils { return "??error??"; } } - + public interface LookupValueComparer { /** - * @return one of 4 instances or <tt>CompareResult</tt>: <tt>LESS_THAN</tt>, <tt>EQUAL</tt>, + * @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) { @@ -154,7 +235,7 @@ final class LookupUtils { return CompareResult.TYPE_MISMATCH; } if (_targetClass == StringEval.class) { - + } return compareSameType(other); } @@ -169,7 +250,7 @@ final class LookupUtils { /** used only for debug purposes */ protected abstract String getValueAsString(); } - + private static final class StringLookupComparer extends LookupValueComparerBase { private String _value; @@ -223,9 +304,9 @@ final class LookupUtils { return String.valueOf(_value); } } - + /** - * Processes the third argument to VLOOKUP, or HLOOKUP (<b>col_index_num</b> + * 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"> @@ -242,17 +323,17 @@ final class LookupUtils { * <tr><td>""</td><td> </td><td>#REF!</td></tr> * <tr><td><blank></td><td> </td><td>#VALUE!</td></tr> * </table><br/> - * - * * Note - out of range errors (both too high and too low) are handled by the caller. + * + * * 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(); + throw EvaluationException.invalidValue(); } if(veRowColIndexArg instanceof StringEval) { StringEval se = (StringEval) veRowColIndexArg; @@ -260,7 +341,7 @@ final class LookupUtils { Double dVal = OperandResolver.parseDouble(strVal); if(dVal == null) { // String does not resolve to a number. Raise #VALUE! error. - throw EvaluationException.invalidRef(); + throw EvaluationException.invalidRef(); // This includes text booleans "TRUE" and "FALSE". They are not valid. } // else - numeric value parses OK @@ -268,9 +349,9 @@ final class LookupUtils { // 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. @@ -279,13 +360,13 @@ final class LookupUtils { 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. + // 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(); @@ -295,10 +376,10 @@ final class LookupUtils { } throw EvaluationException.invalidValue(); } - + /** - * Resolves the last (optional) parameter (<b>range_lookup</b>) to the VLOOKUP and HLOOKUP functions. + * Resolves the last (optional) parameter (<b>range_lookup</b>) to the VLOOKUP and HLOOKUP functions. * @param rangeLookupArg * @param srcCellRow * @param srcCellCol @@ -318,7 +399,7 @@ final class LookupUtils { return false; } if(valEval instanceof BoolEval) { - // Happy day flow + // Happy day flow BoolEval boolEval = (BoolEval) valEval; return boolEval.getBooleanValue(); } @@ -327,7 +408,7 @@ final class LookupUtils { String stringValue = ((StringEval) valEval).getStringValue(); if(stringValue.length() < 1) { // More trickiness: - // Empty string is not the same as BlankEval. It causes #VALUE! error + // Empty string is not the same as BlankEval. It causes #VALUE! error throw EvaluationException.invalidValue(); } // TODO move parseBoolean to OperandResolver @@ -337,10 +418,10 @@ final class LookupUtils { 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. + // 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 + // 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) { @@ -350,7 +431,7 @@ final class LookupUtils { } 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; @@ -364,13 +445,13 @@ final class LookupUtils { } 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. + * @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) { @@ -385,10 +466,10 @@ final class LookupUtils { return -1; } - + /** * Encapsulates some standard binary search functionality so the unusual Excel behaviour can - * be clearly distinguished. + * be clearly distinguished. */ private static final class BinarySearchIndexes { @@ -427,7 +508,7 @@ final class LookupUtils { } /** * 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. @@ -435,7 +516,7 @@ final class LookupUtils { while(true) { int midIx = bsi.getMidIx(); - + if(midIx < 0) { return bsi.getLowIx(); } @@ -455,17 +536,17 @@ final class LookupUtils { } } /** - * Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the + * 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 + * @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) { @@ -511,9 +592,9 @@ final class LookupUtils { } public static LookupValueComparer createLookupComparer(ValueEval lookupValue) throws EvaluationException { - + if (lookupValue instanceof BlankEval) { - // blank eval can never be found in a lookup array + // blank eval can never be found in a lookup array throw new EvaluationException(ErrorEval.NA); } if (lookupValue instanceof StringEval) { diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Match.java b/src/java/org/apache/poi/hssf/record/formula/functions/Match.java index a2a12cdba8..a464ec873a 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Match.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Match.java @@ -29,18 +29,19 @@ 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; +import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; /** * 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 + * + * 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. @@ -50,26 +51,26 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.LookupValueCompa * <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. + * 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 { @@ -85,15 +86,15 @@ public final class Match implements Function { 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]); + ValueVector 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) { @@ -101,19 +102,40 @@ public final class Match implements Function { } } - private static ValueEval[] evaluateLookupRange(Eval eval) throws EvaluationException { + private static final class SingleValueVector implements ValueVector { + + private final ValueEval _value; + + public SingleValueVector(ValueEval value) { + _value = value; + } + + public ValueEval getItem(int index) { + if (index != 0) { + throw new RuntimeException("Invalid index (" + + index + ") only zero is allowed"); + } + return _value; + } + + public int getSize() { + return 1; + } + } + + private static ValueVector evaluateLookupRange(Eval eval) throws EvaluationException { if (eval instanceof RefEval) { RefEval re = (RefEval) eval; - return new ValueEval[] { re.getInnerValueEval(), }; + return new SingleValueVector(re.getInnerValueEval()); } if (eval instanceof AreaEval) { - AreaEval ae = (AreaEval) eval; - if(!ae.isColumn() && !ae.isRow()) { + ValueVector result = LookupUtils.createVector((AreaEval)eval); + if (result == null) { throw new EvaluationException(ErrorEval.NA); } - return ae.getValues(); + return result; } - + // Error handling for lookup_range arg is also unusual if(eval instanceof NumericValueEval) { throw new EvaluationException(ErrorEval.NA); @@ -133,7 +155,7 @@ public final class Match implements Function { - private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) + private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException { Eval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); @@ -156,28 +178,29 @@ public final class Match implements Function { } throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")"); } - + /** * @return zero based index */ - private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange, + private static int findIndexOfValue(ValueEval lookupValue, ValueVector lookupRange, boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException { LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact); - + + int size = lookupRange.getSize(); if(matchExact) { - for (int i = 0; i < lookupRange.length; i++) { - if(lookupComparer.compareTo(lookupRange[i]).isEqual()) { + for (int i = 0; i < size; i++) { + if(lookupComparer.compareTo(lookupRange.getItem(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]); + for (int i = size - 1; i>=0; i--) { + CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i)); if(cmp.isTypeMismatch()) { continue; } @@ -187,11 +210,11 @@ public final class Match implements Function { } 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]); + for (int i = 0; i<size; i++) { + CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i)); if(cmp.isEqual()) { return i; } @@ -212,7 +235,7 @@ public final class Match implements Function { if(isLookupValueWild(stringValue)) { throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet"); } - + } return LookupUtils.createLookupComparer(lookupValue); } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Not.java b/src/java/org/apache/poi/hssf/record/formula/functions/Not.java index 7ce39d6c71..b383e090df 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Not.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Not.java @@ -1,33 +1,27 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT 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 9, 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.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.NumberEval; -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.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.ValueEval; @@ -37,87 +31,21 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * (treated as a boolean). If the specified arg is a number, * then it is true <=> 'number is non-zero' */ -public class Not extends BooleanFunction { - - - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; - boolean b = true; - ValueEval tempVe = null; - - switch (operands.length) { - default: - retval = ErrorEval.VALUE_INVALID; - break; - case 1: - if (operands[0] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[0]; - if (ae.isRow() && ae.containsColumn(srcCol)) { - ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol); - tempVe = singleOperandEvaluate(ve); - } else if (ae.isColumn() && ae.containsRow(srcRow)) { - ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn()); - tempVe = singleOperandEvaluate(ve); - } else { - retval = ErrorEval.VALUE_INVALID; - } - } - else { - tempVe = singleOperandEvaluate(operands[0]); - if (tempVe instanceof StringEval) { - retval = ErrorEval.VALUE_INVALID; - } - else if (tempVe instanceof ErrorEval) { - retval = tempVe; - } - } - } - - if (retval == null) { // if no error - if (tempVe instanceof BoolEval) { - b = b && ((BoolEval) tempVe).getBooleanValue(); - } - else if (tempVe instanceof StringEval) { - retval = ErrorEval.VALUE_INVALID; - } - else if (tempVe instanceof ErrorEval) { - retval = tempVe; - } - retval = b ? BoolEval.FALSE : BoolEval.TRUE; - } - - return retval; - } - - - protected ValueEval singleOperandEvaluate(Eval ve) { - ValueEval retval = ErrorEval.VALUE_INVALID; - if (ve instanceof RefEval) { - RefEval re = (RefEval) ve; - retval = singleOperandEvaluate(re.getInnerValueEval()); - } - else if (ve instanceof BoolEval) { - retval = (BoolEval) ve; - } - else if (ve instanceof NumberEval) { - NumberEval ne = (NumberEval) ve; - retval = ne.getNumberValue() != 0 ? BoolEval.TRUE : BoolEval.FALSE; - } - else if (ve instanceof StringEval) { - StringEval se = (StringEval) ve; - String str = se.getStringValue(); - retval = str.equalsIgnoreCase("true") - ? BoolEval.TRUE - : str.equalsIgnoreCase("false") - ? BoolEval.FALSE - : (ValueEval) ErrorEval.VALUE_INVALID; - } - else if (ve instanceof BlankEval) { - retval = BoolEval.FALSE; - } - else { - retval = ErrorEval.VALUE_INVALID; - } - return retval; - } +public final class Not implements Function { + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + if (args.length != 1) { + return ErrorEval.VALUE_INVALID; + } + boolean boolArgVal; + try { + ValueEval ve = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + Boolean b = OperandResolver.coerceValueToBoolean(ve, false); + boolArgVal = b == null ? false : b.booleanValue(); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + return BoolEval.valueOf(!boolArgVal); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Or.java b/src/java/org/apache/poi/hssf/record/formula/functions/Or.java index 22d7003d4d..2d36037f5d 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Or.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Or.java @@ -1,81 +1,32 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one or more -* contributor license agreements. See the NOTICE file distributed with -* this work for additional information regarding copyright ownership. -* The ASF licenses this file to You under the Apache License, Version 2.0 -* (the "License"); you may not use this file except in compliance with -* the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -/* - * Created on May 9, 2005 - * - */ -package org.apache.poi.hssf.record.formula.functions; +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 -import org.apache.poi.hssf.record.formula.eval.AreaEval; -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.ValueEval; + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ -public class Or extends BooleanFunction { +package org.apache.poi.hssf.record.formula.functions; + +/** + * + */ +public final class Or extends BooleanFunction { - public Eval evaluate(Eval[] operands, int srcRow, short srcCol) { - ValueEval retval = null; - boolean b = false; - boolean atleastOneNonBlank = false; - - /* - * Note: do not abort the loop if b is true, since we could be - * dealing with errorevals later. - */ - outer: - for (int i=0, iSize=operands.length; i<iSize; i++) { - if (operands[i] instanceof AreaEval) { - AreaEval ae = (AreaEval) operands[i]; - ValueEval[] values = ae.getValues(); - for (int j=0, jSize=values.length; j<jSize; j++) { - ValueEval tempVe = singleOperandEvaluate(values[j], srcRow, srcCol, true); - if (tempVe instanceof BoolEval) { - b = b || ((BoolEval) tempVe).getBooleanValue(); - atleastOneNonBlank = true; - } - else if (tempVe instanceof ErrorEval) { - retval = tempVe; - break outer; - } - } - } - else { - ValueEval tempVe = singleOperandEvaluate(operands[i], srcRow, srcCol, false); - if (tempVe instanceof BoolEval) { - b = b || ((BoolEval) tempVe).getBooleanValue(); - atleastOneNonBlank = true; - } - else if (tempVe instanceof ErrorEval) { - retval = tempVe; - break outer; - } - } - } - - if (!atleastOneNonBlank) { - retval = ErrorEval.VALUE_INVALID; - } - - if (retval == null) { // if no error - retval = b ? BoolEval.TRUE : BoolEval.FALSE; - } - - return retval; - } + protected boolean getInitialResultValue() { + return false; + } + protected boolean partialEvaluate(boolean cumulativeResult, boolean currentValue) { + return cumulativeResult || currentValue; + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java b/src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java index 9f6eafa4dc..1ed56e4718 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Sumproduct.java @@ -22,6 +22,7 @@ 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.NumericValueEval; import org.apache.poi.hssf.record.formula.eval.RefEval; @@ -53,16 +54,6 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; */ public final class Sumproduct implements Function { - private static final class EvalEx extends Exception { - private final ErrorEval _error; - - public EvalEx(ErrorEval error) { - _error = error; - } - public ErrorEval getError() { - return _error; - } - } public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { @@ -86,14 +77,14 @@ public final class Sumproduct implements Function { } return evaluateAreaSumProduct(args); } - } catch (EvalEx e) { - return e.getError(); + } catch (EvaluationException e) { + return e.getErrorEval(); } throw new RuntimeException("Invalid arg type for SUMPRODUCT: (" + firstArg.getClass().getName() + ")"); } - private Eval evaluateSingleProduct(Eval[] evalArgs) throws EvalEx { + private static Eval evaluateSingleProduct(Eval[] evalArgs) throws EvaluationException { int maxN = evalArgs.length; double term = 1D; @@ -104,7 +95,7 @@ public final class Sumproduct implements Function { return new NumberEval(term); } - private double getScalarValue(Eval arg) throws EvalEx { + private static double getScalarValue(Eval arg) throws EvaluationException { Eval eval; if (arg instanceof RefEval) { @@ -121,9 +112,9 @@ public final class Sumproduct implements Function { AreaEval ae = (AreaEval) eval; // an area ref can work as a scalar value if it is 1x1 if(!ae.isColumn() || !ae.isRow()) { - throw new EvalEx(ErrorEval.VALUE_INVALID); + throw new EvaluationException(ErrorEval.VALUE_INVALID); } - eval = ae.getValues()[0]; + eval = ae.getRelativeValue(0, 0); } if (!(eval instanceof ValueEval)) { @@ -134,7 +125,7 @@ public final class Sumproduct implements Function { return getProductTerm((ValueEval) eval, true); } - private Eval evaluateAreaSumProduct(Eval[] evalArgs) throws EvalEx { + private static Eval evaluateAreaSumProduct(Eval[] evalArgs) throws EvaluationException { int maxN = evalArgs.length; AreaEval[] args = new AreaEval[maxN]; try { @@ -147,23 +138,27 @@ public final class Sumproduct implements Function { AreaEval firstArg = args[0]; - int height = firstArg.getLastRow() - firstArg.getFirstRow() + 1; - int width = firstArg.getLastColumn() - firstArg.getFirstColumn() + 1; // TODO - junit - + int height = firstArg.getHeight(); + int width = firstArg.getWidth(); // TODO - junit - - double[][][] elements = new double[maxN][][]; - - for (int n = 0; n < maxN; n++) { - elements[n] = evaluateArea(args[n], height, width); + // first check dimensions + if (!areasAllSameSize(args, height, width)) { + // normally this results in #VALUE!, + // but errors in individual cells take precedence + for (int i = 1; i < args.length; i++) { + throwFirstError(args[i]); + } + return ErrorEval.VALUE_INVALID; } + double acc = 0; - for(int r=0; r<height; r++) { - for(int c=0; c<width; c++) { + for (int rrIx=0; rrIx<height; rrIx++) { + for (int rcIx=0; rcIx<width; rcIx++) { double term = 1D; for(int n=0; n<maxN; n++) { - term *= elements[n][r][c]; + double val = getProductTerm(args[n].getRelativeValue(rrIx, rcIx), false); + term *= val; } acc += term; } @@ -172,60 +167,61 @@ public final class Sumproduct implements Function { return new NumberEval(acc); } - /** - * @return a 2-D array of the specified height and width corresponding to the evaluated cell - * values of the specified areaEval - * @throws EvalEx if any ErrorEval value was encountered while evaluating the area - */ - private static double[][] evaluateArea(AreaEval areaEval, int height, int width) throws EvalEx { - int fr =areaEval.getFirstRow(); - int fc =areaEval.getFirstColumn(); - - // check that height and width match - if(areaEval.getLastRow() - fr + 1 != height) { - throw new EvalEx(ErrorEval.VALUE_INVALID); - } - if(areaEval.getLastColumn() - fc + 1 != width) { - throw new EvalEx(ErrorEval.VALUE_INVALID); - } - ValueEval[] values = areaEval.getValues(); - double[][] result = new double[height][width]; - for(int r=0; r<height; r++) { - for(int c=0; c<width; c++) { - ValueEval ve = values[r*width + c]; - result[r][c] = getProductTerm(ve, false); + private static void throwFirstError(AreaEval areaEval) throws EvaluationException { + int height = areaEval.getHeight(); + int width = areaEval.getWidth(); + for (int rrIx=0; rrIx<height; rrIx++) { + for (int rcIx=0; rcIx<width; rcIx++) { + ValueEval ve = areaEval.getRelativeValue(rrIx, rcIx); + if (ve instanceof ErrorEval) { + throw new EvaluationException((ErrorEval) ve); + } + } + } + } + + private static boolean areasAllSameSize(AreaEval[] args, int height, int width) { + for (int i = 0; i < args.length; i++) { + AreaEval areaEval = args[i]; + // check that height and width match + if(areaEval.getHeight() != height) { + return false; + } + if(areaEval.getWidth() != width) { + return false; } } - return result; + return true; } + /** * Determines a <code>double</code> value for the specified <code>ValueEval</code>. * @param isScalarProduct <code>false</code> for SUMPRODUCTs over area refs. - * @throws EvalEx if <code>ve</code> represents an error value. + * @throws EvaluationException if <code>ve</code> represents an error value. * <p/> * Note - string values and empty cells are interpreted differently depending on * <code>isScalarProduct</code>. For scalar products, if any term is blank or a string, the * error (#VALUE!) is raised. For area (sum)products, if any term is blank or a string, the * result is zero. */ - private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvalEx { + private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvaluationException { if(ve instanceof BlankEval || ve == null) { // TODO - shouldn't BlankEval.INSTANCE be used always instead of null? // null seems to occur when the blank cell is part of an area ref (but not reliably) if(isScalarProduct) { - throw new EvalEx(ErrorEval.VALUE_INVALID); + throw new EvaluationException(ErrorEval.VALUE_INVALID); } return 0; } if(ve instanceof ErrorEval) { - throw new EvalEx((ErrorEval)ve); + throw new EvaluationException((ErrorEval)ve); } if(ve instanceof StringEval) { if(isScalarProduct) { - throw new EvalEx(ErrorEval.VALUE_INVALID); + throw new EvaluationException(ErrorEval.VALUE_INVALID); } // Note for area SUMPRODUCTs, string values are interpreted as zero // even if they would parse as valid numeric values diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java b/src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java index bd158b897c..54f7d465e5 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Vlookup.java @@ -42,40 +42,6 @@ import org.apache.poi.hssf.record.formula.functions.LookupUtils.ValueVector; */ public final class Vlookup implements Function { - private static final class ColumnVector implements ValueVector { - - private final AreaEval _tableArray; - private final int _size; - private final int _columnAbsoluteIndex; - private final int _firstRowAbsoluteIndex; - - public ColumnVector(AreaEval tableArray, int columnIndex) { - _columnAbsoluteIndex = tableArray.getFirstColumn() + columnIndex; - if(!tableArray.containsColumn((short)_columnAbsoluteIndex)) { - int lastColIx = tableArray.getLastColumn() - tableArray.getFirstColumn(); - throw new IllegalArgumentException("Specified column index (" + columnIndex - + ") is outside the allowed range (0.." + lastColIx + ")"); - } - _tableArray = tableArray; - _size = tableArray.getLastRow() - tableArray.getFirstRow() + 1; - if(_size < 1) { - throw new RuntimeException("bad table array size zero"); - } - _firstRowAbsoluteIndex = tableArray.getFirstRow(); - } - - 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(_firstRowAbsoluteIndex + index, (short)_columnAbsoluteIndex); - } - public int getSize() { - return _size; - } - } - public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { Eval arg3 = null; switch(args.length) { @@ -93,7 +59,7 @@ public final class Vlookup implements Function { ValueEval lookupValue = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); AreaEval tableArray = LookupUtils.resolveTableArrayArg(args[1]); boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcCellRow, srcCellCol); - int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, new ColumnVector(tableArray, 0), isRangeLookup); + int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup); ValueEval veColIndex = OperandResolver.getSingleValue(args[2], srcCellRow, srcCellCol); int colIndex = LookupUtils.resolveRowOrColIndexArg(veColIndex); ValueVector resultCol = createResultColumnVector(tableArray, colIndex); @@ -113,11 +79,9 @@ public final class Vlookup implements Function { if(colIndex < 0) { throw EvaluationException.invalidValue(); } - int nCols = tableArray.getLastColumn() - tableArray.getFirstColumn() + 1; - - if(colIndex >= nCols) { + if(colIndex >= tableArray.getWidth()) { throw EvaluationException.invalidRef(); } - return new ColumnVector(tableArray, colIndex); + return LookupUtils.createColumnVector(tableArray, colIndex); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 7f8f7aa3b9..e5ece99943 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -296,7 +296,7 @@ public class HSSFCell implements Cell { if (cellType != this.cellType) { - frec = new FormulaRecordAggregate(new FormulaRecord(),null); + frec = new FormulaRecordAggregate(new FormulaRecord()); } else { @@ -592,41 +592,27 @@ public class HSSFCell implements Cell { int row=record.getRow(); short col=record.getColumn(); short styleIndex=record.getXFIndex(); - //Workbook.currentBook=book; - if (formula==null) { - setCellType(CELL_TYPE_BLANK,false,row,col,styleIndex); - } else { - setCellType(CELL_TYPE_FORMULA,false,row,col,styleIndex); - FormulaRecordAggregate rec = (FormulaRecordAggregate) record; - FormulaRecord frec = rec.getFormulaRecord(); - frec.setOptions(( short ) 2); - frec.setValue(0); - - //only set to default if there is no extended format index already set - if (rec.getXFIndex() == (short)0) rec.setXFIndex(( short ) 0x0f); - Ptg[] ptgs = FormulaParser.parse(formula, book); - int size = 0; - - // clear the Ptg Stack - for (int i=0, iSize=frec.getNumberOfExpressionTokens(); i<iSize; i++) { - frec.popExpressionToken(); - } - // fill the Ptg Stack with Ptgs of new formula - for (int k = 0; k < ptgs.length; k++) { - size += ptgs[ k ].getSize(); - frec.pushExpressionToken(ptgs[ k ]); - } - rec.getFormulaRecord().setExpressionLength(( short ) size); - //Workbook.currentBook = null; + if (formula==null) { + setCellType(CELL_TYPE_BLANK, false, row, col, styleIndex); + return; } + setCellType(CELL_TYPE_FORMULA, false, row, col, styleIndex); + FormulaRecordAggregate rec = (FormulaRecordAggregate) record; + FormulaRecord frec = rec.getFormulaRecord(); + frec.setOptions((short) 2); + frec.setValue(0); + + //only set to default if there is no extended format index already set + if (rec.getXFIndex() == (short)0) { + rec.setXFIndex((short) 0x0f); + } + Ptg[] ptgs = FormulaParser.parse(formula, book); + frec.setParsedExpression(ptgs); } public String getCellFormula() { - //Workbook.currentBook=book; - String retval = FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); - //Workbook.currentBook=null; - return retval; + return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); } |