Fix for bug noticed while investigating bugzilla 47048. Made INDEX() support missing args.
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@775360 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
df9cd1738e
commit
b92b2d556b
@ -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;
|
||||
@ -45,7 +47,6 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||
*/
|
||||
public final class Index implements Function {
|
||||
|
||||
// TODO - javadoc for interface method
|
||||
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
|
||||
int nArgs = args.length;
|
||||
if(nArgs < 2) {
|
||||
@ -69,11 +70,10 @@ public final class Index implements Function {
|
||||
|
||||
int rowIx = 0;
|
||||
int columnIx = 0;
|
||||
int areaIx = 0;
|
||||
boolean colArgWasPassed = false;
|
||||
try {
|
||||
switch(nArgs) {
|
||||
case 4:
|
||||
areaIx = convertIndexArgToZeroBase(args[3], srcCellRow, srcCellCol);
|
||||
throw new RuntimeException("Incomplete code" +
|
||||
" - don't know how to support the 'area_num' parameter yet)");
|
||||
// Excel expression might look like this "INDEX( (A1:B4, C3:D6, D2:E5 ), 1, 2, 3)
|
||||
@ -82,24 +82,32 @@ public final class Index implements Function {
|
||||
// The formula parser doesn't seem to support this yet. Not sure if the evaluator does either
|
||||
|
||||
case 3:
|
||||
columnIx = convertIndexArgToZeroBase(args[2], srcCellRow, srcCellCol);
|
||||
columnIx = resolveIndexArg(args[2], srcCellRow, srcCellCol);
|
||||
colArgWasPassed = true;
|
||||
case 2:
|
||||
rowIx = convertIndexArgToZeroBase(args[1], srcCellRow, srcCellCol);
|
||||
rowIx = resolveIndexArg(args[1], srcCellRow, srcCellCol);
|
||||
break;
|
||||
default:
|
||||
// too many arguments
|
||||
return ErrorEval.VALUE_INVALID;
|
||||
}
|
||||
return getValueFromArea(reference, rowIx, columnIx, nArgs);
|
||||
return getValueFromArea(reference, rowIx, columnIx, colArgWasPassed, srcCellRow, srcCellCol);
|
||||
} catch (EvaluationException e) {
|
||||
return e.getErrorEval();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nArgs - needed because error codes are slightly different when only 2 args are passed
|
||||
* @param colArgWasPassed <code>false</code> if the INDEX argument list had just 2 items
|
||||
* (exactly 1 comma). If anything is passed for the <tt>column_num</tt> argument
|
||||
* (including {@link BlankEval} or {@link MissingArgEval}) this parameter will be
|
||||
* <code>true</code>. 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;
|
||||
|
||||
@ -107,63 +115,85 @@ public final class Index implements Function {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -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.</p>
|
||||
*
|
||||
* This class contains just a few specific cases that directly invoke {@link Index},
|
||||
* with minimum overhead.<br/>
|
||||
* Another test: {@link TestIndexFunctionFromSpreadsheet} operates from a higher level
|
||||
* and has far greater coverage of input permutations.<br/>
|
||||
*
|
||||
* @author Josh Micich
|
||||
*/
|
||||
public final class TestIndex extends TestCase {
|
||||
|
||||
private static final Index FUNC_INST = new Index();
|
||||
private static final double[] TEST_VALUES0 = {
|
||||
1, 2,
|
||||
3, 4,
|
||||
@ -76,7 +84,32 @@ public final class TestIndex extends TestCase {
|
||||
args = new Eval[] { arg0, new NumberEval(rowNum), };
|
||||
}
|
||||
|
||||
double actual = NumericFunctionInvoker.invoke(new Index(), args);
|
||||
double actual = NumericFunctionInvoker.invoke(FUNC_INST, args);
|
||||
assertEquals(expectedResult, actual, 0D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests expressions like "INDEX(A1:C1,,2)".<br/>
|
||||
* This problem was found while fixing bug 47048 and is observable up to svn r773441.
|
||||
*/
|
||||
public void testMissingArg() {
|
||||
ValueEval[] values = {
|
||||
new NumberEval(25.0),
|
||||
new NumberEval(26.0),
|
||||
new NumberEval(28.0),
|
||||
};
|
||||
AreaEval arg0 = EvalFactory.createAreaEval("A10:C10", values);
|
||||
Eval[] args = new Eval[] { arg0, MissingArgEval.instance, new NumberEval(2), };
|
||||
Eval actualResult;
|
||||
try {
|
||||
actualResult = FUNC_INST.evaluate(args, 1, (short)1);
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getMessage().equals("Unexpected arg eval type (org.apache.poi.hssf.record.formula.eval.MissingArgEval")) {
|
||||
throw new AssertionFailedError("Identified bug 47048b - INDEX() should support missing-arg");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
assertEquals(NumberEval.class, actualResult.getClass());
|
||||
assertEquals(26.0, ((NumberEval)actualResult).getNumberValue(), 0.0);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user