Fixed special cases of INDEX function (single columns / single rows, and errors)

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@693658 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-09-09 23:46:46 +00:00
parent 2640d092ea
commit 27c3aeb55f
7 changed files with 319 additions and 5 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">Fixed special cases of INDEX function (single column/single row, errors)</action>
<action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action> <action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action>
<action dev="POI-DEVELOPERS" type="add">45738 - Initial HWPF support for Office Art Shapes</action> <action dev="POI-DEVELOPERS" type="add">45738 - Initial HWPF support for Office Art Shapes</action>
<action dev="POI-DEVELOPERS" type="fix">45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings</action> <action dev="POI-DEVELOPERS" type="fix">45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">Fixed special cases of INDEX function (single column/single row, errors)</action>
<action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action> <action dev="POI-DEVELOPERS" type="add">45761 - Support for Very Hidden excel sheets in HSSF</action>
<action dev="POI-DEVELOPERS" type="add">45738 - Initial HWPF support for Office Art Shapes</action> <action dev="POI-DEVELOPERS" type="add">45738 - Initial HWPF support for Office Art Shapes</action>
<action dev="POI-DEVELOPERS" type="fix">45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings</action> <action dev="POI-DEVELOPERS" type="fix">45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings</action>

View File

@ -22,6 +22,7 @@ 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.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException; 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.OperandResolver;
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.eval.ValueEval;
/** /**
@ -51,6 +52,10 @@ public final class Index implements Function {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
Eval firstArg = args[0]; Eval firstArg = args[0];
if (firstArg instanceof RefEval) {
// convert to area ref for simpler code in getValueFromArea()
firstArg = ((RefEval)firstArg).offset(0, 0, 0, 0);
}
if(!(firstArg instanceof AreaEval)) { if(!(firstArg instanceof AreaEval)) {
// else the other variation of this function takes an array as the first argument // else the other variation of this function takes an array as the first argument
@ -84,16 +89,63 @@ public final class Index implements Function {
// too many arguments // too many arguments
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;
} }
return getValueFromArea(reference, rowIx, columnIx); return getValueFromArea(reference, rowIx, columnIx, nArgs);
} catch (EvaluationException e) { } catch (EvaluationException e) {
return e.getErrorEval(); return e.getErrorEval();
} }
} }
private static ValueEval getValueFromArea(AreaEval ae, int rowIx, int columnIx) throws EvaluationException { /**
* @param nArgs - 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 {
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;
} else {
if (nArgs == 2) {
rowIx = 0;
columnIx = pRowIx;
} else {
rowIx = pRowIx == -1 ? 0 : pRowIx;
columnIx = pColumnIx;
}
}
if (rowIx < -1 || columnIx < -1) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
} else if (ae.isColumn()) {
if (nArgs == 2) {
rowIx = pRowIx;
columnIx = 0;
} else {
rowIx = pRowIx;
columnIx = pColumnIx == -1 ? 0 : pColumnIx;
}
if (rowIx < -1 || columnIx < -1) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
} else {
if (nArgs == 2) {
// always an error with 2-D area refs
if (pRowIx < -1) {
throw new EvaluationException(ErrorEval.VALUE_INVALID);
}
throw new EvaluationException(ErrorEval.REF_INVALID);
}
// Normal case - area ref is 2-D, and both index args were provided
rowIx = pRowIx;
columnIx = pColumnIx;
}
int width = ae.getWidth(); int width = ae.getWidth();
int height = ae.getHeight(); int height = ae.getHeight();
// Slightly irregular logic for bounds checking errors // Slightly irregular logic for bounds checking errors
if (rowIx >= height || columnIx >= width) { if (rowIx >= height || columnIx >= width) {
throw new EvaluationException(ErrorEval.REF_INVALID); throw new EvaluationException(ErrorEval.REF_INVALID);

View File

@ -34,6 +34,7 @@ public final class AllIndividualFunctionEvaluationTests {
result.addTestSuite(TestDate.class); result.addTestSuite(TestDate.class);
result.addTestSuite(TestFinanceLib.class); result.addTestSuite(TestFinanceLib.class);
result.addTestSuite(TestIndex.class); result.addTestSuite(TestIndex.class);
result.addTestSuite(TestIndexFunctionFromSpreadsheet.class);
result.addTestSuite(TestIsBlank.class); result.addTestSuite(TestIsBlank.class);
result.addTestSuite(TestLen.class); result.addTestSuite(TestLen.class);
result.addTestSuite(TestLookupFunctionsFromSpreadsheet.class); result.addTestSuite(TestLookupFunctionsFromSpreadsheet.class);

View File

@ -48,11 +48,11 @@ public final class TestIndex extends TestCase {
double[] values = TEST_VALUES0; double[] values = TEST_VALUES0;
confirmAreaEval("C1:D6", values, 4, 1, 7); confirmAreaEval("C1:D6", values, 4, 1, 7);
confirmAreaEval("C1:D6", values, 6, 2, 12); confirmAreaEval("C1:D6", values, 6, 2, 12);
confirmAreaEval("C1:D6", values, 3, -1, 5); confirmAreaEval("C1:D6", values, 3, 1, 5);
// now treat same data as 3 columns, 4 rows // now treat same data as 3 columns, 4 rows
confirmAreaEval("C10:E13", values, 2, 2, 5); confirmAreaEval("C10:E13", values, 2, 2, 5);
confirmAreaEval("C10:E13", values, 4, -1, 10); confirmAreaEval("C10:E13", values, 4, 1, 10);
} }
/** /**

View File

@ -0,0 +1,259 @@
/* ====================================================================
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 java.io.PrintStream;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
import org.apache.poi.hssf.util.CellReference;
/**
* Tests INDEX() as loaded from a test data spreadsheet.<p/>
*
* @author Josh Micich
*/
public final class TestIndexFunctionFromSpreadsheet extends TestCase {
private static final class Result {
public static final int SOME_EVALUATIONS_FAILED = -1;
public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
public static final int NO_EVALUATIONS_FOUND = 0;
}
/**
* This class defines constants for navigating around the test data spreadsheet used for these tests.
*/
private static final class SS {
/** Name of the test spreadsheet (found in the standard test data folder) */
public final static String FILENAME = "IndexFunctionTestCaseData.xls";
public static final int COLUMN_INDEX_EVALUATION = 2; // Column 'C'
public static final int COLUMN_INDEX_EXPECTED_RESULT = 3; // Column 'D'
}
// Note - multiple failures are aggregated before ending.
// If one or more functions fail, a single AssertionFailedError is thrown at the end
private int _evaluationFailureCount;
private int _evaluationSuccessCount;
private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
if (expected == null) {
throw new AssertionFailedError(msg + " - Bad setup data expected value is null");
}
if(actual == null) {
throw new AssertionFailedError(msg + " - actual value was null");
}
if(expected.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
confirmErrorResult(msg, expected.getErrorCellValue(), actual);
return;
}
if(actual.getCellType() == HSSFCell.CELL_TYPE_ERROR) {
throw unexpectedError(msg, expected, actual.getErrorValue());
}
if(actual.getCellType() != expected.getCellType()) {
throw wrongTypeError(msg, expected, actual);
}
switch (expected.getCellType()) {
case HSSFCell.CELL_TYPE_BOOLEAN:
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
break;
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
case HSSFCell.CELL_TYPE_NUMERIC:
assertEquals(expected.getNumericCellValue(), actual.getNumberValue(), 0.0);
break;
case HSSFCell.CELL_TYPE_STRING:
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
break;
}
}
private static AssertionFailedError wrongTypeError(String msgPrefix, HSSFCell expectedCell, CellValue actualValue) {
return new AssertionFailedError(msgPrefix + " Result type mismatch. Evaluated result was "
+ formatValue(actualValue)
+ " but the expected result was "
+ formatValue(expectedCell)
);
}
private static AssertionFailedError unexpectedError(String msgPrefix, HSSFCell expected, int actualErrorCode) {
return new AssertionFailedError(msgPrefix + " Error code ("
+ ErrorEval.getText(actualErrorCode)
+ ") was evaluated, but the expected result was "
+ formatValue(expected)
);
}
private static void confirmErrorResult(String msgPrefix, int expectedErrorCode, CellValue actual) {
if(actual.getCellType() != HSSFCell.CELL_TYPE_ERROR) {
throw new AssertionFailedError(msgPrefix + " Expected cell error ("
+ ErrorEval.getText(expectedErrorCode) + ") but actual value was "
+ formatValue(actual));
}
if(expectedErrorCode != actual.getErrorValue()) {
throw new AssertionFailedError(msgPrefix + " Expected cell error code ("
+ ErrorEval.getText(expectedErrorCode)
+ ") but actual error code was ("
+ ErrorEval.getText(actual.getErrorValue())
+ ")");
}
}
private static String formatValue(HSSFCell expecedCell) {
switch (expecedCell.getCellType()) {
case HSSFCell.CELL_TYPE_BLANK: return "<blank>";
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(expecedCell.getBooleanCellValue());
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(expecedCell.getNumericCellValue());
case HSSFCell.CELL_TYPE_STRING: return expecedCell.getRichStringCellValue().getString();
}
throw new RuntimeException("Unexpected cell type of expected value (" + expecedCell.getCellType() + ")");
}
private static String formatValue(CellValue actual) {
switch (actual.getCellType()) {
case HSSFCell.CELL_TYPE_BLANK: return "<blank>";
case HSSFCell.CELL_TYPE_BOOLEAN: return String.valueOf(actual.getBooleanValue());
case HSSFCell.CELL_TYPE_NUMERIC: return String.valueOf(actual.getNumberValue());
case HSSFCell.CELL_TYPE_STRING: return actual.getRichTextStringValue().getString();
}
throw new RuntimeException("Unexpected cell type of evaluated value (" + actual.getCellType() + ")");
}
protected void setUp() {
_evaluationFailureCount = 0;
_evaluationSuccessCount = 0;
}
public void testFunctionsFromTestSpreadsheet() {
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook(SS.FILENAME);
processTestSheet(workbook, workbook.getSheetName(0));
// confirm results
String successMsg = "There were "
+ _evaluationSuccessCount + " function(s) without error";
if(_evaluationFailureCount > 0) {
String msg = _evaluationFailureCount + " evaluation(s) failed. " + successMsg;
throw new AssertionFailedError(msg);
}
if(false) { // normally no output for successful tests
System.out.println(getClass().getName() + ": " + successMsg);
}
}
private void processTestSheet(HSSFWorkbook workbook, String sheetName) {
HSSFSheet sheet = workbook.getSheetAt(0);
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook);
int maxRows = sheet.getLastRowNum()+1;
int result = Result.NO_EVALUATIONS_FOUND; // so far
for(int rowIndex=0; rowIndex<maxRows; rowIndex++) {
HSSFRow r = sheet.getRow(rowIndex);
if(r == null) {
continue;
}
HSSFCell c = r.getCell(SS.COLUMN_INDEX_EVALUATION);
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
continue;
}
CellValue actualValue = evaluator.evaluate(c);
HSSFCell expectedValueCell = r.getCell(SS.COLUMN_INDEX_EXPECTED_RESULT);
String msgPrefix = formatTestCaseDetails(sheetName, r.getRowNum(), c);
try {
confirmExpectedResult(msgPrefix, expectedValueCell, actualValue);
_evaluationSuccessCount ++;
if(result != Result.SOME_EVALUATIONS_FAILED) {
result = Result.ALL_EVALUATIONS_SUCCEEDED;
}
} catch (RuntimeException e) {
_evaluationFailureCount ++;
printShortStackTrace(System.err, e);
result = Result.SOME_EVALUATIONS_FAILED;
} catch (AssertionFailedError e) {
_evaluationFailureCount ++;
printShortStackTrace(System.err, e);
result = Result.SOME_EVALUATIONS_FAILED;
}
}
}
private static String formatTestCaseDetails(String sheetName, int rowNum, HSSFCell c) {
StringBuffer sb = new StringBuffer();
CellReference cr = new CellReference(sheetName, rowNum, c.getCellNum(), false, false);
sb.append(cr.formatAsString());
sb.append(" {=").append(c.getCellFormula()).append("}");
return sb.toString();
}
/**
* Useful to keep output concise when expecting many failures to be reported by this test case
*/
private static void printShortStackTrace(PrintStream ps, Throwable e) {
StackTraceElement[] stes = e.getStackTrace();
int startIx = 0;
// skip any top frames inside junit.framework.Assert
while(startIx<stes.length) {
if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
break;
}
startIx++;
}
// skip bottom frames (part of junit framework)
int endIx = startIx+1;
while(endIx < stes.length) {
if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
break;
}
endIx++;
}
if(startIx >= endIx) {
// something went wrong. just print the whole stack trace
e.printStackTrace(ps);
}
endIx -= 4; // skip 4 frames of reflection invocation
ps.println(e.toString());
for(int i=startIx; i<endIx; i++) {
ps.println("\tat " + stes[i].toString());
}
}
}