From c8bf92f963bbc4df4846ab40ea62f3ba82310935 Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Fri, 9 Jan 2015 16:51:02 +0000 Subject: [PATCH] Bug 57196: Resolve RefEval to it's inner ValueEval in Hex2Dec git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1650597 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/ss/formula/functions/Hex2Dec.java | 8 +- .../poi/xssf/usermodel/TestXSSFBugs.java | 85 ++++++++++ .../poi/hssf/model/TestFormulaParser.java | 151 +++++++++++------- .../poi/ss/formula/functions/TestHex2Dec.java | 51 ++++++ test-data/spreadsheet/57196.xlsx | Bin 0 -> 9359 bytes 5 files changed, 234 insertions(+), 61 deletions(-) create mode 100644 test-data/spreadsheet/57196.xlsx diff --git a/src/java/org/apache/poi/ss/formula/functions/Hex2Dec.java b/src/java/org/apache/poi/ss/formula/functions/Hex2Dec.java index ccd641eaef..907b62916f 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Hex2Dec.java +++ b/src/java/org/apache/poi/ss/formula/functions/Hex2Dec.java @@ -43,7 +43,13 @@ public class Hex2Dec extends Fixed1ArgFunction implements FreeRefFunction { static final int MAX_NUMBER_OF_PLACES = 10; public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval numberVE) { - String hex = OperandResolver.coerceValueToString(numberVE); + final String hex; + if (numberVE instanceof RefEval) { + RefEval re = (RefEval) numberVE; + hex = OperandResolver.coerceValueToString(re.getInnerValueEval(re.getFirstSheetIndex())); + } else { + hex = OperandResolver.coerceValueToString(numberVE); + } try { return new NumberEval(BaseNumberUtils.convertToDecimal(hex, HEXADECIMAL_BASE, MAX_NUMBER_OF_PLACES)); } catch (IllegalArgumentException e) { 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 48d8bae0bb..49683edb06 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -35,6 +35,7 @@ import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLException; import org.apache.poi.POIXMLProperties; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; @@ -1924,4 +1925,88 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { assertEquals(expect, value.formatAsString()); } + + @Test + public void testBug57196() throws IOException { + Workbook wb = XSSFTestDataSamples.openSampleWorkbook("57196.xlsx"); + Sheet sheet = wb.getSheet("Feuil1"); + Row mod=sheet.getRow(1); + mod.getCell(1).setCellValue(3); + HSSFFormulaEvaluator.evaluateAllFormulaCells(wb); +// FileOutputStream fileOutput = new FileOutputStream("/tmp/57196.xlsx"); +// wb.write(fileOutput); +// fileOutput.close(); + wb.close(); + } + + @Test + public void test57196_Detail() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet("Sheet1"); + XSSFRow row = sheet.createRow(0); + XSSFCell cell = row.createCell(0); + cell.setCellFormula("DEC2HEX(HEX2DEC(O8)-O2+D2)"); + XSSFFormulaEvaluator fe = new XSSFFormulaEvaluator(wb); + CellValue cv = fe.evaluate(cell); + + assertNotNull(cv); + } + + @Test + public void test57196_Detail2() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet("Sheet1"); + XSSFRow row = sheet.createRow(0); + XSSFCell cell = row.createCell(0); + cell.setCellFormula("DEC2HEX(O2+D2)"); + XSSFFormulaEvaluator fe = new XSSFFormulaEvaluator(wb); + CellValue cv = fe.evaluate(cell); + + assertNotNull(cv); + } + + @Test + public void test57196_WorkbookEvaluator() { + //System.setProperty("org.apache.poi.util.POILogger", "org.apache.poi.util.SystemOutLogger"); + //System.setProperty("poi.log.level", "3"); + try { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFSheet sheet = wb.createSheet("Sheet1"); + XSSFRow row = sheet.createRow(0); + XSSFCell cell = row.createCell(0); + + // simple formula worked + cell.setCellFormula("DEC2HEX(O2+D2)"); + + WorkbookEvaluator workbookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(wb), null, null); + workbookEvaluator.setDebugEvaluationOutputForNextEval(true); + workbookEvaluator.evaluate(new XSSFEvaluationCell(cell)); + + // this already failed! Hex2Dec did not correctly handle RefEval + cell.setCellFormula("HEX2DEC(O8)"); + workbookEvaluator.clearAllCachedResultValues(); + + workbookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(wb), null, null); + workbookEvaluator.setDebugEvaluationOutputForNextEval(true); + workbookEvaluator.evaluate(new XSSFEvaluationCell(cell)); + + // slightly more complex one failed + cell.setCellFormula("HEX2DEC(O8)-O2+D2"); + workbookEvaluator.clearAllCachedResultValues(); + + workbookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(wb), null, null); + workbookEvaluator.setDebugEvaluationOutputForNextEval(true); + workbookEvaluator.evaluate(new XSSFEvaluationCell(cell)); + + // more complicated failed + cell.setCellFormula("DEC2HEX(HEX2DEC(O8)-O2+D2)"); + workbookEvaluator.clearAllCachedResultValues(); + + workbookEvaluator.setDebugEvaluationOutputForNextEval(true); + workbookEvaluator.evaluate(new XSSFEvaluationCell(cell)); + } finally { + System.clearProperty("org.apache.poi.util.POILogger"); + System.clearProperty("poi.log.level"); + } + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index fc5dd837ab..ce017f7ed2 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -18,6 +18,9 @@ package org.apache.poi.hssf.model; import static org.junit.Assert.assertArrayEquals; + +import java.io.IOException; + import junit.framework.AssertionFailedError; import junit.framework.TestCase; @@ -37,44 +40,12 @@ import org.apache.poi.ss.formula.FormulaParseException; import org.apache.poi.ss.formula.FormulaParser; import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.constant.ErrorConstant; -import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; -import org.apache.poi.ss.formula.ptg.AddPtg; -import org.apache.poi.ss.formula.ptg.Area3DPtg; -import org.apache.poi.ss.formula.ptg.AreaI; -import org.apache.poi.ss.formula.ptg.AreaPtg; -import org.apache.poi.ss.formula.ptg.AreaPtgBase; -import org.apache.poi.ss.formula.ptg.ArrayPtg; -import org.apache.poi.ss.formula.ptg.AttrPtg; -import org.apache.poi.ss.formula.ptg.BoolPtg; -import org.apache.poi.ss.formula.ptg.ConcatPtg; -import org.apache.poi.ss.formula.ptg.DividePtg; -import org.apache.poi.ss.formula.ptg.EqualPtg; -import org.apache.poi.ss.formula.ptg.ErrPtg; -import org.apache.poi.ss.formula.ptg.FuncPtg; -import org.apache.poi.ss.formula.ptg.FuncVarPtg; -import org.apache.poi.ss.formula.ptg.IntPtg; -import org.apache.poi.ss.formula.ptg.MemAreaPtg; -import org.apache.poi.ss.formula.ptg.MemFuncPtg; -import org.apache.poi.ss.formula.ptg.MissingArgPtg; -import org.apache.poi.ss.formula.ptg.MultiplyPtg; -import org.apache.poi.ss.formula.ptg.NamePtg; -import org.apache.poi.ss.formula.ptg.NumberPtg; -import org.apache.poi.ss.formula.ptg.ParenthesisPtg; -import org.apache.poi.ss.formula.ptg.PercentPtg; -import org.apache.poi.ss.formula.ptg.PowerPtg; -import org.apache.poi.ss.formula.ptg.Ptg; -import org.apache.poi.ss.formula.ptg.RangePtg; -import org.apache.poi.ss.formula.ptg.Ref3DPtg; -import org.apache.poi.ss.formula.ptg.RefPtg; -import org.apache.poi.ss.formula.ptg.StringPtg; -import org.apache.poi.ss.formula.ptg.SubtractPtg; -import org.apache.poi.ss.formula.ptg.UnaryMinusPtg; -import org.apache.poi.ss.formula.ptg.UnaryPlusPtg; -import org.apache.poi.ss.formula.ptg.UnionPtg; +import org.apache.poi.ss.formula.ptg.*; import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues; import org.apache.poi.ss.usermodel.Name; import org.apache.poi.util.HexRead; import org.apache.poi.util.LittleEndianByteArrayInputStream; +import org.junit.Test; /** * Test the low level formula parser functionality. High level tests are to @@ -166,7 +137,7 @@ public final class TestFormulaParser extends TestCase { StringPtg.class, StringPtg.class, FuncVarPtg.class); } - public void testWorksheetReferences() { + public void testWorksheetReferences() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("NoQuotesNeeded"); @@ -181,6 +152,8 @@ public final class TestFormulaParser extends TestCase { cell = row.createCell(1); cell.setCellFormula("'Quotes Needed Here &#$@'!A1"); + + wb.close(); } public void testUnaryMinus() { @@ -256,8 +229,9 @@ public final class TestFormulaParser extends TestCase { confirmTokenClasses("40000/2", IntPtg.class, IntPtg.class, DividePtg.class); } - /** bug 35027, underscore in sheet name */ - public void testUnderscore() { + /** bug 35027, underscore in sheet name + * @throws IOException */ + public void testUnderscore() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Cash_Flow"); @@ -268,10 +242,13 @@ public final class TestFormulaParser extends TestCase { cell = row.createCell(0); cell.setCellFormula("Cash_Flow!A1"); + + wb.close(); } - /** bug 49725, defined names with underscore */ - public void testNamesWithUnderscore() { + /** bug 49725, defined names with underscore + * @throws IOException */ + public void testNamesWithUnderscore() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); //or new XSSFWorkbook(); HSSFSheet sheet = wb.createSheet("NamesWithUnderscore"); @@ -312,6 +289,8 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("INDEX(DA6_LEO_WBS_Name,MATCH($A3,DA6_LEO_WBS_Number,0))"); assertEquals("INDEX(DA6_LEO_WBS_Name,MATCH($A3,DA6_LEO_WBS_Number,0))", cell.getCellFormula()); + + wb.close(); } // bug 38396 : Formula with exponential numbers not parsed correctly. @@ -321,7 +300,7 @@ public final class TestFormulaParser extends TestCase { confirmTokenClasses("1.3E1/2", NumberPtg.class, IntPtg.class, DividePtg.class); } - public void testExponentialInSheet() { + public void testExponentialInSheet() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Cash_Flow"); @@ -390,9 +369,11 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("-10E-1/3.1E2*4E3/3E4"); formula = cell.getCellFormula(); assertEquals("Exponential formula string", "-1/310*4000/30000", formula); + + wb.close(); } - public void testNumbers() { + public void testNumbers() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Cash_Flow"); @@ -429,9 +410,11 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("10E-1"); formula = cell.getCellFormula(); assertEquals("1", formula); + + wb.close(); } - public void testRanges() { + public void testRanges() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Cash_Flow"); @@ -452,9 +435,11 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("A1...A2"); formula = cell.getCellFormula(); assertEquals("A1:A2", formula); + + wb.close(); } - public void testMultiSheetReference() { + public void testMultiSheetReference() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); wb.createSheet("Cash_Flow"); @@ -500,6 +485,8 @@ public final class TestFormulaParser extends TestCase { cell.setCellFormula("Cash_Flow:\'Test Sheet\'!A1:B2"); formula = cell.getCellFormula(); assertEquals("Cash_Flow:\'Test Sheet\'!A1:B2", formula); + + wb.close(); } /** @@ -660,7 +647,7 @@ public final class TestFormulaParser extends TestCase { StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class); assertEquals(expectedValue, sp.getValue()); } - public void testParseStringLiterals_bug28754() { + public void testParseStringLiterals_bug28754() throws IOException { StringPtg sp; try { @@ -674,17 +661,21 @@ public final class TestFormulaParser extends TestCase { assertEquals("test\"ing", sp.getValue()); HSSFWorkbook wb = new HSSFWorkbook(); - HSSFSheet sheet = wb.createSheet(); - wb.setSheetName(0, "Sheet1"); - - HSSFRow row = sheet.createRow(0); - HSSFCell cell = row.createCell(0); - cell.setCellFormula("right(\"test\"\"ing\", 3)"); - String actualCellFormula = cell.getCellFormula(); - if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) { - throw new AssertionFailedError("Identified bug 28754b"); + try { + HSSFSheet sheet = wb.createSheet(); + wb.setSheetName(0, "Sheet1"); + + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + cell.setCellFormula("right(\"test\"\"ing\", 3)"); + String actualCellFormula = cell.getCellFormula(); + if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) { + throw new AssertionFailedError("Identified bug 28754b"); + } + assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula); + } finally { + wb.close(); } - assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula); } public void testParseStringLiterals() { @@ -735,7 +726,7 @@ public final class TestFormulaParser extends TestCase { } } - public void testSetFormulaWithRowBeyond32768_Bug44539() { + public void testSetFormulaWithRowBeyond32768_Bug44539() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet(); @@ -748,6 +739,8 @@ public final class TestFormulaParser extends TestCase { fail("Identified bug 44539"); } assertEquals("SUM(A32769:A32770)", cell.getCellFormula()); + + wb.close(); } public void testSpaceAtStartOfFormula() { @@ -984,7 +977,7 @@ public final class TestFormulaParser extends TestCase { assertEquals(-5.0, ((Double)element).doubleValue(), 0.0); } - public void testRangeOperator() { + public void testRangeOperator() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet(); @@ -1004,16 +997,19 @@ public final class TestFormulaParser extends TestCase { wb.setSheetName(0, "A1...A2"); cell.setCellFormula("A1...A2!B1"); assertEquals("A1...A2!B1", cell.getCellFormula()); + + wb.close(); } - public void testBooleanNamedSheet() { - + public void testBooleanNamedSheet() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet("true"); HSSFCell cell = sheet.createRow(0).createCell(0); cell.setCellFormula("'true'!B2"); assertEquals("'true'!B2", cell.getCellFormula()); + + wb.close(); } public void testParseExternalWorkbookReference() { @@ -1087,8 +1083,9 @@ public final class TestFormulaParser extends TestCase { assertEquals(15, mf.getLenRefSubexpression()); } - /** Named ranges with backslashes, e.g. 'POI\\2009' */ - public void testBackSlashInNames() { + /** Named ranges with backslashes, e.g. 'POI\\2009' + * @throws IOException */ + public void testBackSlashInNames() throws IOException { HSSFWorkbook wb = new HSSFWorkbook(); HSSFName name = wb.createName(); @@ -1105,6 +1102,8 @@ public final class TestFormulaParser extends TestCase { HSSFCell cell_D1 = row.createCell(2); cell_D1.setCellFormula("NOT(POI\\2009=\"3.5-final\")"); assertEquals("NOT(POI\\2009=\"3.5-final\")", cell_D1.getCellFormula()); + + wb.close(); } /** @@ -1353,4 +1352,36 @@ public final class TestFormulaParser extends TestCase { private static void confirmParseException(FormulaParseException e, String expMsg) { assertEquals(expMsg, e.getMessage()); } + + @Test + public void test57196_Formula() { + HSSFWorkbook wb = new HSSFWorkbook(); + Ptg[] ptgs = HSSFFormulaParser.parse("DEC2HEX(HEX2DEC(O8)-O2+D2)", wb, FormulaType.CELL, -1); + assertNotNull("Ptg array should not be null", ptgs); + + confirmTokenClasses(ptgs, + NameXPtg.class, // ?? + NameXPtg.class, // ?? + RefPtg.class, // O8 + FuncVarPtg.class, // HEX2DEC + RefPtg.class, // O2 + SubtractPtg.class, + RefPtg.class, // D2 + AddPtg.class, + FuncVarPtg.class // DEC2HEX + ); + + RefPtg o8 = (RefPtg) ptgs[2]; + FuncVarPtg hex2Dec = (FuncVarPtg) ptgs[3]; + RefPtg o2 = (RefPtg) ptgs[4]; + RefPtg d2 = (RefPtg) ptgs[6]; + FuncVarPtg dec2Hex = (FuncVarPtg) ptgs[8]; + + assertEquals("O8", o8.toFormulaString()); + assertEquals(255, hex2Dec.getFunctionIndex()); + //assertEquals("", hex2Dec.toString()); + assertEquals("O2", o2.toFormulaString()); + assertEquals("D2", d2.toFormulaString()); + assertEquals(255, dec2Hex.getFunctionIndex()); + } } diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java b/src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java index 7c04157416..b7ca30cd51 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestHex2Dec.java @@ -18,6 +18,12 @@ package org.apache.poi.ss.formula.functions; import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.IStabilityClassifier; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.WorkbookEvaluator; import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.NumberEval; import org.apache.poi.ss.formula.eval.StringEval; @@ -57,4 +63,49 @@ public final class TestHex2Dec extends TestCase { confirmValueError("not a valid octal number","GGGGGGG", ErrorEval.NUM_ERROR); confirmValueError("not a valid octal number","3.14159", ErrorEval.NUM_ERROR); } + + public void testEvalOperationEvaluationContext() { + OperationEvaluationContext ctx = createContext(); + + ValueEval[] args = new ValueEval[] { ctx.getRefEval(0, 0) }; + ValueEval result = new Hex2Dec().evaluate(args, ctx); + + assertEquals(NumberEval.class, result.getClass()); + assertEquals("0", ((NumberEval) result).getStringValue()); + } + + public void testEvalOperationEvaluationContextFails() { + OperationEvaluationContext ctx = createContext(); + + ValueEval[] args = new ValueEval[] { ctx.getRefEval(0, 0), ctx.getRefEval(0, 0) }; + ValueEval result = new Hex2Dec().evaluate(args, ctx); + + assertEquals(ErrorEval.class, result.getClass()); + assertEquals(ErrorEval.VALUE_INVALID, result); + } + + private OperationEvaluationContext createContext() { + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet(); + HSSFEvaluationWorkbook workbook = HSSFEvaluationWorkbook.create(wb); + WorkbookEvaluator workbookEvaluator = new WorkbookEvaluator(workbook, new IStabilityClassifier() { + + public boolean isCellFinal(int sheetIndex, int rowIndex, int columnIndex) { + return true; + } + }, null); + OperationEvaluationContext ctx = new OperationEvaluationContext(workbookEvaluator, + workbook, 0, 0, 0, null); + return ctx; + } + + public void testRefs() { + OperationEvaluationContext ctx = createContext(); + + ValueEval[] args = new ValueEval[] { ctx.getRefEval(0, 0) }; + ValueEval result = new Hex2Dec().evaluate(args, -1, -1); + + assertEquals(NumberEval.class, result.getClass()); + assertEquals("0", ((NumberEval) result).getStringValue()); + } } diff --git a/test-data/spreadsheet/57196.xlsx b/test-data/spreadsheet/57196.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ed2b65e34332778e646ebda2eb4bbe794ffeb274 GIT binary patch literal 9359 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