diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index a30727d236..19943dc4af 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 43354 - support for evalating formulas with missing args 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row Initial support for creating hyperlinks in HSLF diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 5083faa385..94d1e2caee 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 43354 - support for evalating formulas with missing args 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row Initial support for creating hyperlinks in HSLF diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/MissingArgEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/MissingArgEval.java new file mode 100644 index 0000000000..708329ba69 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/eval/MissingArgEval.java @@ -0,0 +1,35 @@ +/* ==================================================================== + 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.eval; + +/** + * Represents the (intermediate) evaluated result of a missing function argument. In most cases + * this can be translated into {@link BlankEval} but there are some notable exceptions. Functions + * COUNT and COUNTA do count their missing args. Note - the differences between + * {@link MissingArgEval} and {@link BlankEval} have not been investigated fully, so the POI + * evaluator may need to be updated to account for these as they are found. + * + * @author Josh Micich + */ +public final class MissingArgEval implements ValueEval { + + public static MissingArgEval instance = new MissingArgEval(); + + private MissingArgEval() { + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Count.java b/src/java/org/apache/poi/hssf/record/formula/functions/Count.java index fd5944e858..62f4b6137a 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Count.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Count.java @@ -19,6 +19,7 @@ package org.apache.poi.hssf.record.formula.functions; 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.MissingArgEval; import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate; @@ -64,6 +65,10 @@ public final class Count implements Function { // only numbers are counted return true; } + if(valueEval == MissingArgEval.instance) { + // oh yeah, and missing arguments + return true; + } // error values and string values not counted return false; diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index cf334454bd..e80de22fc0 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -52,6 +52,7 @@ import org.apache.poi.hssf.record.formula.eval.BoolEval; 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.FunctionEval; +import org.apache.poi.hssf.record.formula.eval.MissingArgEval; import org.apache.poi.hssf.record.formula.eval.NameEval; import org.apache.poi.hssf.record.formula.eval.NameXEval; import org.apache.poi.hssf.record.formula.eval.NumberEval; @@ -284,10 +285,7 @@ public final class WorkbookEvaluator { continue; } if (ptg instanceof MemErrPtg) { continue; } - if (ptg instanceof MissingArgPtg) { - // TODO - might need to push BlankEval or MissingArgEval - continue; - } + Eval opResult; if (ptg instanceof OperationPtg) { OperationPtg optg = (OperationPtg) ptg; @@ -306,6 +304,9 @@ public final class WorkbookEvaluator { } // logDebug("invoke " + operation + " (nAgs=" + numops + ")"); opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum); + if (opResult == MissingArgEval.instance) { + opResult = BlankEval.INSTANCE; + } } else { opResult = getEvalForPtg(ptg, sheetIndex, tracker); } @@ -424,6 +425,9 @@ public final class WorkbookEvaluator { if (ptg instanceof ErrPtg) { return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode()); } + if (ptg instanceof MissingArgPtg) { + return MissingArgEval.instance; + } if (ptg instanceof AreaErrPtg ||ptg instanceof RefErrorPtg || ptg instanceof DeletedArea3DPtg || ptg instanceof DeletedRef3DPtg) { return ErrorEval.REF_INVALID; diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java index fe37a3c84b..a26539cdd2 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java @@ -36,6 +36,7 @@ public class AllFormulaEvalTests { result.addTestSuite(TestExternalFunction.class); result.addTestSuite(TestFormulaBugs.class); result.addTestSuite(TestFormulasFromSpreadsheet.class); + result.addTestSuite(TestMissingArgEval.class); result.addTestSuite(TestPercentEval.class); result.addTestSuite(TestRangeEval.class); result.addTestSuite(TestUnaryPlusEval.class); diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestMissingArgEval.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestMissingArgEval.java new file mode 100644 index 0000000000..1605b127fb --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestMissingArgEval.java @@ -0,0 +1,74 @@ +/* ==================================================================== + 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.eval; + +import java.util.EmptyStackException; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; + +/** + * Tests for {@link MissingArgEval} + * + * @author Josh Micich + */ +public final class TestMissingArgEval extends TestCase { + + public void testEvaluateMissingArgs() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + HSSFSheet sheet = wb.createSheet("Sheet1"); + HSSFCell cell = sheet.createRow(0).createCell(0); + + cell.setCellFormula("if(true,)"); + fe.clearAllCachedResultValues(); + CellValue cv; + try { + cv = fe.evaluate(cell); + } catch (EmptyStackException e) { + throw new AssertionFailedError("Missing args evaluation not implemented (bug 43354"); + } + // MissingArg -> BlankEval -> zero (as formula result) + assertEquals(0.0, cv.getNumberValue(), 0.0); + + // MissingArg -> BlankEval -> empty string (in concatenation) + cell.setCellFormula("\"abc\"&if(true,)"); + fe.clearAllCachedResultValues(); + assertEquals("abc", fe.evaluate(cell).getStringValue()); + } + + public void testCountFuncs() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + HSSFSheet sheet = wb.createSheet("Sheet1"); + HSSFCell cell = sheet.createRow(0).createCell(0); + + cell.setCellFormula("COUNT(C5,,,,)"); // 4 missing args, C5 is blank + assertEquals(4.0, fe.evaluate(cell).getNumberValue(), 0.0); + + cell.setCellFormula("COUNTA(C5,,)"); // 2 missing args, C5 is blank + fe.clearAllCachedResultValues(); + assertEquals(2.0, fe.evaluate(cell).getNumberValue(), 0.0); + } +}