From 27c3aeb55fd4e065e7e1c0fba1daf40c80d38061 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Tue, 9 Sep 2008 23:46:46 +0000 Subject: [PATCH] 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 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../hssf/record/formula/functions/Index.java | 58 +++- .../hssf/data/IndexFunctionTestCaseData.xls | Bin 0 -> 24576 bytes .../AllIndividualFunctionEvaluationTests.java | 1 + .../record/formula/functions/TestIndex.java | 4 +- .../TestIndexFunctionFromSpreadsheet.java | 259 ++++++++++++++++++ 7 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls create mode 100644 src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 4fb704e5d..95cab0e78 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + Fixed special cases of INDEX function (single column/single row, errors) 45761 - Support for Very Hidden excel sheets in HSSF 45738 - Initial HWPF support for Office Art Shapes 45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 118f81ba7..e7fb447da 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + Fixed special cases of INDEX function (single column/single row, errors) 45761 - Support for Very Hidden excel sheets in HSSF 45738 - Initial HWPF support for Office Art Shapes 45720 - Fixed HSSFWorkbook.cloneSheet to correctly clone sheets with drawings 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 3c93c0846..1c149dbdf 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 @@ -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.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.ValueEval; /** @@ -51,6 +52,10 @@ public final class Index implements Function { return ErrorEval.VALUE_INVALID; } 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)) { // 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 return ErrorEval.VALUE_INVALID; } - return getValueFromArea(reference, rowIx, columnIx); + return getValueFromArea(reference, rowIx, columnIx, nArgs); } catch (EvaluationException e) { 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 height = ae.getHeight(); - // Slightly irregular logic for bounds checking errors if (rowIx >= height || columnIx >= width) { throw new EvaluationException(ErrorEval.REF_INVALID); diff --git a/src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls b/src/testcases/org/apache/poi/hssf/data/IndexFunctionTestCaseData.xls new file mode 100644 index 0000000000000000000000000000000000000000..9dcde6177295a38037d74dbcf7ee9c5774c44086 GIT binary patch literal 24576 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 + * + * @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 ""; + 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 ""; + 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= 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