diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index adb4bd877..46d7fbfee 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -1091,10 +1091,10 @@ public final class FormulaParser { return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); case '-': Match('-'); - return new ParseNode(UnaryMinusPtg.instance, powerFactor()); + return parseUnary(false); case '+': Match('+'); - return new ParseNode(UnaryPlusPtg.instance, powerFactor()); + return parseUnary(true); case '(': Match('('); ParseNode inside = comparisonExpression(); @@ -1118,6 +1118,35 @@ public final class FormulaParser { } + private ParseNode parseUnary(boolean isPlus) { + + boolean numberFollows = IsDigit(look) || look=='.'; + ParseNode factor = powerFactor(); + + if (numberFollows) { + // + or - directly next to a number is parsed with the number + + Ptg token = factor.getToken(); + if (token instanceof NumberPtg) { + if (isPlus) { + return factor; + } + token = new NumberPtg(-((NumberPtg)token).getValue()); + return new ParseNode(token); + } + if (token instanceof IntPtg) { + if (isPlus) { + return factor; + } + int intVal = ((IntPtg)token).getValue(); + // note - cannot use IntPtg for negatives + token = new NumberPtg(-intVal); + return new ParseNode(token); + } + } + return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor); + } + private ParseNode parseArray() { List rowsData = new ArrayList(); while(true) { diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 76dce58da..cdf42e07f 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -187,6 +187,43 @@ public final class TestFormulaParser extends TestCase { confirmTokenClasses("+A1", RefPtg.class, UnaryPlusPtg.class); } + /** + * There may be multiple ways to encode an expression involving {@link UnaryPlusPtg} + * or {@link UnaryMinusPtg}. These may be perfectly equivalent from a formula + * evaluation perspective, or formula rendering. However, differences in the way + * POI encodes formulas may cause unnecessary confusion. These non-critical tests + * check that POI follows the same encoding rules as Excel. + */ + public void testExactEncodingOfUnaryPlusAndMinus() { + // as tested in Excel: + confirmUnary("-3", -3, NumberPtg.class); + confirmUnary("--4", -4, NumberPtg.class, UnaryMinusPtg.class); + confirmUnary("+++5", 5, IntPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class); + confirmUnary("++-6", -6, NumberPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class); + + // Spaces muck things up a bit. It would be clearer why the following cases are + // reasonable if POI encoded tAttrSpace in the right places. + // Otherwise these differences look capricious. + confirmUnary("+ 12", 12, IntPtg.class, UnaryPlusPtg.class); + confirmUnary("- 13", 13, IntPtg.class, UnaryMinusPtg.class); + } + + private static void confirmUnary(String formulaText, double val, Class...expectedTokenTypes) { + Ptg[] ptgs = parseFormula(formulaText); + confirmTokenClasses(ptgs, expectedTokenTypes); + Ptg ptg0 = ptgs[0]; + if (ptg0 instanceof IntPtg) { + IntPtg intPtg = (IntPtg) ptg0; + assertEquals((int)val, intPtg.getValue()); + } else if (ptg0 instanceof NumberPtg) { + NumberPtg numberPtg = (NumberPtg) ptg0; + assertEquals(val, numberPtg.getValue(), 0.0); + } else { + fail("bad ptg0 " + ptg0); + } + } + + public void testLeadingSpaceInString() { String value = " hi "; Ptg[] ptgs = parseFormula("\"" + value + "\""); @@ -325,7 +362,7 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("+.1"); formula = cell.getCellFormula(); - assertEquals("+0.1", formula); + assertEquals("0.1", formula); cell.setCellFormula("-.1"); formula = cell.getCellFormula();