From 14be991668e4ae8f05b349ae3b01c40627ce2e21 Mon Sep 17 00:00:00 2001 From: David North Date: Wed, 19 Aug 2015 10:10:08 +0000 Subject: [PATCH] Make intersection formulae work in XSSF. Patch from Matt Hillsdon plus additional tests. https://bz.apache.org/bugzilla/show_bug.cgi?id=52111 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1696549 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/ss/formula/FormulaParser.java | 47 ++++++++++++-- .../ss/formula/OperandClassTransformer.java | 4 +- .../poi/xssf/usermodel/TestXSSFBugs.java | 26 ++++++++ .../poi/hssf/model/TestFormulaParser.java | 58 +++++++++++++++++- .../apache/poi/hssf/usermodel/TestBugs.java | 23 ++++--- .../spreadsheet/Intersection-52111-xssf.xlsx | Bin 0 -> 9861 bytes test-data/spreadsheet/Intersection-52111.xls | Bin 13824 -> 26624 bytes 7 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 test-data/spreadsheet/Intersection-52111-xssf.xlsx diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index 5124a44632..53f2dd2f9d 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -39,6 +39,7 @@ import org.apache.poi.ss.formula.ptg.FuncPtg; import org.apache.poi.ss.formula.ptg.FuncVarPtg; import org.apache.poi.ss.formula.ptg.GreaterEqualPtg; import org.apache.poi.ss.formula.ptg.GreaterThanPtg; +import org.apache.poi.ss.formula.ptg.IntersectionPtg; import org.apache.poi.ss.formula.ptg.IntPtg; import org.apache.poi.ss.formula.ptg.LessEqualPtg; import org.apache.poi.ss.formula.ptg.LessThanPtg; @@ -100,6 +101,12 @@ public final class FormulaParser { */ private char look; + /** + * Tracks whether the run of whitespace preceeding "look" could be an + * intersection operator. See GetChar. + */ + private boolean _inIntersection = false; + private FormulaParsingWorkbook _book; private SpreadsheetVersion _ssVersion; @@ -145,9 +152,20 @@ public final class FormulaParser { fp.parse(); return fp.getRPNPtg(formulaType); } - + /** Read New Character From Input Stream */ private void GetChar() { + // The intersection operator is a space. We track whether the run of + // whitespace preceeding "look" counts as an intersection operator. + if (IsWhite(look)) { + if (look == ' ') { + _inIntersection = true; + } + } + else { + _inIntersection = false; + } + // Check to see if we've walked off the end of the string. if (_pointer > _formulaLength) { throw new RuntimeException("too far"); @@ -158,6 +176,7 @@ public final class FormulaParser { // Just return if so and reset 'look' to something to keep // SkipWhitespace from spinning look = (char)0; + _inIntersection = false; } _pointer++; //System.out.println("Got char: "+ look); @@ -1108,7 +1127,7 @@ public final class FormulaParser { return parseUnary(true); case '(': Match('('); - ParseNode inside = comparisonExpression(); + ParseNode inside = unionExpression(); Match(')'); return new ParseNode(ParenthesisPtg.instance, inside); case '"': @@ -1447,8 +1466,9 @@ public final class FormulaParser { result = new ParseNode(operator, result, other); } } + private ParseNode unionExpression() { - ParseNode result = comparisonExpression(); + ParseNode result = intersectionExpression(); boolean hasUnions = false; while (true) { SkipWhite(); @@ -1456,7 +1476,7 @@ public final class FormulaParser { case ',': GetChar(); hasUnions = true; - ParseNode other = comparisonExpression(); + ParseNode other = intersectionExpression(); result = new ParseNode(UnionPtg.instance, result, other); continue; } @@ -1467,6 +1487,25 @@ public final class FormulaParser { } } + private ParseNode intersectionExpression() { + ParseNode result = comparisonExpression(); + boolean hasIntersections = false; + while (true) { + SkipWhite(); + if (_inIntersection) { + // Don't getChar() as the space has already been eaten and recorded by SkipWhite(). + hasIntersections = true; + ParseNode other = comparisonExpression(); + result = new ParseNode(IntersectionPtg.instance, result, other); + continue; + } + if (hasIntersections) { + return augmentWithMemPtg(result); + } + return result; + } + } + private ParseNode comparisonExpression() { ParseNode result = concatExpression(); while (true) { diff --git a/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java index 05b7d56d71..ea3a488761 100644 --- a/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java +++ b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java @@ -21,6 +21,7 @@ import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; import org.apache.poi.ss.formula.ptg.AttrPtg; import org.apache.poi.ss.formula.ptg.ControlPtg; import org.apache.poi.ss.formula.ptg.FuncVarPtg; +import org.apache.poi.ss.formula.ptg.IntersectionPtg; import org.apache.poi.ss.formula.ptg.MemAreaPtg; import org.apache.poi.ss.formula.ptg.MemFuncPtg; import org.apache.poi.ss.formula.ptg.Ptg; @@ -117,7 +118,8 @@ final class OperandClassTransformer { if (token instanceof ValueOperatorPtg || token instanceof ControlPtg || token instanceof MemFuncPtg || token instanceof MemAreaPtg - || token instanceof UnionPtg) { + || token instanceof UnionPtg + || token instanceof IntersectionPtg) { // Value Operator Ptgs and Control are base tokens, so token will be unchanged // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java index 1409d42274..00aa4b2f75 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -2501,4 +2501,30 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("57181.xlsm"); assertEquals(9, wb.getNumberOfSheets()); } + + @Test + public void bug52111() throws Exception { + Workbook wb = XSSFTestDataSamples.openSampleWorkbook("Intersection-52111-xssf.xlsx"); + Sheet s = wb.getSheetAt(0); + assertFormula(wb, s.getRow(2).getCell(0), "(C2:D3 D3:E4)", "4.0"); + assertFormula(wb, s.getRow(6).getCell(0), "Tabelle2!E:E Tabelle2!11:11", "5.0"); + assertFormula(wb, s.getRow(8).getCell(0), "Tabelle2!E:F Tabelle2!11:12", null); + } + + private void assertFormula(Workbook wb, Cell intF, String expectedFormula, String expectedResultOrNull) { + assertEquals(Cell.CELL_TYPE_FORMULA, intF.getCellType()); + if (null == expectedResultOrNull) { + assertEquals(Cell.CELL_TYPE_ERROR, intF.getCachedFormulaResultType()); + expectedResultOrNull = "#VALUE!"; + } + else { + assertEquals(Cell.CELL_TYPE_NUMERIC, intF.getCachedFormulaResultType()); + } + + assertEquals(expectedFormula, intF.getCellFormula()); + + // Check we can evaluate it correctly + FormulaEvaluator eval = wb.getCreationHelper().createFormulaEvaluator(); + assertEquals(expectedResultOrNull, eval.evaluate(intF).formatAsString()); + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index ce017f7ed2..09bf5a9b6a 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -702,7 +702,6 @@ public final class TestFormulaParser extends TestCase { assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); } public void testParserErrors() { - parseExpectedException("1 2"); parseExpectedException(" 12 . 345 "); parseExpectedException("1 .23 "); @@ -1060,8 +1059,65 @@ public final class TestFormulaParser extends TestCase { ); MemFuncPtg mf = (MemFuncPtg)ptgs[0]; assertEquals(45, mf.getLenRefSubexpression()); + + // We don't check the type of the operands. + confirmTokenClasses("1,2", MemAreaPtg.class, IntPtg.class, IntPtg.class, UnionPtg.class); } + public void testIntersection() { + String formula = "Sheet1!$B$2:$C$3 OFFSET(Sheet1!$E$2:$E$4, 1,Sheet1!$A$1) Sheet1!$D$6"; + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet("Sheet1"); + Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1); + + confirmTokenClasses(ptgs, + // TODO - AttrPtg.class, // Excel prepends this + MemFuncPtg.class, + Area3DPtg.class, + Area3DPtg.class, + IntPtg.class, + Ref3DPtg.class, + FuncVarPtg.class, + IntersectionPtg.class, + Ref3DPtg.class, + IntersectionPtg.class + ); + MemFuncPtg mf = (MemFuncPtg)ptgs[0]; + assertEquals(45, mf.getLenRefSubexpression()); + + // This used to be an error but now parses. Union has the same behaviour. + confirmTokenClasses("1 2", MemAreaPtg.class, IntPtg.class, IntPtg.class, IntersectionPtg.class); + } + + public void testComparisonInParen() { + confirmTokenClasses("(A1 > B2)", + RefPtg.class, + RefPtg.class, + GreaterThanPtg.class, + ParenthesisPtg.class + ); + } + + public void testUnionInParen() { + confirmTokenClasses("(A1:B2,B2:C3)", + MemAreaPtg.class, + AreaPtg.class, + AreaPtg.class, + UnionPtg.class, + ParenthesisPtg.class + ); + } + + public void testIntersectionInParen() { + confirmTokenClasses("(A1:B2 B2:C3)", + MemAreaPtg.class, + AreaPtg.class, + AreaPtg.class, + IntersectionPtg.class, + ParenthesisPtg.class + ); + } + public void testRange_bug46643() { String formula = "Sheet1!A1:Sheet1!B3"; HSSFWorkbook wb = new HSSFWorkbook(); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index af6e8bb036..c1e14bc674 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -2664,17 +2664,26 @@ public final class TestBugs extends BaseTestBugzillaIssues { public void bug52111() throws Exception { Workbook wb = openSample("Intersection-52111.xls"); Sheet s = wb.getSheetAt(0); - - // Check we can read it correctly - Cell intF = s.getRow(2).getCell(0); + assertFormula(wb, s.getRow(2).getCell(0), "(C2:D3 D3:E4)", "4.0"); + assertFormula(wb, s.getRow(6).getCell(0), "Tabelle2!E:E Tabelle2!$A11:$IV11", "5.0"); + assertFormula(wb, s.getRow(8).getCell(0), "Tabelle2!E:F Tabelle2!$A11:$IV12", null); + } + + private void assertFormula(Workbook wb, Cell intF, String expectedFormula, String expectedResultOrNull) { assertEquals(Cell.CELL_TYPE_FORMULA, intF.getCellType()); - assertEquals(Cell.CELL_TYPE_NUMERIC, intF.getCachedFormulaResultType()); - - assertEquals("(C2:D3 D3:E4)", intF.getCellFormula()); + if (null == expectedResultOrNull) { + assertEquals(Cell.CELL_TYPE_ERROR, intF.getCachedFormulaResultType()); + expectedResultOrNull = "#VALUE!"; + } + else { + assertEquals(Cell.CELL_TYPE_NUMERIC, intF.getCachedFormulaResultType()); + } + + assertEquals(expectedFormula, intF.getCellFormula()); // Check we can evaluate it correctly FormulaEvaluator eval = wb.getCreationHelper().createFormulaEvaluator(); - assertEquals("4.0", eval.evaluate(intF).formatAsString()); + assertEquals(expectedResultOrNull, eval.evaluate(intF).formatAsString()); } @Test diff --git a/test-data/spreadsheet/Intersection-52111-xssf.xlsx b/test-data/spreadsheet/Intersection-52111-xssf.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1ff05838550510612d583350bedf913c51d4b5a7 GIT binary patch literal 9861 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 Xb5(_(v-h7O9}OT6r8*7O&%OTx(lX2A literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/Intersection-52111.xls b/test-data/spreadsheet/Intersection-52111.xls index 835130e8b716c5bf6f160b6d9d5211e825be7c6e..dd33ffe5abe7e7edf22c7369a60cece3d25e913c 100644 GIT binary patch literal 26624 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 -- 2.39.5