diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Index.java b/src/java/org/apache/poi/hssf/record/formula/functions/Index.java index 1c3432225..371a06e30 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Index.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Index.java @@ -18,9 +18,11 @@ package org.apache.poi.hssf.record.formula.functions; import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.MissingArgEval; 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.ValueEval; @@ -28,7 +30,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; /** * Implementation for the Excel function INDEX *
- *
+ *
* Syntax :
* INDEX ( reference, row_num[, column_num [, area_num]])
* INDEX ( array, row_num[, column_num])
@@ -40,12 +42,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
*
false
if the INDEX argument list had just 2 items
+ * (exactly 1 comma). If anything is passed for the column_num argument
+ * (including {@link BlankEval} or {@link MissingArgEval}) this parameter will be
+ * true
. This parameter is needed because error codes are slightly
+ * different when only 2 args are passed.
*/
- private static ValueEval getValueFromArea(AreaEval ae, int pRowIx, int pColumnIx, int nArgs) throws EvaluationException {
+ private static ValueEval getValueFromArea(AreaEval ae, int pRowIx, int pColumnIx,
+ boolean colArgWasPassed, int srcRowIx, int srcColIx) throws EvaluationException {
+ boolean rowArgWasEmpty = pRowIx == 0;
+ boolean colArgWasEmpty = pColumnIx == 0;
int rowIx;
int columnIx;
-
+
// when the area ref is a single row or a single column,
// there are special rules for conversion of rowIx and columnIx
if (ae.isRow()) {
if (ae.isColumn()) {
- rowIx = pRowIx == -1 ? 0 : pRowIx;
- columnIx = pColumnIx == -1 ? 0 : pColumnIx;
+ // single cell ref
+ rowIx = rowArgWasEmpty ? 0 : pRowIx-1;
+ columnIx = colArgWasEmpty ? 0 : pColumnIx-1;
} else {
- if (nArgs == 2) {
- rowIx = 0;
- columnIx = pRowIx;
+ if (colArgWasPassed) {
+ rowIx = rowArgWasEmpty ? 0 : pRowIx-1;
+ columnIx = pColumnIx-1;
} else {
- rowIx = pRowIx == -1 ? 0 : pRowIx;
- columnIx = pColumnIx;
+ // special case - row arg seems to get used as the column index
+ rowIx = 0;
+ // transfer both the index value and the empty flag from 'row' to 'column':
+ columnIx = pRowIx-1;
+ colArgWasEmpty = rowArgWasEmpty;
}
}
- if (rowIx < -1 || columnIx < -1) {
- throw new EvaluationException(ErrorEval.VALUE_INVALID);
- }
} else if (ae.isColumn()) {
- if (nArgs == 2) {
- rowIx = pRowIx;
+ if (rowArgWasEmpty) {
+ rowIx = srcRowIx - ae.getFirstRow();
+ } else {
+ rowIx = pRowIx-1;
+ }
+ if (colArgWasEmpty) {
columnIx = 0;
} else {
- rowIx = pRowIx;
- columnIx = pColumnIx == -1 ? 0 : pColumnIx;
- }
- if (rowIx < -1 || columnIx < -1) {
- throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ columnIx = colArgWasEmpty ? 0 : pColumnIx-1;
}
} else {
- if (nArgs == 2) {
+ // ae is an area (not single row or column)
+ if (!colArgWasPassed) {
// always an error with 2-D area refs
- if (pRowIx < -1) {
- throw new EvaluationException(ErrorEval.VALUE_INVALID);
- }
- throw new EvaluationException(ErrorEval.REF_INVALID);
+ // Note - the type of error changes if the pRowArg is negative
+ throw new EvaluationException(pRowIx < 0 ? ErrorEval.VALUE_INVALID : ErrorEval.REF_INVALID);
}
// Normal case - area ref is 2-D, and both index args were provided
- rowIx = pRowIx;
- columnIx = pColumnIx;
+ // if either arg is missing (or blank) the logic is similar to OperandResolver.getSingleValue()
+ if (rowArgWasEmpty) {
+ rowIx = srcRowIx - ae.getFirstRow();
+ } else {
+ rowIx = pRowIx-1;
+ }
+ if (colArgWasEmpty) {
+ columnIx = srcColIx - ae.getFirstColumn();
+ } else {
+ columnIx = pColumnIx-1;
+ }
}
-
+
int width = ae.getWidth();
int height = ae.getHeight();
// Slightly irregular logic for bounds checking errors
- if (rowIx >= height || columnIx >= width) {
+ if (!rowArgWasEmpty && rowIx >= height || !colArgWasEmpty && columnIx >= width) {
+ // high bounds check fail gives #REF! if arg was explicitly passed
throw new EvaluationException(ErrorEval.REF_INVALID);
}
- if (rowIx < 0 || columnIx < 0) {
+ if (rowIx < 0 || columnIx < 0 || rowIx >= height || columnIx >= width) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
return ae.getRelativeValue(rowIx, columnIx);
}
+
/**
- * takes a NumberEval representing a 1-based index and returns the zero-based int value
+ * @param arg a 1-based index.
+ * @return the resolved 1-based index. Zero if the arg was missing or blank
+ * @throws EvaluationException if the arg is an error value evaluates to a negative numeric value
*/
- private static int convertIndexArgToZeroBase(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
-
+ private static int resolveIndexArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
+
ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
- int oneBasedVal = OperandResolver.coerceValueToInt(ev);
- return oneBasedVal - 1;
+ if (ev == MissingArgEval.instance) {
+ return 0;
+ }
+ if (ev == BlankEval.INSTANCE) {
+ return 0;
+ }
+ int result = OperandResolver.coerceValueToInt(ev);
+ if (result < 0) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ return result;
}
}
diff --git a/src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls b/src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls
index 9dcde6177..bf0b23acc 100644
Binary files a/src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls and b/src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java
index 95355733d..764e30cd9 100755
--- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndex.java
@@ -17,20 +17,28 @@
package org.apache.poi.hssf.record.formula.functions;
+import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
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.MissingArgEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
- * Tests for the INDEX() function
- *
+ * Tests for the INDEX() function.
+ *
+ * This class contains just a few specific cases that directly invoke {@link Index},
+ * with minimum overhead.