Patch from Josh from bug #44608 - Support for PercentPtg in the formula evaluator

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@637598 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-03-16 15:38:09 +00:00
parent 8ca38c3e23
commit 97265001e9
8 changed files with 381 additions and 206 deletions

View File

@ -36,6 +36,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44608 - Support for PercentPtg in the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action> <action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action>
<action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action> <action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action>
<action dev="POI-DEVELOPERS" type="add">44593 - Improved handling of short DVRecords</action> <action dev="POI-DEVELOPERS" type="add">44593 - Improved handling of short DVRecords</action>

View File

@ -33,6 +33,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.1-beta1" date="2008-??-??"> <release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44608 - Support for PercentPtg in the formula evaluator</action>
<action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action> <action dev="POI-DEVELOPERS" type="fix">44606 - Support calculated string values for evaluated formulas</action>
<action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action> <action dev="POI-DEVELOPERS" type="add">Add accessors to horizontal and vertical alignment in HSSFTextbox</action>
<action dev="POI-DEVELOPERS" type="add">44593 - Improved handling of short DVRecords</action> <action dev="POI-DEVELOPERS" type="add">44593 - Improved handling of short DVRecords</action>

View File

@ -24,71 +24,40 @@ import java.util.Stack;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.FormulaParser;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.formula.AddPtg;
import org.apache.poi.hssf.record.formula.Area3DPtg; import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.AreaPtg; import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.BoolPtg; import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.ConcatPtg;
import org.apache.poi.hssf.record.formula.ControlPtg; import org.apache.poi.hssf.record.formula.ControlPtg;
import org.apache.poi.hssf.record.formula.DividePtg;
import org.apache.poi.hssf.record.formula.EqualPtg;
import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.GreaterEqualPtg;
import org.apache.poi.hssf.record.formula.GreaterThanPtg;
import org.apache.poi.hssf.record.formula.IntPtg; import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.LessEqualPtg;
import org.apache.poi.hssf.record.formula.LessThanPtg;
import org.apache.poi.hssf.record.formula.MemErrPtg; import org.apache.poi.hssf.record.formula.MemErrPtg;
import org.apache.poi.hssf.record.formula.MissingArgPtg; import org.apache.poi.hssf.record.formula.MissingArgPtg;
import org.apache.poi.hssf.record.formula.MultiplyPtg;
import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.NotEqualPtg;
import org.apache.poi.hssf.record.formula.NumberPtg; import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.OperationPtg; import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.ParenthesisPtg; import org.apache.poi.hssf.record.formula.ParenthesisPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.ReferencePtg; import org.apache.poi.hssf.record.formula.ReferencePtg;
import org.apache.poi.hssf.record.formula.StringPtg; import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.record.formula.UnionPtg; import org.apache.poi.hssf.record.formula.UnionPtg;
import org.apache.poi.hssf.record.formula.UnknownPtg; import org.apache.poi.hssf.record.formula.UnknownPtg;
import org.apache.poi.hssf.record.formula.eval.AddEval;
import org.apache.poi.hssf.record.formula.eval.Area2DEval; import org.apache.poi.hssf.record.formula.eval.Area2DEval;
import org.apache.poi.hssf.record.formula.eval.Area3DEval; import org.apache.poi.hssf.record.formula.eval.Area3DEval;
import org.apache.poi.hssf.record.formula.eval.AreaEval; import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ConcatEval;
import org.apache.poi.hssf.record.formula.eval.DivideEval;
import org.apache.poi.hssf.record.formula.eval.EqualEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; 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.Eval;
import org.apache.poi.hssf.record.formula.eval.FuncVarEval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval; import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval;
import org.apache.poi.hssf.record.formula.eval.GreaterThanEval;
import org.apache.poi.hssf.record.formula.eval.LessEqualEval;
import org.apache.poi.hssf.record.formula.eval.LessThanEval;
import org.apache.poi.hssf.record.formula.eval.MultiplyEval;
import org.apache.poi.hssf.record.formula.eval.NameEval; import org.apache.poi.hssf.record.formula.eval.NameEval;
import org.apache.poi.hssf.record.formula.eval.NotEqualEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval; import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.PowerEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval; import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
import org.apache.poi.hssf.record.formula.eval.Ref3DEval; import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.SubtractEval;
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
/** /**
@ -98,8 +67,6 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
public class HSSFFormulaEvaluator { public class HSSFFormulaEvaluator {
// params to lookup the right constructor using reflection // params to lookup the right constructor using reflection
private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class };
private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class }; private static final Class[] VALUE_CONTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class };
private static final Class[] AREA3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval[].class }; private static final Class[] AREA3D_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class, ValueEval[].class };
@ -111,8 +78,6 @@ public class HSSFFormulaEvaluator {
// Maps for mapping *Eval to *Ptg // Maps for mapping *Eval to *Ptg
private static final Map VALUE_EVALS_MAP = new HashMap(); private static final Map VALUE_EVALS_MAP = new HashMap();
private static final Map OPERATION_EVALS_MAP = new HashMap();
/* /*
* Following is the mapping between the Ptg tokens returned * Following is the mapping between the Ptg tokens returned
* by the FormulaParser and the *Eval classes that are used * by the FormulaParser and the *Eval classes that are used
@ -124,26 +89,6 @@ public class HSSFFormulaEvaluator {
VALUE_EVALS_MAP.put(NumberPtg.class, NumberEval.class); VALUE_EVALS_MAP.put(NumberPtg.class, NumberEval.class);
VALUE_EVALS_MAP.put(StringPtg.class, StringEval.class); VALUE_EVALS_MAP.put(StringPtg.class, StringEval.class);
OPERATION_EVALS_MAP.put(AddPtg.class, AddEval.class);
OPERATION_EVALS_MAP.put(ConcatPtg.class, ConcatEval.class);
OPERATION_EVALS_MAP.put(DividePtg.class, DivideEval.class);
OPERATION_EVALS_MAP.put(EqualPtg.class, EqualEval.class);
//OPERATION_EVALS_MAP.put(ExpPtg.class, ExpEval.class); // TODO: check
// this
OPERATION_EVALS_MAP.put(FuncPtg.class, FuncVarEval.class); // TODO:
// check this
OPERATION_EVALS_MAP.put(FuncVarPtg.class, FuncVarEval.class);
OPERATION_EVALS_MAP.put(GreaterEqualPtg.class, GreaterEqualEval.class);
OPERATION_EVALS_MAP.put(GreaterThanPtg.class, GreaterThanEval.class);
OPERATION_EVALS_MAP.put(LessEqualPtg.class, LessEqualEval.class);
OPERATION_EVALS_MAP.put(LessThanPtg.class, LessThanEval.class);
OPERATION_EVALS_MAP.put(MultiplyPtg.class, MultiplyEval.class);
OPERATION_EVALS_MAP.put(NotEqualPtg.class, NotEqualEval.class);
OPERATION_EVALS_MAP.put(PowerPtg.class, PowerEval.class);
OPERATION_EVALS_MAP.put(SubtractPtg.class, SubtractEval.class);
OPERATION_EVALS_MAP.put(UnaryMinusPtg.class, UnaryMinusEval.class);
OPERATION_EVALS_MAP.put(UnaryPlusPtg.class, UnaryPlusEval.class);
} }
@ -402,7 +347,7 @@ public class HSSFFormulaEvaluator {
if (optg instanceof AttrPtg) { continue; } if (optg instanceof AttrPtg) { continue; }
if (optg instanceof UnionPtg) { continue; } if (optg instanceof UnionPtg) { continue; }
OperationEval operation = (OperationEval) getOperationEvalForPtg(optg); OperationEval operation = OperationEvaluatorFactory.create(optg);
int numops = operation.getNumberOfOperands(); int numops = operation.getNumberOfOperands();
Eval[] ops = new Eval[numops]; Eval[] ops = new Eval[numops];
@ -557,25 +502,6 @@ public class HSSFFormulaEvaluator {
return values; return values;
} }
/**
* returns the OperationEval concrete impl instance corresponding
* to the suplied operationPtg
* @param ptg
*/
protected static Eval getOperationEvalForPtg(OperationPtg ptg) {
Eval retval = null;
Class clazz = (Class) OPERATION_EVALS_MAP.get(ptg.getClass());
try {
Constructor constructor = clazz.getConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY);
retval = (OperationEval) constructor.newInstance(new Ptg[] { ptg });
}
catch (Exception e) {
throw new RuntimeException("Fatal Error: ", e);
}
return retval;
}
/** /**
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
* one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg, * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,

View File

@ -0,0 +1,165 @@
/* ====================================================================
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.usermodel;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.hssf.record.formula.AddPtg;
import org.apache.poi.hssf.record.formula.ConcatPtg;
import org.apache.poi.hssf.record.formula.DividePtg;
import org.apache.poi.hssf.record.formula.EqualPtg;
import org.apache.poi.hssf.record.formula.ExpPtg;
import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.GreaterEqualPtg;
import org.apache.poi.hssf.record.formula.GreaterThanPtg;
import org.apache.poi.hssf.record.formula.LessEqualPtg;
import org.apache.poi.hssf.record.formula.LessThanPtg;
import org.apache.poi.hssf.record.formula.MultiplyPtg;
import org.apache.poi.hssf.record.formula.NotEqualPtg;
import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.SubtractPtg;
import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.record.formula.eval.AddEval;
import org.apache.poi.hssf.record.formula.eval.ConcatEval;
import org.apache.poi.hssf.record.formula.eval.DivideEval;
import org.apache.poi.hssf.record.formula.eval.EqualEval;
import org.apache.poi.hssf.record.formula.eval.FuncVarEval;
import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval;
import org.apache.poi.hssf.record.formula.eval.GreaterThanEval;
import org.apache.poi.hssf.record.formula.eval.LessEqualEval;
import org.apache.poi.hssf.record.formula.eval.LessThanEval;
import org.apache.poi.hssf.record.formula.eval.MultiplyEval;
import org.apache.poi.hssf.record.formula.eval.NotEqualEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.PercentEval;
import org.apache.poi.hssf.record.formula.eval.PowerEval;
import org.apache.poi.hssf.record.formula.eval.SubtractEval;
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
/**
* This class creates <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt>
* formula tokens.
*
* @author Josh Micich
*/
final class OperationEvaluatorFactory {
private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class };
private static final Map _constructorsByPtgClass = initialiseConstructorsMap();
private OperationEvaluatorFactory() {
// no instances of this class
}
private static Map initialiseConstructorsMap() {
Map m = new HashMap(32);
add(m, AddPtg.class, AddEval.class);
add(m, ConcatPtg.class, ConcatEval.class);
add(m, DividePtg.class, DivideEval.class);
add(m, EqualPtg.class, EqualEval.class);
add(m, FuncPtg.class, FuncVarEval.class);
add(m, FuncVarPtg.class, FuncVarEval.class);
add(m, GreaterEqualPtg.class, GreaterEqualEval.class);
add(m, GreaterThanPtg.class, GreaterThanEval.class);
add(m, LessEqualPtg.class, LessEqualEval.class);
add(m, LessThanPtg.class, LessThanEval.class);
add(m, MultiplyPtg.class, MultiplyEval.class);
add(m, NotEqualPtg.class, NotEqualEval.class);
add(m, PercentPtg.class, PercentEval.class);
add(m, PowerPtg.class, PowerEval.class);
add(m, SubtractPtg.class, SubtractEval.class);
add(m, UnaryMinusPtg.class, UnaryMinusEval.class);
add(m, UnaryPlusPtg.class, UnaryPlusEval.class);
return m;
}
private static void add(Map m, Class ptgClass, Class evalClass) {
// perform some validation now, to keep later exception handlers simple
if(!Ptg.class.isAssignableFrom(ptgClass)) {
throw new IllegalArgumentException("Expected Ptg subclass");
}
if(!OperationEval.class.isAssignableFrom(evalClass)) {
throw new IllegalArgumentException("Expected OperationEval subclass");
}
if (!Modifier.isPublic(evalClass.getModifiers())) {
throw new RuntimeException("Eval class must be public");
}
if (Modifier.isAbstract(evalClass.getModifiers())) {
throw new RuntimeException("Eval class must not be abstract");
}
Constructor constructor;
try {
constructor = evalClass.getDeclaredConstructor(OPERATION_CONSTRUCTOR_CLASS_ARRAY);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Missing constructor");
}
if (!Modifier.isPublic(constructor.getModifiers())) {
throw new RuntimeException("Eval constructor must be public");
}
m.put(ptgClass, constructor);
}
/**
* returns the OperationEval concrete impl instance corresponding
* to the supplied operationPtg
*/
public static OperationEval create(OperationPtg ptg) {
if(ptg == null) {
throw new IllegalArgumentException("ptg must not be null");
}
Class ptgClass = ptg.getClass();
Constructor constructor = (Constructor) _constructorsByPtgClass.get(ptgClass);
if(constructor == null) {
if(ptgClass == ExpPtg.class) {
// ExpPtg is used for array formulas and shared formulas.
// it is currently unsupported, and may not even get implemented here
throw new RuntimeException("ExpPtg currently not supported");
}
throw new RuntimeException("Unexpected operation ptg class (" + ptgClass.getName() + ")");
}
Object result;
Object[] initargs = { ptg };
try {
result = constructor.newInstance(initargs);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
return (OperationEval) result;
}
}

View File

@ -33,6 +33,7 @@ public class AllFormulaEvalTests {
result.addTestSuite(TestExternalFunction.class); result.addTestSuite(TestExternalFunction.class);
result.addTestSuite(TestFormulaBugs.class); result.addTestSuite(TestFormulaBugs.class);
result.addTestSuite(TestFormulasFromSpreadsheet.class); result.addTestSuite(TestFormulasFromSpreadsheet.class);
result.addTestSuite(TestPercentEval.class);
result.addTestSuite(TestUnaryPlusEval.class); result.addTestSuite(TestUnaryPlusEval.class);
return result; return result;
} }

View File

@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
package org.apache.poi.hssf.record.formula.eval; package org.apache.poi.hssf.record.formula.eval;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -59,36 +58,36 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
* Name of the test spreadsheet (found in the standard test data folder) * Name of the test spreadsheet (found in the standard test data folder)
*/ */
public final static String FILENAME = "FormulaEvalTestData.xls"; public final static String FILENAME = "FormulaEvalTestData.xls";
/** /**
* Row (zero-based) in the test spreadsheet where the operator examples start. * Row (zero-based) in the test spreadsheet where the operator examples start.
*/ */
public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23' public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23'
/** /**
* Row (zero-based) in the test spreadsheet where the function examples start. * Row (zero-based) in the test spreadsheet where the function examples start.
*/ */
public static final int START_FUNCTIONS_ROW_INDEX = 83; // Row '84' public static final int START_FUNCTIONS_ROW_INDEX = 87; // Row '88'
/** /**
* Index of the column that contains the function names * Index of the column that contains the function names
*/ */
public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B' public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B'
/** /**
* Used to indicate when there are no more functions left * Used to indicate when there are no more functions left
*/ */
public static final String FUNCTION_NAMES_END_SENTINEL = "<END-OF-FUNCTIONS>"; public static final String FUNCTION_NAMES_END_SENTINEL = "<END-OF-FUNCTIONS>";
/** /**
* Index of the column where the test values start (for each function) * Index of the column where the test values start (for each function)
*/ */
public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D' public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D'
/** /**
* Each function takes 4 rows in the test spreadsheet * Each function takes 4 rows in the test spreadsheet
*/ */
public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4; public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4;
} }
private HSSFWorkbook workbook; private HSSFWorkbook workbook;
private HSSFSheet sheet; private HSSFSheet sheet;
// Note - multiple failures are aggregated before ending. // Note - multiple failures are aggregated before ending.
// If one or more functions fail, a single AssertionFailedError is thrown at the end // If one or more functions fail, a single AssertionFailedError is thrown at the end
@ -97,16 +96,16 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
private int _evaluationFailureCount; private int _evaluationFailureCount;
private int _evaluationSuccessCount; private int _evaluationSuccessCount;
private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) { private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) {
if (row == null) { if (row == null) {
return null; return null;
} }
return row.getCell(columnIndex); return row.getCell(columnIndex);
} }
private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) { private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
if (expected == null) { if (expected == null) {
throw new AssertionFailedError(msg + " - Bad setup data expected value is null"); throw new AssertionFailedError(msg + " - Bad setup data expected value is null");
} }
if(actual == null) { if(actual == null) {
@ -114,121 +113,121 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
} }
if (expected.getCellType() == HSSFCell.CELL_TYPE_STRING) { if (expected.getCellType() == HSSFCell.CELL_TYPE_STRING) {
String value = expected.getRichStringCellValue().getString(); String value = expected.getRichStringCellValue().getString();
if (value.startsWith("#")) { if (value.startsWith("#")) {
// TODO - this code never called // TODO - this code never called
expected.setCellType(HSSFCell.CELL_TYPE_ERROR); expected.setCellType(HSSFCell.CELL_TYPE_ERROR);
// expected.setCellErrorValue(...?); // expected.setCellErrorValue(...?);
} }
} }
switch (expected.getCellType()) { switch (expected.getCellType()) {
case HSSFCell.CELL_TYPE_BLANK: case HSSFCell.CELL_TYPE_BLANK:
assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType()); assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType());
break; break;
case HSSFCell.CELL_TYPE_BOOLEAN: case HSSFCell.CELL_TYPE_BOOLEAN:
assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType()); assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType());
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue()); assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
break; break;
case HSSFCell.CELL_TYPE_ERROR: case HSSFCell.CELL_TYPE_ERROR:
assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType());
if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values
assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue()); assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue());
} }
break; break;
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation 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); throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
case HSSFCell.CELL_TYPE_NUMERIC: case HSSFCell.CELL_TYPE_NUMERIC:
assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType()); assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType());
TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR); TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR);
// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue()); // double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue());
// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue()); // double pctExpected = Math.abs(0.00001*expected.getNumericCellValue());
// assertTrue(msg, delta <= pctExpected); // assertTrue(msg, delta <= pctExpected);
break; break;
case HSSFCell.CELL_TYPE_STRING: case HSSFCell.CELL_TYPE_STRING:
assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType()); assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType());
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString()); assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
break; break;
} }
} }
protected void setUp() throws Exception { protected void setUp() throws Exception {
if (workbook == null) { if (workbook == null) {
String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME; String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME;
FileInputStream fin = new FileInputStream( filePath ); FileInputStream fin = new FileInputStream( filePath );
workbook = new HSSFWorkbook( fin ); workbook = new HSSFWorkbook( fin );
sheet = workbook.getSheetAt( 0 ); sheet = workbook.getSheetAt( 0 );
} }
_functionFailureCount = 0; _functionFailureCount = 0;
_functionSuccessCount = 0; _functionSuccessCount = 0;
_evaluationFailureCount = 0; _evaluationFailureCount = 0;
_evaluationSuccessCount = 0; _evaluationSuccessCount = 0;
} }
public void testFunctionsFromTestSpreadsheet() { public void testFunctionsFromTestSpreadsheet() {
processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null); processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null);
processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null); processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null);
// example for debugging individual functions/operators: // example for debugging individual functions/operators:
// processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval"); // processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval");
// processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE"); // processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE");
// confirm results // confirm results
String successMsg = "There were " String successMsg = "There were "
+ _evaluationSuccessCount + " successful evaluation(s) and " + _evaluationSuccessCount + " successful evaluation(s) and "
+ _functionSuccessCount + " function(s) without error"; + _functionSuccessCount + " function(s) without error";
if(_functionFailureCount > 0) { if(_functionFailureCount > 0) {
String msg = _functionFailureCount + " function(s) failed in " String msg = _functionFailureCount + " function(s) failed in "
+ _evaluationFailureCount + " evaluation(s). " + successMsg; + _evaluationFailureCount + " evaluation(s). " + successMsg;
throw new AssertionFailedError(msg); throw new AssertionFailedError(msg);
} }
if(false) { // normally no output for successful tests if(false) { // normally no output for successful tests
System.out.println(getClass().getName() + ": " + successMsg); System.out.println(getClass().getName() + ": " + successMsg);
} }
} }
/** /**
* @param startRowIndex row index in the spreadsheet where the first function/operator is found * @param startRowIndex row index in the spreadsheet where the first function/operator is found
* @param testFocusFunctionName name of a single function/operator to test alone. * @param testFocusFunctionName name of a single function/operator to test alone.
* Typically pass <code>null</code> to test all functions * Typically pass <code>null</code> to test all functions
*/ */
private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) { private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook); HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook);
int rowIndex = startRowIndex; int rowIndex = startRowIndex;
while (true) { while (true) {
HSSFRow r = sheet.getRow(rowIndex); HSSFRow r = sheet.getRow(rowIndex);
String targetFunctionName = getTargetFunctionName(r); String targetFunctionName = getTargetFunctionName(r);
if(targetFunctionName == null) { if(targetFunctionName == null) {
throw new AssertionFailedError("Test spreadsheet cell empty on row (" throw new AssertionFailedError("Test spreadsheet cell empty on row ("
+ (rowIndex+1) + "). Expected function name or '" + (rowIndex+1) + "). Expected function name or '"
+ SS.FUNCTION_NAMES_END_SENTINEL + "'"); + SS.FUNCTION_NAMES_END_SENTINEL + "'");
} }
if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) { if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) {
// found end of functions list // found end of functions list
break; break;
} }
if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) { if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) {
// expected results are on the row below // expected results are on the row below
HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1); HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1);
if(expectedValuesRow == null) { if(expectedValuesRow == null) {
int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row
throw new AssertionFailedError("Missing expected values row for function '" throw new AssertionFailedError("Missing expected values row for function '"
+ targetFunctionName + " (row " + missingRowNum + ")"); + targetFunctionName + " (row " + missingRowNum + ")");
} }
switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) { switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) {
case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break; case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break;
case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break; case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break;
default: default:
throw new RuntimeException("unexpected result"); throw new RuntimeException("unexpected result");
case Result.NO_EVALUATIONS_FOUND: // do nothing case Result.NO_EVALUATIONS_FOUND: // do nothing
} }
} }
rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION; rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION;
} }
} }
/** /**
@ -236,16 +235,16 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
* @return a constant from the local Result class denoting whether there were any evaluation * @return a constant from the local Result class denoting whether there were any evaluation
* cases, and whether they all succeeded. * cases, and whether they all succeeded.
*/ */
private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName,
HSSFRow formulasRow, HSSFRow expectedValuesRow) { HSSFRow formulasRow, HSSFRow expectedValuesRow) {
int result = Result.NO_EVALUATIONS_FOUND; // so far int result = Result.NO_EVALUATIONS_FOUND; // so far
short endcolnum = formulasRow.getLastCellNum(); short endcolnum = formulasRow.getLastCellNum();
evaluator.setCurrentRow(formulasRow); evaluator.setCurrentRow(formulasRow);
// iterate across the row for all the evaluation cases // iterate across the row for all the evaluation cases
for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) { for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) {
HSSFCell c = formulasRow.getCell(colnum); HSSFCell c = formulasRow.getCell(colnum);
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
continue; continue;
} }
@ -265,13 +264,13 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
printShortStackTrace(System.err, e); printShortStackTrace(System.err, e);
result = Result.SOME_EVALUATIONS_FAILED; result = Result.SOME_EVALUATIONS_FAILED;
} }
} }
return result; return result;
} }
/** /**
* Useful to keep output concise when expecting many failures to be reported by this test case * Useful to keep output concise when expecting many failures to be reported by this test case
*/ */
private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) { private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) {
StackTraceElement[] stes = e.getStackTrace(); StackTraceElement[] stes = e.getStackTrace();
@ -304,8 +303,8 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
} }
/** /**
* @return <code>null</code> if cell is missing, empty or blank * @return <code>null</code> if cell is missing, empty or blank
*/ */
private static String getTargetFunctionName(HSSFRow r) { private static String getTargetFunctionName(HSSFRow r) {
if(r == null) { if(r == null) {
System.err.println("Warning - given null row, can't figure out function name"); System.err.println("Warning - given null row, can't figure out function name");

View File

@ -0,0 +1,82 @@
/* ====================================================================
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 junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.functions.NumericFunctionInvoker;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
/**
* Test for percent operator evaluator.
*
* @author Josh Micich
*/
public final class TestPercentEval extends TestCase {
private static void confirm(ValueEval arg, double expectedResult) {
Eval[] args = {
arg,
};
PercentEval opEval = new PercentEval(new PercentPtg());
double result = NumericFunctionInvoker.invoke(opEval, args, -1, (short)-1);
assertEquals(expectedResult, result, 0);
}
public void testBasic() {
confirm(new NumberEval(5), 0.05);
confirm(new NumberEval(3000), 30.0);
confirm(new NumberEval(-150), -1.5);
confirm(new StringEval("0.2"), 0.002);
confirm(BoolEval.TRUE, 0.01);
}
public void testInSpreadSheet() {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("Sheet1");
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell((short)0);
cell.setCellFormula("B1%");
row.createCell((short)1).setCellValue(50.0);
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
fe.setCurrentRow(row);
CellValue cv;
try {
cv = fe.evaluate(cell);
} catch (RuntimeException e) {
if(e.getCause() instanceof NullPointerException) {
throw new AssertionFailedError("Identified bug 44608");
}
// else some other unexpected error
throw e;
}
assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
assertEquals(0.5, cv.getNumberValue(), 0.0);
}
}