Index: src/java/org/apache/poi/hssf/model/FormulaParser.java
===================================================================
--- src/java/org/apache/poi/hssf/model/FormulaParser.java (revision 618865)
+++ src/java/org/apache/poi/hssf/model/FormulaParser.java (working copy)
@@ -947,20 +947,28 @@
// Excel allows to have AttrPtg at position 0 (such as Blanks) which
// do not have any operands. Skip them.
- stack.push(ptgs[0].toFormulaString(book));
+ int i;
+ if(ptgs[0] instanceof AttrPtg) {
+ // TODO -this requirement is unclear and is not addressed by any junits
+ stack.push(ptgs[0].toFormulaString(book));
+ i=1;
+ } else {
+ i=0;
+ }
- for (int i = 1; i < ptgs.length; i++) {
- if (! (ptgs[i] instanceof OperationPtg)) {
- stack.push(ptgs[i].toFormulaString(book));
+ for ( ; i < ptgs.length; i++) {
+ Ptg ptg = ptgs[i];
+ if (! (ptg instanceof OperationPtg)) {
+ stack.push(ptg.toFormulaString(book));
continue;
}
- if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
- ifptg = (AttrPtg) ptgs[i];
+ if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
+ ifptg = (AttrPtg) ptg;
continue;
}
- final OperationPtg o = (OperationPtg) ptgs[i];
+ final OperationPtg o = (OperationPtg) ptg;
final String[] operands = new String[o.getNumberOfOperands()];
for (int j = operands.length; j > 0; j--) {
Index: src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
===================================================================
--- src/java/org/apache/poi/hssf/record/formula/FuncPtg.java (revision 618865)
+++ src/java/org/apache/poi/hssf/record/formula/FuncPtg.java (working copy)
@@ -63,6 +63,10 @@
}
}
+ public FuncPtg(int functionIndex, int numberOfParameters) {
+ field_2_fnc_index = (short) functionIndex;
+ numParams = numberOfParameters;
+ }
public void writeBytes(byte[] array, int offset) {
array[offset+0]= (byte) (sid + ptgClass);
Index: src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java (working copy)
@@ -173,7 +173,7 @@
* formula evaluated.
*/
public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) {
- return new FormulaParser(formula, workbook.getWorkbook());
+ return new FormulaParser(formula, workbook.getWorkbook());
}
/**
@@ -286,19 +286,19 @@
CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook));
switch (cv.getCellType()) {
case HSSFCell.CELL_TYPE_BOOLEAN:
- cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN);
+ cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN);
cell.setCellValue(cv.getBooleanValue());
break;
case HSSFCell.CELL_TYPE_ERROR:
- cell.setCellType(HSSFCell.CELL_TYPE_ERROR);
+ cell.setCellType(HSSFCell.CELL_TYPE_ERROR);
cell.setCellValue(cv.getErrorValue());
break;
case HSSFCell.CELL_TYPE_NUMERIC:
- cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
+ cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
cell.setCellValue(cv.getNumberValue());
break;
case HSSFCell.CELL_TYPE_STRING:
- cell.setCellType(HSSFCell.CELL_TYPE_STRING);
+ cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cell.setCellValue(cv.getRichTextStringValue());
break;
case HSSFCell.CELL_TYPE_BLANK:
@@ -337,6 +337,11 @@
else if (eval instanceof BlankEval) {
retval = new CellValue(HSSFCell.CELL_TYPE_BLANK);
}
+ else if (eval instanceof ErrorEval) {
+ retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
+ retval.setErrorValue((byte)((ErrorEval)eval).getErrorCode());
+// retval.setRichTextStringValue(new HSSFRichTextString("#An error occurred. check cell.getErrorCode()"));
+ }
else {
retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
}
@@ -401,7 +406,7 @@
short rownum = ptg.getRow();
HSSFRow row = sheet.getRow(rownum);
HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
- pushRef2DEval(ptg, stack, cell, row, sheet, workbook);
+ stack.push(createRef2DEval(ptg, cell, row, sheet, workbook));
}
else if (ptgs[i] instanceof Ref3DPtg) {
Ref3DPtg ptg = (Ref3DPtg) ptgs[i];
@@ -411,7 +416,7 @@
HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex()));
HSSFRow row = xsheet.getRow(rownum);
HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
- pushRef3DEval(ptg, stack, cell, row, xsheet, workbook);
+ stack.push(createRef3DEval(ptg, cell, row, xsheet, workbook));
}
else if (ptgs[i] instanceof AreaPtg) {
AreaPtg ap = (AreaPtg) ptgs[i];
@@ -544,104 +549,77 @@
* @param workbook
*/
protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
- ValueEval retval = BlankEval.INSTANCE;
- if (cell != null) {
- switch (cell.getCellType()) {
+
+ if (cell == null) {
+ return BlankEval.INSTANCE;
+ }
+ switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
- retval = new NumberEval(cell.getNumericCellValue());
- break;
+ return new NumberEval(cell.getNumericCellValue());
case HSSFCell.CELL_TYPE_STRING:
- retval = new StringEval(cell.getRichStringCellValue().getString());
- break;
+ return new StringEval(cell.getRichStringCellValue().getString());
case HSSFCell.CELL_TYPE_FORMULA:
- retval = internalEvaluate(cell, row, sheet, workbook);
- break;
+ return internalEvaluate(cell, row, sheet, workbook);
case HSSFCell.CELL_TYPE_BOOLEAN:
- retval = cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE;
- break;
+ return BoolEval.valueOf(cell.getBooleanCellValue());
case HSSFCell.CELL_TYPE_BLANK:
- retval = BlankEval.INSTANCE;
- break;
+ return BlankEval.INSTANCE;
case HSSFCell.CELL_TYPE_ERROR:
- retval = ErrorEval.UNKNOWN_ERROR; // TODO: think about this...
- break;
- }
+ return ErrorEval.valueOf(cell.getErrorCellValue());
}
- return retval;
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
- * create a Ref2DEval for ReferencePtg and push it on the stack.
+ * Creates a Ref2DEval for ReferencePtg.
* Non existent cells are treated as RefEvals containing BlankEval.
- * @param ptg
- * @param stack
- * @param cell
- * @param sheet
- * @param workbook
*/
- protected static void pushRef2DEval(ReferencePtg ptg, Stack stack,
- HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
- if (cell != null)
- switch (cell.getCellType()) {
+ private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell,
+ HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
+ if (cell == null) {
+ return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
+ }
+
+ switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
- stack.push(new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
- break;
+ return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
case HSSFCell.CELL_TYPE_STRING:
- stack.push(new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
- break;
+ return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
case HSSFCell.CELL_TYPE_FORMULA:
- stack.push(new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
- break;
+ return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
case HSSFCell.CELL_TYPE_BOOLEAN:
- stack.push(new Ref2DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
- break;
+ return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
case HSSFCell.CELL_TYPE_BLANK:
- stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
- break;
+ return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
case HSSFCell.CELL_TYPE_ERROR:
- stack.push(new Ref2DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
- break;
- }
- else {
- stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
+ return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
}
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
- * create a Ref3DEval for Ref3DPtg and push it on the stack.
- *
- * @param ptg
- * @param stack
- * @param cell
- * @param sheet
- * @param workbook
+ * create a Ref3DEval for Ref3DPtg.
*/
- protected static void pushRef3DEval(Ref3DPtg ptg, Stack stack, HSSFCell cell,
+ private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell,
HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
- if (cell != null)
- switch (cell.getCellType()) {
+ if (cell == null) {
+ return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
+ }
+ switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
- stack.push(new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
- break;
+ return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
case HSSFCell.CELL_TYPE_STRING:
- stack.push(new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
- break;
+ return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
case HSSFCell.CELL_TYPE_FORMULA:
- stack.push(new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
- break;
+ return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
case HSSFCell.CELL_TYPE_BOOLEAN:
- stack.push(new Ref3DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
- break;
+ return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
case HSSFCell.CELL_TYPE_BLANK:
- stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
- break;
+ return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
case HSSFCell.CELL_TYPE_ERROR:
- stack.push(new Ref3DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
- break;
- }
- else {
- stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
+ return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
}
+ throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
@@ -726,15 +704,15 @@
/**
* @return Returns the richTextStringValue.
*/
- public HSSFRichTextString getRichTextStringValue() {
- return richTextStringValue;
- }
+ public HSSFRichTextString getRichTextStringValue() {
+ return richTextStringValue;
+ }
/**
* @param richTextStringValue The richTextStringValue to set.
*/
- public void setRichTextStringValue(HSSFRichTextString richTextStringValue) {
- this.richTextStringValue = richTextStringValue;
- }
+ public void setRichTextStringValue(HSSFRichTextString richTextStringValue) {
+ this.richTextStringValue = richTextStringValue;
+ }
}
/**
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java (working copy)
@@ -24,31 +24,91 @@
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
*
*/
-public class ErrorEval implements ValueEval {
+public final class ErrorEval implements ValueEval {
+ /**
+ * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6)
+ */
+ private static final class ErrorCode {
+ /** #NULL! - Intersection of two cell ranges is empty */
+ public static final int NULL = 0x00;
+ /** #DIV/0! - Division by zero */
+ public static final int DIV_0 = 0x07;
+ /** #VALUE! - Wrong type of operand */
+ public static final int VALUE = 0x0F;
+ /** #REF! - Illegal or deleted cell reference */
+ public static final int REF = 0x17;
+ /** #NAME? - Wrong function or range name */
+ public static final int NAME = 0x1D;
+ /** #NUM! - Value range overflow */
+ public static final int NUM = 0x24;
+ /** #N/A - Argument or function not available */
+ public static final int N_A = 0x2A;
+
+ public static final String getText(int errorCode) {
+ switch(errorCode) {
+ case NULL: return "#NULL!";
+ case DIV_0: return "#DIV/0!";
+ case VALUE: return "#VALUE!";
+ case REF: return "#REF!";
+ case NAME: return "#NAME?";
+ case NUM: return "#NUM!";
+ case N_A: return "#N/A";
+ }
+ return "???";
+ }
+ }
- private int errorCode;
+ /** #NULL! - Intersection of two cell ranges is empty */
+ public static final ErrorEval NULL_INTERSECTION = new ErrorEval(ErrorCode.NULL);
+ /** #DIV/0! - Division by zero */
+ public static final ErrorEval DIV_ZERO = new ErrorEval(ErrorCode.DIV_0);
+ /** #VALUE! - Wrong type of operand */
+ public static final ErrorEval VALUE_INVALID = new ErrorEval(ErrorCode.VALUE);
+ /** #REF! - Illegal or deleted cell reference */
+ public static final ErrorEval REF_INVALID = new ErrorEval(ErrorCode.REF);
+ /** #NAME? - Wrong function or range name */
+ public static final ErrorEval NAME_INVALID = new ErrorEval(ErrorCode.NAME);
+ /** #NUM! - Value range overflow */
+ public static final ErrorEval NUM_ERROR = new ErrorEval(ErrorCode.NUM);
+ /** #N/A - Argument or function not available */
+ public static final ErrorEval NA = new ErrorEval(ErrorCode.N_A);
-
- public static final ErrorEval NAME_INVALID = new ErrorEval(525);
-
- public static final ErrorEval VALUE_INVALID = new ErrorEval(519);
-
- // Non std error codes
+ /**
+ * Translates an Excel internal error code into the corresponding POI ErrorEval instance
+ * @param errorCode
+ */
+ public static ErrorEval valueOf(int errorCode) {
+ switch(errorCode) {
+ case ErrorCode.NULL: return NULL_INTERSECTION;
+ case ErrorCode.DIV_0: return DIV_ZERO;
+ case ErrorCode.VALUE: return VALUE_INVALID;
+// case ErrorCode.REF: return REF_INVALID;
+ case ErrorCode.REF: return UNKNOWN_ERROR;
+ case ErrorCode.NAME: return NAME_INVALID;
+ case ErrorCode.NUM: return NUM_ERROR;
+ case ErrorCode.N_A: return NA;
+
+ // these cases probably shouldn't be coming through here
+ // but (as of Jan-2008) a lot of code depends on it.
+// case -20: return UNKNOWN_ERROR;
+// case -30: return FUNCTION_NOT_IMPLEMENTED;
+// case -60: return CIRCULAR_REF_ERROR;
+ }
+ throw new RuntimeException("Unexpected error code (" + errorCode + ")");
+ }
+
+ // POI internal error codes
public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
-
public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
+ // Note - Excel does not seem to represent this condition with an error code
+ public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60);
- public static final ErrorEval REF_INVALID = new ErrorEval(-40);
- public static final ErrorEval NA = new ErrorEval(-50);
-
- public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60);
-
- public static final ErrorEval DIV_ZERO = new ErrorEval(-70);
-
- public static final ErrorEval NUM_ERROR = new ErrorEval(-80);
-
+ private int errorCode;
+ /**
+ * @param errorCode an 8-bit value
+ */
private ErrorEval(int errorCode) {
this.errorCode = errorCode;
}
@@ -56,9 +116,11 @@
public int getErrorCode() {
return errorCode;
}
-
- public String getStringValue() {
- return "Err:" + Integer.toString(errorCode);
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(ErrorCode.getText(errorCode));
+ sb.append("]");
+ return sb.toString();
}
-
}
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java (working copy)
@@ -34,6 +34,16 @@
public static final BoolEval FALSE = new BoolEval(false);
public static final BoolEval TRUE = new BoolEval(true);
+
+ /**
+ * Convenience method for the following:
+ * (b ? BoolEval.TRUE : BoolEval.FALSE)
+ * @return a BoolEval instance representing b.
+ */
+ public static final BoolEval valueOf(boolean b) {
+ // TODO - find / replace all occurrences
+ return b ? TRUE : FALSE;
+ }
public BoolEval(Ptg ptg) {
this.value = ((BoolPtg) ptg).getValue();
@@ -48,10 +58,17 @@
}
public double getNumberValue() {
- return value ? (short) 1 : (short) 0;
+ return value ? 1 : 0;
}
public String getStringValue() {
return value ? "TRUE" : "FALSE";
}
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getStringValue());
+ sb.append("]");
+ return sb.toString();
+ }
}
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java (working copy)
@@ -58,6 +58,9 @@
// ));
+ /**
+ * called by reflection
+ */
public UnaryPlusEval(Ptg ptg) {
this.delegate = (UnaryPlusPtg) ptg;
}
@@ -108,7 +111,7 @@
}
else if (ae.isColumn()) {
if (ae.containsRow(srcRow)) {
- ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
+ ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn());
if (ve instanceof RefEval) {
ve = ((RefEval) ve).getInnerValueEval();
}
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java (working copy)
@@ -22,28 +22,34 @@
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-public class T implements Function {
-
-
+public final class T implements Function {
- public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
- ValueEval retval = null;
- switch (operands.length) {
- default:
- retval = ErrorEval.VALUE_INVALID;
- break;
- case 1:
- if (operands[0] instanceof StringEval
- || operands[0] instanceof ErrorEval) {
- retval = (ValueEval) operands[0];
- }
- else if (operands[0] instanceof ErrorEval) {
- retval = StringEval.EMPTY_INSTANCE;
- }
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ switch (args.length) {
+ default:
+ return ErrorEval.VALUE_INVALID;
+ case 1:
+ break;
}
- return retval;
+ Eval arg = args[0];
+ if (arg instanceof RefEval) {
+ RefEval re = (RefEval) arg;
+ arg = re.getInnerValueEval();
+ }
+
+ if (arg instanceof StringEval) {
+ // Text values are returned unmodified
+ return arg;
+ }
+
+ if (arg instanceof ErrorEval) {
+ // Error values also returned unmodified
+ return arg;
+ }
+ // for all other argument types the result is empty string
+ return StringEval.EMPTY_INSTANCE;
}
}
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java (working copy)
@@ -40,6 +40,9 @@
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+ if(ve instanceof ErrorEval) {
+ return ve;
+ }
if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve;
d0 = ne.getNumberValue();
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java (working copy)
@@ -40,6 +40,9 @@
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+ if(ve instanceof ErrorEval) {
+ return ve;
+ }
if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve;
d0 = ne.getNumberValue();
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java (working copy)
@@ -14,16 +14,228 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
package org.apache.poi.hssf.record.formula.functions;
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+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 >
- *
+ * Implementation for the Excel function SUMPRODUCT
+ *
+ * Syntax :
+ * SUMPRODUCT ( array1[, array2[, array3[, ...]]])
+ *
+ * array1, ... arrayN | typically area references,
+ * possibly cell references or scalar values |
+ *
+ *
+ * Let An(i,j) represent the element in the ith row jth column
+ * of the nth array
+ * Assuming each array has the same dimensions (W, H), the result is defined as:
+ * SUMPRODUCT = Σi: 1..H
+ * ( Σj: 1..W
+ * ( Πn: 1..N
+ * An(i,j)
+ * )
+ * )
+ *
+ * @author Josh Micich
*/
-public class Sumproduct extends NotImplementedFunction {
+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) {
+
+ int maxN = args.length;
+
+ if(maxN < 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ Eval firstArg = args[0];
+ try {
+ if(firstArg instanceof NumericValueEval) {
+ return evaluateSingleProduct(args);
+ }
+ if(firstArg instanceof RefEval) {
+ return evaluateSingleProduct(args);
+ }
+ if(firstArg instanceof AreaEval) {
+ AreaEval ae = (AreaEval) firstArg;
+ if(ae.isRow() && ae.isColumn()) {
+ return evaluateSingleProduct(args);
+ }
+ return evaluateAreaSumProduct(args);
+ }
+ } catch (EvalEx e) {
+ return e.getError();
+ }
+ throw new RuntimeException("Invalid arg type for SUMPRODUCT: ("
+ + firstArg.getClass().getName() + ")");
+ }
+
+ private Eval evaluateSingleProduct(Eval[] evalArgs) throws EvalEx {
+ int maxN = evalArgs.length;
+
+ double term = 1D;
+ for(int n=0; ndouble value for the specified ValueEval
.
+ * @param isScalarProduct false
for SUMPRODUCTs over area refs.
+ * @throws EvalEx if ve
represents an error value.
+ *
+ * Note - string values and empty cells are interpreted differently depending on
+ * isScalarProduct
. 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 {
+
+ 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);
+ }
+ return 0;
+ }
+
+ if(ve instanceof ErrorEval) {
+ throw new EvalEx((ErrorEval)ve);
+ }
+ if(ve instanceof StringEval) {
+ if(isScalarProduct) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ // Note for area SUMPRODUCTs, string values are interpreted as zero
+ // even if they would parse as valid numeric values
+ return 0;
+ }
+ if(ve instanceof NumericValueEval) {
+ NumericValueEval nve = (NumericValueEval) ve;
+ return nve.getNumberValue();
+ }
+ throw new RuntimeException("Unexpected value eval class ("
+ + ve.getClass().getName() + ")");
+ }
}
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Na.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Na.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Na.java (working copy)
@@ -14,12 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 15, 2005
- *
- */
+
+
package org.apache.poi.hssf.record.formula.functions;
-public class Na extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+/**
+ * Implementation of Excel function NA()
+ *
+ * @author Josh Micich
+ */
+public final class Na implements Function {
+
+ public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+ return ErrorEval.NA;
+ }
+
}
Index: src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
===================================================================
--- src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java (revision 618865)
+++ src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java (working copy)
@@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/*
- * Created on May 22, 2005
- *
- */
+
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
@@ -36,7 +33,50 @@
* where the order of operands does not matter
*/
public abstract class MultiOperandNumericFunction extends NumericFunction {
+ static final double[] EMPTY_DOUBLE_ARRAY = { };
+
+ private static class DoubleList {
+ private double[] _array;
+ private int _count;
+ public DoubleList() {
+ _array = new double[8];
+ _count = 0;
+ }
+
+ public double[] toArray() {
+ if(_count < 1) {
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ double[] result = new double[_count];
+ System.arraycopy(_array, 0, result, 0, _count);
+ return result;
+ }
+
+ public void add(double[] values) {
+ int addLen = values.length;
+ ensureCapacity(_count + addLen);
+ System.arraycopy(values, 0, _array, _count, addLen);
+ _count += addLen;
+ }
+
+ private void ensureCapacity(int reqSize) {
+ if(reqSize > _array.length) {
+ int newSize = reqSize * 3 / 2; // grow with 50% extra
+ double[] newArr = new double[newSize];
+ System.arraycopy(_array, 0, newArr, 0, _count);
+ _array = newArr;
+ }
+ }
+
+ public void add(double value) {
+ ensureCapacity(_count + 1);
+ _array[_count] = value;
+ _count++;
+ }
+ }
+
+
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (
ValueEvalToNumericXlator.BOOL_IS_PARSED
@@ -76,40 +116,26 @@
* from among the list of operands. Blanks and Blank equivalent cells
* are ignored. Error operands or cells containing operands of type
* that are considered invalid and would result in #VALUE! error in
- * excel cause this function to return null.
+ * excel cause this function to return null
.
*
* @param operands
* @param srcRow
* @param srcCol
*/
protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) {
- double[] retval = new double[30];
- int count = 0;
+ if (operands.length > getMaxNumOperands()) {
+ return null;
+ }
+ DoubleList retval = new DoubleList();
- outer: do { // goto simulator loop
- if (operands.length > getMaxNumOperands()) {
- break outer;
+ for (int i=0, iSize=operands.length; i indicate to calling subclass that error occurred
- break;
+ return null; // indicate to calling subclass that error occurred
}
}
+ return retval.toArray();
}
- else { // for ValueEvals other than AreaEval
- retval = new double[1];
- ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
-
- if (ve instanceof NumericValueEval) {
- NumericValueEval nve = (NumericValueEval) ve;
- retval = putInArray(retval, count++, nve.getNumberValue());
- }
- else if (ve instanceof BlankEval) {} // ignore operand
- else {
- retval = null; // null => indicate to calling subclass that error occurred
- }
- }
- if (retval != null && retval.length >= 1) {
- double[] temp = retval;
- retval = new double[count];
- System.arraycopy(temp, 0, retval, 0, count);
+ // for ValueEvals other than AreaEval
+ ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
+
+ if (ve instanceof NumericValueEval) {
+ NumericValueEval nve = (NumericValueEval) ve;
+ return new double[] { nve.getNumberValue(), };
}
- return retval;
+ if (ve instanceof BlankEval) {
+ // ignore blanks
+ return EMPTY_DOUBLE_ARRAY;
+ }
+ return null;
}
/**
- * puts d at position pos in array arr. If pos is greater than arr, the
- * array is dynamically resized (using a simple doubling rule).
- * @param arr
- * @param pos
- * @param d
+ * Ensures that a two dimensional array has all sub-arrays present and the same length
+ * @return false
if any sub-array is missing, or is of different length
*/
- private static double[] putInArray(double[] arr, int pos, double d) {
- double[] tarr = arr;
- while (pos >= arr.length) {
- arr = new double[arr.length << 1];
+ protected static final boolean areSubArraysConsistent(double[][] values) {
+
+ if (values == null || values.length < 1) {
+ // TODO this doesn't seem right. Fix or add comment.
+ return true;
}
- if (tarr.length != arr.length) {
- System.arraycopy(tarr, 0, arr, 0, tarr.length);
+
+ if (values[0] == null) {
+ return false;
}
- arr[pos] = d;
- return arr;
- }
-
- private static double[] putInArray(double[] arr, int pos, double[] d) {
- double[] tarr = arr;
- while (pos+d.length >= arr.length) {
- arr = new double[arr.length << 1];
+ int outerMax = values.length;
+ int innerMax = values[0].length;
+ for (int i=1; i 0) {
- if (values[0] == null)
- break outer;
- int len = values[0].length;
- for (int i=1, iSize=values.length; i
+ *
+ * Syntax:
+ * MATCH(lookup_value, lookup_array, match_type)
+ *
+ * Returns a 1-based index specifying at what position in the lookup_array the specified
+ * lookup_value is found.
+ *
+ * Specific matching behaviour can be modified with the optional match_type parameter.
+ *
+ *
+ * Value | Matching Behaviour |
+ * 1 | (default) find the largest value that is less than or equal to lookup_value.
+ * The lookup_array must be in ascending order*. |
+ * 0 | find the first value that is exactly equal to lookup_value.
+ * The lookup_array can be in any order. |
+ * -1 | find the smallest value that is greater than or equal to lookup_value.
+ * The lookup_array must be in descending order*. |
+ *
+ *
+ * * Note regarding order - For the match_type 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.
+ * The (ascending) sort order expected by MATCH() is:
+ * numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)
+ * 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 {
+
+ 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) {
+
+ double match_type = 1; // default
+
+ switch(args.length) {
+ case 3:
+ try {
+ match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
+ } catch (EvalEx e) {
+ // Excel/MATCH() seems to have slightly abnormal handling of errors with
+ // the last parameter. Errors do not propagate up. Every error gets
+ // translated into #REF!
+ return ErrorEval.REF_INVALID;
+ }
+ case 2:
+ break;
+ default:
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ boolean matchExact = match_type == 0;
+ // Note - Excel does not strictly require -1 and +1
+ boolean findLargestLessThanOrEqual = match_type > 0;
+
+
+ try {
+ ValueEval lookupValue = evaluateLookupValue(args[0], srcCellRow, srcCellCol);
+ ValueEval[] lookupRange = evaluateLookupRange(args[1]);
+ int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
+ return new NumberEval(index + 1); // +1 to convert to 1-based
+ } catch (EvalEx e) {
+ return e.getError();
+ }
+ }
+
+ private static ValueEval chooseSingleElementFromArea(AreaEval ae,
+ int srcCellRow, short srcCellCol) throws EvalEx {
+ if (ae.isColumn()) {
+ if(ae.isRow()) {
+ return ae.getValues()[0];
+ }
+ if(!ae.containsRow(srcCellRow)) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ return ae.getValueAt(srcCellRow, ae.getFirstColumn());
+ }
+ if(!ae.isRow()) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ if(!ae.containsColumn(srcCellCol)) {
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ return ae.getValueAt(ae.getFirstRow(), srcCellCol);
+
+ }
+
+ private static ValueEval evaluateLookupValue(Eval eval, int srcCellRow, short srcCellCol)
+ throws EvalEx {
+ if (eval instanceof RefEval) {
+ RefEval re = (RefEval) eval;
+ return re.getInnerValueEval();
+ }
+ if (eval instanceof AreaEval) {
+ return chooseSingleElementFromArea((AreaEval) eval, srcCellRow, srcCellCol);
+ }
+ if (eval instanceof ValueEval) {
+ return (ValueEval) eval;
+ }
+ throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+ }
+
+
+ private static ValueEval[] evaluateLookupRange(Eval eval) throws EvalEx {
+ if (eval instanceof RefEval) {
+ RefEval re = (RefEval) eval;
+ return new ValueEval[] { re.getInnerValueEval(), };
+ }
+ if (eval instanceof AreaEval) {
+ AreaEval ae = (AreaEval) eval;
+ if(!ae.isColumn() && !ae.isRow()) {
+ throw new EvalEx(ErrorEval.NA);
+ }
+ return ae.getValues();
+ }
+
+ // Error handling for lookup_range arg is also unusual
+ if(eval instanceof NumericValueEval) {
+ throw new EvalEx(ErrorEval.NA);
+ }
+ if (eval instanceof StringEval) {
+ StringEval se = (StringEval) eval;
+ Double d = parseDouble(se.getStringValue());
+ if(d == null) {
+ // plain string
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ // else looks like a number
+ throw new EvalEx(ErrorEval.NA);
+ }
+ throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+ }
+
+
+ private static Double parseDouble(String stringValue) {
+ // TODO find better home for parseDouble
+ return Countif.parseDouble(stringValue);
+ }
+
+
+
+ private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol)
+ throws EvalEx {
+ Eval match_type = arg;
+ if(arg instanceof AreaReference) {
+ AreaEval ae = (AreaEval) arg;
+ // an area ref can work as a scalar value if it is 1x1
+ if(ae.isColumn() && ae.isRow()) {
+ match_type = ae.getValues()[0];
+ } else {
+ match_type = chooseSingleElementFromArea(ae, srcCellRow, srcCellCol);
+ }
+ }
+
+ if(match_type instanceof RefEval) {
+ RefEval re = (RefEval) match_type;
+ match_type = re.getInnerValueEval();
+ }
+ if(match_type instanceof ErrorEval) {
+ throw new EvalEx((ErrorEval)match_type);
+ }
+ if(match_type instanceof NumericValueEval) {
+ NumericValueEval ne = (NumericValueEval) match_type;
+ return ne.getNumberValue();
+ }
+ if (match_type instanceof StringEval) {
+ StringEval se = (StringEval) match_type;
+ Double d = parseDouble(se.getStringValue());
+ if(d == null) {
+ // plain string
+ throw new EvalEx(ErrorEval.VALUE_INVALID);
+ }
+ // if the string parses as a number, it is ok
+ return d.doubleValue();
+ }
+ throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
+ }
+
+ /**
+ * @return zero based index
+ */
+ private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
+ boolean matchExact, boolean findLargestLessThanOrEqual) throws EvalEx {
+ // TODO - wildcard matching when matchExact and lookupValue is text containing * or ?
+ if(matchExact) {
+ for (int i = 0; i < lookupRange.length; i++) {
+ ValueEval lri = lookupRange[i];
+ if(lri.getClass() != lookupValue.getClass()) {
+ continue;
+ }
+ if(compareValues(lookupValue, lri) == 0) {
+ return i;
+ }
+ }
+ } else {
+ // Note - backward iteration
+ if(findLargestLessThanOrEqual) {
+ for (int i = lookupRange.length - 1; i>=0; i--) {
+ ValueEval lri = lookupRange[i];
+ if(lri.getClass() != lookupValue.getClass()) {
+ continue;
+ }
+ int cmp = compareValues(lookupValue, lri);
+ if(cmp == 0) {
+ return i;
+ }
+ if(cmp > 0) {
+ return i;
+ }
+ }
+ } else {
+ // find smallest greater than or equal to
+ for (int i = 0; i 0) {
+ if(i<1) {
+ throw new EvalEx(ErrorEval.NA);
+ }
+ return i-1;
+ }
+ }
+
+ }
+ }
+
+ throw new EvalEx(ErrorEval.NA);
+ }
+
+
+ /**
+ * This method can only compare a pair of NumericValueEvals, StringEvals
+ * or BoolEvals
+ * @return negative for a<b, positive for a>b and 0 for a = b
+ */
+ private static int compareValues(ValueEval a, ValueEval b) {
+ if (a instanceof StringEval) {
+ StringEval sa = (StringEval) a;
+ StringEval sb = (StringEval) b;
+ return sa.getStringValue().compareToIgnoreCase(sb.getStringValue());
+ }
+ if (a instanceof NumericValueEval) {
+ NumericValueEval na = (NumericValueEval) a;
+ NumericValueEval nb = (NumericValueEval) b;
+ return Double.compare(na.getNumberValue(), nb.getNumberValue());
+ }
+ if (a instanceof BoolEval) {
+ boolean ba = ((BoolEval) a).getBooleanValue();
+ boolean bb = ((BoolEval) b).getBooleanValue();
+ if(ba == bb) {
+ return 0;
+ }
+ // TRUE > FALSE
+ if(ba) {
+ return +1;
+ }
+ return -1;
+ }
+ throw new RuntimeException("bad eval type (" + a.getClass().getName() + ")");
+ }
}
Index: src/scratchpad/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
===================================================================
--- src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java (revision 618865)
+++ src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java (working copy)
@@ -36,8 +36,11 @@
result.addTestSuite(TestFinanceLib.class);
result.addTestSuite(TestIndex.class);
result.addTestSuite(TestMathX.class);
+ result.addTestSuite(TestMatch.class);
result.addTestSuite(TestRowCol.class);
+ result.addTestSuite(TestSumproduct.class);
result.addTestSuite(TestStatsLib.class);
+ result.addTestSuite(TestTFunc.class);
return result;
}
Index: src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
===================================================================
--- src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java (revision 618865)
+++ src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java (working copy)
@@ -23,13 +23,14 @@
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.OperationEval;
/**
* Test helper class for invoking functions with numeric results.
*
* @author Josh Micich
*/
-final class NumericFunctionInvoker {
+public final class NumericFunctionInvoker {
private NumericFunctionInvoker() {
// no instances of this class
@@ -61,11 +62,35 @@
}
/**
+ * Invokes the specified operator with the arguments.
+ *
+ * This method cannot be used for confirming error return codes. Any non-numeric evaluation
+ * result causes the current junit test to fail.
+ */
+ public static double invoke(OperationEval f, Eval[] args, int srcCellRow, int srcCellCol) {
+ try {
+ return invokeInternal(f, args, srcCellRow, srcCellCol);
+ } catch (NumericEvalEx e) {
+ throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName()
+ + ") failed: " + e.getMessage());
+ }
+
+ }
+ /**
* Formats nicer error messages for the junit output
*/
- private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
+ private static double invokeInternal(Object target, Eval[] args, int srcCellRow, int srcCellCol)
throws NumericEvalEx {
- Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
+ Eval evalResult;
+ // TODO - make OperationEval extend Function
+ if (target instanceof Function) {
+ Function ff = (Function) target;
+ evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
+ } else {
+ OperationEval ff = (OperationEval) target;
+ evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
+ }
+
if(evalResult == null) {
throw new NumericEvalEx("Result object was null");
}
@@ -89,6 +114,9 @@
if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
return "Unknown error";
}
+ if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) {
+ return "Error code: #VALUE! (invalid value)";
+ }
return "Error code=" + ee.getErrorCode();
}
private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {
Index: src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
===================================================================
--- src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java (revision 618865)
+++ src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java (working copy)
@@ -18,6 +18,8 @@
package org.apache.poi.hssf.model;
+import java.util.List;
+
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
@@ -26,6 +28,7 @@
import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.DividePtg;
import org.apache.poi.hssf.record.formula.EqualPtg;
+import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.LessEqualPtg;
@@ -397,7 +400,7 @@
public void testUnderscore() {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -438,7 +441,7 @@
public void testExponentialInSheet() throws Exception {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -514,7 +517,7 @@
public void testNumbers() {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -553,7 +556,7 @@
public void testRanges() {
HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");;
+ wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@@ -571,5 +574,19 @@
cell.setCellFormula("A1...A2");
formula = cell.getCellFormula();
assertEquals("A1:A2", formula);
- }
+ }
+
+ /**
+ * Test for bug observable at svn revision 618865 (5-Feb-2008)
+ * a formula consisting of a single no-arg function got rendered without the function braces
+ */
+ public void testToFormulaStringZeroArgFunction() {
+
+ Workbook book = Workbook.createWorkbook(); // not really used in this test
+
+ Ptg[] ptgs = {
+ new FuncPtg(10, 0),
+ };
+ assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs));
+ }
}
Index: src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream