From: Josh Micich
- * Primarily used by test cases when testing for specific parsing exceptions.
null
if name cannot be split at a dot
- */
- private AreaReference parseArea(String name) {
- int dotPos = name.indexOf('.');
- if (dotPos < 0) {
- return null;
- }
- int dotCount = 1;
- while (dotCounttrue
if the specified name is a valid cell reference
- */
- private static boolean isValidCellReference(String str) {
- return CellReference.classifyCellReference(str) == NameType.CELL;
- }
-
-
- /**
- * Note - Excel function names are 'case aware but not case sensitive'. This method may end
- * up creating a defined name record in the workbook if the specified name is not an internal
- * Excel function, and has not been encountered before.
- *
- * @param name case preserved function name (as it was entered/appeared in the formula).
- */
- private ParseNode function(String name) {
- Ptg nameToken = null;
- if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
- // user defined function
- // in the token tree, the name is more or less the first argument
- int nameIndex = book.getNameIndex(name);
- if (nameIndex >= 0) {
- Name hName = book.getNameAt(nameIndex);
- if (!hName.isFunctionName()) {
- throw new FormulaParseException("Attempt to use name '" + name
- + "' as a function, but defined name in workbook does not refer to a function");
- }
-
- // calls to user-defined functions within the workbook
- // get a Name token which points to a defined name record
- nameToken = new NamePtg(nameIndex);
- } else {
- if(book instanceof HSSFWorkbook) {
- nameToken = ((HSSFWorkbook)book).getNameXPtg(name);
- }
- if (nameToken == null) {
- throw new FormulaParseException("Name '" + name
- + "' is completely unknown in the current workbook");
- }
- }
- }
-
- Match('(');
- ParseNode[] args = Arguments();
- Match(')');
-
- return getFunction(name, nameToken, args);
- }
-
- /**
- * Generates the variable function ptg for the formula.
- *
- * For IF Formulas, additional PTGs are added to the tokens
- * @param name a {@link NamePtg} or {@link NameXPtg} or null
- * @param numArgs
- * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
- */
- private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
-
- FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase());
- int numArgs = args.length;
- if(fm == null) {
- if (namePtg == null) {
- throw new IllegalStateException("NamePtg must be supplied for external functions");
- }
- // must be external function
- ParseNode[] allArgs = new ParseNode[numArgs+1];
- allArgs[0] = new ParseNode(namePtg);
- System.arraycopy(args, 0, allArgs, 1, numArgs);
- return new ParseNode(new FuncVarPtg(name, (byte)(numArgs+1)), allArgs);
- }
-
- if (namePtg != null) {
- throw new IllegalStateException("NamePtg no applicable to internal functions");
- }
- boolean isVarArgs = !fm.hasFixedArgsLength();
- int funcIx = fm.getIndex();
- validateNumArgs(args.length, fm);
-
- AbstractFunctionPtg retval;
- if(isVarArgs) {
- retval = new FuncVarPtg(name, (byte)numArgs);
- } else {
- retval = new FuncPtg(funcIx);
- }
- return new ParseNode(retval, args);
- }
-
- private void validateNumArgs(int numArgs, FunctionMetadata fm) {
- if(numArgs < fm.getMinParams()) {
- String msg = "Too few arguments to function '" + fm.getName() + "'. ";
- if(fm.hasFixedArgsLength()) {
- msg += "Expected " + fm.getMinParams();
- } else {
- msg += "At least " + fm.getMinParams() + " were expected";
- }
- msg += " but got " + numArgs + ".";
- throw new FormulaParseException(msg);
- }
- if(numArgs > fm.getMaxParams()) {
- String msg = "Too many arguments to function '" + fm.getName() + "'. ";
- if(fm.hasFixedArgsLength()) {
- msg += "Expected " + fm.getMaxParams();
- } else {
- msg += "At most " + fm.getMaxParams() + " were expected";
- }
- msg += " but got " + numArgs + ".";
- throw new FormulaParseException(msg);
- }
- }
-
- private static boolean isArgumentDelimiter(char ch) {
- return ch == ',' || ch == ')';
- }
-
- /** get arguments to a function */
- private ParseNode[] Arguments() {
- //average 2 args per function
- List temp = new ArrayList(2);
- SkipWhite();
- if(look == ')') {
- return ParseNode.EMPTY_ARRAY;
- }
-
- boolean missedPrevArg = true;
- int numArgs = 0;
- while (true) {
- SkipWhite();
- if (isArgumentDelimiter(look)) {
- if (missedPrevArg) {
- temp.add(new ParseNode(MissingArgPtg.instance));
- numArgs++;
- }
- if (look == ')') {
- break;
- }
- Match(',');
- missedPrevArg = true;
- continue;
- }
- temp.add(comparisonExpression());
- numArgs++;
- missedPrevArg = false;
- SkipWhite();
- if (!isArgumentDelimiter(look)) {
- throw expected("',' or ')'");
- }
- }
- ParseNode[] result = new ParseNode[temp.size()];
- temp.toArray(result);
- return result;
- }
-
- /** Parse and Translate a Math Factor */
- private ParseNode powerFactor() {
- ParseNode result = percentFactor();
- while(true) {
- SkipWhite();
- if(look != '^') {
- return result;
- }
- Match('^');
- ParseNode other = percentFactor();
- result = new ParseNode(PowerPtg.instance, result, other);
- }
- }
-
- private ParseNode percentFactor() {
- ParseNode result = parseSimpleFactor();
- while(true) {
- SkipWhite();
- if(look != '%') {
- return result;
- }
- Match('%');
- result = new ParseNode(PercentPtg.instance, result);
- }
- }
-
-
- /**
- * factors (without ^ or % )
- */
- private ParseNode parseSimpleFactor() {
- SkipWhite();
- switch(look) {
- case '#':
- return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
- case '-':
- Match('-');
- return new ParseNode(UnaryMinusPtg.instance, powerFactor());
- case '+':
- Match('+');
- return new ParseNode(UnaryPlusPtg.instance, powerFactor());
- case '(':
- Match('(');
- ParseNode inside = comparisonExpression();
- Match(')');
- return new ParseNode(ParenthesisPtg.instance, inside);
- case '"':
- return new ParseNode(new StringPtg(parseStringLiteral()));
- case '{':
- Match('{');
- ParseNode arrayNode = parseArray();
- Match('}');
- return arrayNode;
- }
- if (IsAlpha(look) || look == '\''){
- return parseFunctionReferenceOrName();
- }
- // else - assume number
- return new ParseNode(parseNumber());
- }
-
-
- private ParseNode parseArray() {
- List rowsData = new ArrayList();
- while(true) {
- Object[] singleRowData = parseArrayRow();
- rowsData.add(singleRowData);
- if (look == '}') {
- break;
- }
- if (look != ';') {
- throw expected("'}' or ';'");
- }
- Match(';');
- }
- int nRows = rowsData.size();
- Object[][] values2d = new Object[nRows][];
- rowsData.toArray(values2d);
- int nColumns = values2d[0].length;
- checkRowLengths(values2d, nColumns);
-
- return new ParseNode(new ArrayPtg(values2d));
- }
- private void checkRowLengths(Object[][] values2d, int nColumns) {
- for (int i = 0; i < values2d.length; i++) {
- int rowLen = values2d[i].length;
- if (rowLen != nColumns) {
- throw new FormulaParseException("Array row " + i + " has length " + rowLen
- + " but row 0 has length " + nColumns);
- }
- }
- }
-
- private Object[] parseArrayRow() {
- List temp = new ArrayList();
- while (true) {
- temp.add(parseArrayItem());
- SkipWhite();
- switch(look) {
- case '}':
- case ';':
- break;
- case ',':
- Match(',');
- continue;
- default:
- throw expected("'}' or ','");
-
- }
- break;
- }
-
- Object[] result = new Object[temp.size()];
- temp.toArray(result);
- return result;
- }
-
- private Object parseArrayItem() {
- SkipWhite();
- switch(look) {
- case '"': return new UnicodeString(parseStringLiteral());
- case '#': return ErrorConstant.valueOf(parseErrorLiteral());
- case 'F': case 'f':
- case 'T': case 't':
- return parseBooleanLiteral();
- }
- // else assume number
- return convertArrayNumber(parseNumber());
- }
-
- private Boolean parseBooleanLiteral() {
- String iden = parseIdentifier();
- if ("TRUE".equalsIgnoreCase(iden)) {
- return Boolean.TRUE;
- }
- if ("FALSE".equalsIgnoreCase(iden)) {
- return Boolean.FALSE;
- }
- throw expected("'TRUE' or 'FALSE'");
- }
-
- private static Double convertArrayNumber(Ptg ptg) {
- if (ptg instanceof IntPtg) {
- return new Double(((IntPtg)ptg).getValue());
- }
- if (ptg instanceof NumberPtg) {
- return new Double(((NumberPtg)ptg).getValue());
- }
- throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
- }
-
- private Ptg parseNumber() {
- String number2 = null;
- String exponent = null;
- String number1 = GetNum();
-
- if (look == '.') {
- GetChar();
- number2 = GetNum();
- }
-
- if (look == 'E') {
- GetChar();
-
- String sign = "";
- if (look == '+') {
- GetChar();
- } else if (look == '-') {
- GetChar();
- sign = "-";
- }
-
- String number = GetNum();
- if (number == null) {
- throw expected("Integer");
- }
- exponent = sign + number;
- }
-
- if (number1 == null && number2 == null) {
- throw expected("Integer");
- }
-
- return getNumberPtgFromString(number1, number2, exponent);
- }
-
-
- private int parseErrorLiteral() {
- Match('#');
- String part1 = parseIdentifier().toUpperCase();
-
- switch(part1.charAt(0)) {
- case 'V':
- if(part1.equals("VALUE")) {
- Match('!');
- return HSSFErrorConstants.ERROR_VALUE;
- }
- throw expected("#VALUE!");
- case 'R':
- if(part1.equals("REF")) {
- Match('!');
- return HSSFErrorConstants.ERROR_REF;
- }
- throw expected("#REF!");
- case 'D':
- if(part1.equals("DIV")) {
- Match('/');
- Match('0');
- Match('!');
- return HSSFErrorConstants.ERROR_DIV_0;
- }
- throw expected("#DIV/0!");
- case 'N':
- if(part1.equals("NAME")) {
- Match('?'); // only one that ends in '?'
- return HSSFErrorConstants.ERROR_NAME;
- }
- if(part1.equals("NUM")) {
- Match('!');
- return HSSFErrorConstants.ERROR_NUM;
- }
- if(part1.equals("NULL")) {
- Match('!');
- return HSSFErrorConstants.ERROR_NULL;
- }
- if(part1.equals("N")) {
- Match('/');
- if(look != 'A' && look != 'a') {
- throw expected("#N/A");
- }
- Match(look);
- // Note - no '!' or '?' suffix
- return HSSFErrorConstants.ERROR_NA;
- }
- throw expected("#NAME?, #NUM!, #NULL! or #N/A");
-
- }
- throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
- }
-
-
- /**
- * Get a PTG for an integer from its string representation.
- * return Int or Number Ptg based on size of input
- */
- private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
- StringBuffer number = new StringBuffer();
-
- if (number2 == null) {
- number.append(number1);
-
- if (exponent != null) {
- number.append('E');
- number.append(exponent);
- }
-
- String numberStr = number.toString();
- int intVal;
- try {
- intVal = Integer.parseInt(numberStr);
- } catch (NumberFormatException e) {
- return new NumberPtg(numberStr);
- }
- if (IntPtg.isInRange(intVal)) {
- return new IntPtg(intVal);
- }
- return new NumberPtg(numberStr);
- }
-
- if (number1 != null) {
- number.append(number1);
- }
-
- number.append('.');
- number.append(number2);
-
- if (exponent != null) {
- number.append('E');
- number.append(exponent);
- }
-
- return new NumberPtg(number.toString());
- }
-
-
- private String parseStringLiteral() {
- Match('"');
-
- StringBuffer token = new StringBuffer();
- while (true) {
- if (look == '"') {
- GetChar();
- if (look != '"') {
- break;
- }
- }
- token.append(look);
- GetChar();
- }
- return token.toString();
- }
-
- /** Parse and Translate a Math Term */
- private ParseNode Term() {
- ParseNode result = powerFactor();
- while(true) {
- SkipWhite();
- Ptg operator;
- switch(look) {
- case '*':
- Match('*');
- operator = MultiplyPtg.instance;
- break;
- case '/':
- Match('/');
- operator = DividePtg.instance;
- break;
- default:
- return result; // finished with Term
- }
- ParseNode other = powerFactor();
- result = new ParseNode(operator, result, other);
- }
- }
-
- private ParseNode comparisonExpression() {
- ParseNode result = concatExpression();
- while (true) {
- SkipWhite();
- switch(look) {
- case '=':
- case '>':
- case '<':
- Ptg comparisonToken = getComparisonToken();
- ParseNode other = concatExpression();
- result = new ParseNode(comparisonToken, result, other);
- continue;
- }
- return result; // finished with predicate expression
- }
- }
-
- private Ptg getComparisonToken() {
- if(look == '=') {
- Match(look);
- return EqualPtg.instance;
- }
- boolean isGreater = look == '>';
- Match(look);
- if(isGreater) {
- if(look == '=') {
- Match('=');
- return GreaterEqualPtg.instance;
- }
- return GreaterThanPtg.instance;
- }
- switch(look) {
- case '=':
- Match('=');
- return LessEqualPtg.instance;
- case '>':
- Match('>');
- return NotEqualPtg.instance;
- }
- return LessThanPtg.instance;
- }
-
-
- private ParseNode concatExpression() {
- ParseNode result = additiveExpression();
- while (true) {
- SkipWhite();
- if(look != '&') {
- break; // finished with concat expression
- }
- Match('&');
- ParseNode other = additiveExpression();
- result = new ParseNode(ConcatPtg.instance, result, other);
- }
- return result;
- }
-
-
- /** Parse and Translate an Expression */
- private ParseNode additiveExpression() {
- ParseNode result = Term();
- while (true) {
- SkipWhite();
- Ptg operator;
- switch(look) {
- case '+':
- Match('+');
- operator = AddPtg.instance;
- break;
- case '-':
- Match('-');
- operator = SubtractPtg.instance;
- break;
- default:
- return result; // finished with additive expression
- }
- ParseNode other = Term();
- result = new ParseNode(operator, result, other);
- }
- }
-
- //{--------------------------------------------------------------}
- //{ Parse and Translate an Assignment Statement }
- /**
-procedure Assignment;
-var Name: string[8];
-begin
- Name := GetName;
- Match('=');
- Expression;
-
-end;
- **/
-
-
- /**
- * API call to execute the parsing of the formula
- * @deprecated use {@link #parse(String, Workbook)} directly
- */
- public void parse() {
- pointer=0;
- GetChar();
- _rootNode = comparisonExpression();
-
- if(pointer <= formulaLength) {
- String msg = "Unused input [" + formulaString.substring(pointer-1)
- + "] after attempting to parse the formula [" + formulaString + "]";
- throw new FormulaParseException(msg);
- }
- }
-
-
- /*********************************
- * PARSER IMPLEMENTATION ENDS HERE
- * EXCEL SPECIFIC METHODS BELOW
- *******************************/
-
- /** API call to retrive the array of Ptgs created as
- * a result of the parsing
- */
- public Ptg[] getRPNPtg() {
- return getRPNPtg(FORMULA_TYPE_CELL);
- }
-
- public Ptg[] getRPNPtg(int formulaType) {
- OperandClassTransformer oct = new OperandClassTransformer(formulaType);
- // RVA is for 'operand class': 'reference', 'value', 'array'
- oct.transformFormula(_rootNode);
- return ParseNode.toTokenArray(_rootNode);
- }
-
- /**
- * Convenience method which takes in a list then passes it to the
- * other toFormulaString signature.
- * @param book workbook for 3D and named references
- * @param lptgs list of Ptg, can be null or empty
- * @return a human readable String
- */
- public static String toFormulaString(HSSFWorkbook book, List lptgs) {
- String retval = null;
- if (lptgs == null || lptgs.size() == 0) return "#NAME";
- Ptg[] ptgs = new Ptg[lptgs.size()];
- ptgs = (Ptg[])lptgs.toArray(ptgs);
- retval = toFormulaString(book, ptgs);
- return retval;
- }
-
- /**
- * Static method to convert an array of Ptgs in RPN order
- * to a human readable string format in infix mode.
- * @param book workbook for named and 3D references
- * @param ptgs array of Ptg, can be null or empty
- * @return a human readable String
- */
- public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
- return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
- }
-}
diff --git a/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java
new file mode 100644
index 0000000000..c492c5e86e
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java
@@ -0,0 +1,72 @@
+/* ====================================================================
+ 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.model;
+
+import java.util.List;
+
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.formula.FormulaParser;
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;
+import org.apache.poi.ss.formula.FormulaRenderer;
+import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
+
+/**
+ * HSSF wrapper for the {@link FormulaParser}
+ *
+ * @author Josh Micich
+ */
+public final class HSSFFormulaParser {
+
+ private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) {
+ return HSSFEvaluationWorkbook.create(book);
+ }
+
+ private HSSFFormulaParser() {
+ // no instances of this class
+ }
+
+ public static Ptg[] parse(String formula, HSSFWorkbook workbook) {
+ return FormulaParser.parse(formula, createParsingWorkbook(workbook));
+ }
+
+ public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) {
+ return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType);
+ }
+
+ public static String toFormulaString(HSSFWorkbook book, List lptgs) {
+ return toFormulaString(HSSFEvaluationWorkbook.create(book), lptgs);
+ }
+ /**
+ * Convenience method which takes in a list then passes it to the
+ * other toFormulaString signature.
+ * @param book workbook for 3D and named references
+ * @param lptgs list of Ptg, can be null or empty
+ * @return a human readable String
+ */
+ public static String toFormulaString(FormulaRenderingWorkbook book, List lptgs) {
+ Ptg[] ptgs = new Ptg[lptgs.size()];
+ lptgs.toArray(ptgs);
+ return FormulaRenderer.toFormulaString(book, ptgs);
+ }
+
+ public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
+ return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java
deleted file mode 100644
index 8b7b56638b..0000000000
--- a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/* ====================================================================
- 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.model;
-
-import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
-import org.apache.poi.hssf.record.formula.ControlPtg;
-import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
-import org.apache.poi.hssf.record.formula.Ptg;
-
-/**
- * This class performs 'operand class' transformation. Non-base tokens are classified into three
- * operand classes:
- *
true
if one of the current node's parents is a
- * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on
- * the function return value).
- */
- private void transformNode(ParseNode node, byte desiredOperandClass,
- boolean callerForceArrayFlag) {
- Ptg token = node.getToken();
- ParseNode[] children = node.getChildren();
- boolean isSimpleValueFunc = isSimpleValueFunction(token);
-
- if (isSimpleValueFunc) {
- boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY;
- for (int i = 0; i < children.length; i++) {
- transformNode(children[i], desiredOperandClass, localForceArray);
- }
- setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag);
- return;
- }
-
- if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) {
- // Value Operator Ptgs and Control are base tokens, so token will be unchanged
- // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag
-
- // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
- // All direct operands of value operators that are initially 'R' type will
- // be converted to 'V' type.
- byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
- for (int i = 0; i < children.length; i++) {
- transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
- }
- return;
- }
- if (token instanceof AbstractFunctionPtg) {
- transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
- return;
- }
- if (children.length > 0) {
- throw new IllegalStateException("Node should not have any children");
- }
-
- if (token.isBaseToken()) {
- // nothing to do
- return;
- }
- token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
- }
-
- private static boolean isSimpleValueFunction(Ptg token) {
- if (token instanceof AbstractFunctionPtg) {
- AbstractFunctionPtg aptg = (AbstractFunctionPtg) token;
- if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) {
- return false;
- }
- int numberOfOperands = aptg.getNumberOfOperands();
- for (int i=numberOfOperands-1; i>=0; i--) {
- if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
-
- private byte transformClass(byte currentOperandClass, byte desiredOperandClass,
- boolean callerForceArrayFlag) {
- switch (desiredOperandClass) {
- case Ptg.CLASS_VALUE:
- if (!callerForceArrayFlag) {
- return Ptg.CLASS_VALUE;
- }
- // else fall through
- case Ptg.CLASS_ARRAY:
- return Ptg.CLASS_ARRAY;
- case Ptg.CLASS_REF:
- if (!callerForceArrayFlag) {
- return currentOperandClass;
- }
- return Ptg.CLASS_REF;
- }
- throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")");
- }
-
- private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children,
- byte desiredOperandClass, boolean callerForceArrayFlag) {
-
- boolean localForceArrayFlag;
- byte defaultReturnOperandClass = afp.getDefaultOperandClass();
-
- if (callerForceArrayFlag) {
- switch (defaultReturnOperandClass) {
- case Ptg.CLASS_REF:
- if (desiredOperandClass == Ptg.CLASS_REF) {
- afp.setClass(Ptg.CLASS_REF);
- } else {
- afp.setClass(Ptg.CLASS_ARRAY);
- }
- localForceArrayFlag = false;
- break;
- case Ptg.CLASS_ARRAY:
- afp.setClass(Ptg.CLASS_ARRAY);
- localForceArrayFlag = false;
- break;
- case Ptg.CLASS_VALUE:
- afp.setClass(Ptg.CLASS_ARRAY);
- localForceArrayFlag = true;
- break;
- default:
- throw new IllegalStateException("Unexpected operand class ("
- + defaultReturnOperandClass + ")");
- }
- } else {
- if (defaultReturnOperandClass == desiredOperandClass) {
- localForceArrayFlag = false;
- // an alternative would have been to for non-base Ptgs to set their operand class
- // from their default, but this would require the call in many subclasses because
- // the default OC is not known until the end of the constructor
- afp.setClass(defaultReturnOperandClass);
- } else {
- switch (desiredOperandClass) {
- case Ptg.CLASS_VALUE:
- // always OK to set functions to return 'value'
- afp.setClass(Ptg.CLASS_VALUE);
- localForceArrayFlag = false;
- break;
- case Ptg.CLASS_ARRAY:
- switch (defaultReturnOperandClass) {
- case Ptg.CLASS_REF:
- afp.setClass(Ptg.CLASS_REF);
-// afp.setClass(Ptg.CLASS_ARRAY);
- break;
- case Ptg.CLASS_VALUE:
- afp.setClass(Ptg.CLASS_ARRAY);
- break;
- default:
- throw new IllegalStateException("Unexpected operand class ("
- + defaultReturnOperandClass + ")");
- }
- localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE);
- break;
- case Ptg.CLASS_REF:
- switch (defaultReturnOperandClass) {
- case Ptg.CLASS_ARRAY:
- afp.setClass(Ptg.CLASS_ARRAY);
- break;
- case Ptg.CLASS_VALUE:
- afp.setClass(Ptg.CLASS_VALUE);
- break;
- default:
- throw new IllegalStateException("Unexpected operand class ("
- + defaultReturnOperandClass + ")");
- }
- localForceArrayFlag = false;
- break;
- default:
- throw new IllegalStateException("Unexpected operand class ("
- + desiredOperandClass + ")");
- }
-
- }
- }
-
- for (int i = 0; i < children.length; i++) {
- ParseNode child = children[i];
- byte paramOperandClass = afp.getParameterClass(i);
- transformNode(child, paramOperandClass, localForceArrayFlag);
- }
- }
-
- private void setSimpleValueFuncClass(AbstractFunctionPtg afp,
- byte desiredOperandClass, boolean callerForceArrayFlag) {
-
- if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) {
- afp.setClass(Ptg.CLASS_ARRAY);
- } else {
- afp.setClass(Ptg.CLASS_VALUE);
- }
- }
-}
diff --git a/src/java/org/apache/poi/hssf/model/ParseNode.java b/src/java/org/apache/poi/hssf/model/ParseNode.java
deleted file mode 100644
index acd8cb12be..0000000000
--- a/src/java/org/apache/poi/hssf/model/ParseNode.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/* ====================================================================
- 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.model;
-
-import org.apache.poi.hssf.record.formula.AttrPtg;
-import org.apache.poi.hssf.record.formula.FuncVarPtg;
-import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
-/**
- * Represents a syntactic element from a formula by encapsulating the corresponding Ptg
- * token. Each ParseNode may have child ParseNodes in the case when the wrapped
- * Ptg is non-atomic.
- *
- * @author Josh Micich
- */
-final class ParseNode {
-
- public static final ParseNode[] EMPTY_ARRAY = { };
- private final Ptg _token;
- private final ParseNode[] _children;
- private boolean _isIf;
- private final int _tokenCount;
-
- public ParseNode(Ptg token, ParseNode[] children) {
- _token = token;
- _children = children;
- _isIf = isIf(token);
- int tokenCount = 1;
- for (int i = 0; i < children.length; i++) {
- tokenCount += children[i].getTokenCount();
- }
- if (_isIf) {
- // there will be 2 or 3 extra tAttr tokens according to whether the false param is present
- tokenCount += children.length;
- }
- _tokenCount = tokenCount;
- }
- public ParseNode(Ptg token) {
- this(token, EMPTY_ARRAY);
- }
- public ParseNode(Ptg token, ParseNode child0) {
- this(token, new ParseNode[] { child0, });
- }
- public ParseNode(Ptg token, ParseNode child0, ParseNode child1) {
- this(token, new ParseNode[] { child0, child1, });
- }
- private int getTokenCount() {
- return _tokenCount;
- }
-
- /**
- * Collects the array of Ptg tokens for the specified tree.
- */
- public static Ptg[] toTokenArray(ParseNode rootNode) {
- TokenCollector temp = new TokenCollector(rootNode.getTokenCount());
- rootNode.collectPtgs(temp);
- return temp.getResult();
- }
- private void collectPtgs(TokenCollector temp) {
- if (isIf(getToken())) {
- collectIfPtgs(temp);
- return;
- }
- for (int i=0; i< getChildren().length; i++) {
- getChildren()[i].collectPtgs(temp);
- }
- temp.add(getToken());
- }
- /**
- * The IF() function gets marked up with two or three tAttr tokens.
- * Similar logic will be required for CHOOSE() when it is supported
- *
- * See excelfileformat.pdf sec 3.10.5 "tAttr (19H)
- */
- private void collectIfPtgs(TokenCollector temp) {
-
- // condition goes first
- getChildren()[0].collectPtgs(temp);
-
- // placeholder for tAttrIf
- int ifAttrIndex = temp.createPlaceholder();
-
- // true parameter
- getChildren()[1].collectPtgs(temp);
-
- // placeholder for first skip attr
- int skipAfterTrueParamIndex = temp.createPlaceholder();
- int trueParamSize = temp.sumTokenSizes(ifAttrIndex+1, skipAfterTrueParamIndex);
-
- AttrPtg attrIf = new AttrPtg();
- attrIf.setOptimizedIf(true);
- AttrPtg attrSkipAfterTrue = new AttrPtg();
- attrSkipAfterTrue.setGoto(true);
-
- if (getChildren().length > 2) {
- // false param present
-
- // false parameter
- getChildren()[2].collectPtgs(temp);
-
- int skipAfterFalseParamIndex = temp.createPlaceholder();
-
- AttrPtg attrSkipAfterFalse = new AttrPtg();
- attrSkipAfterFalse.setGoto(true);
-
- int falseParamSize = temp.sumTokenSizes(skipAfterTrueParamIndex+1, skipAfterFalseParamIndex);
-
- attrIf.setData((short)(trueParamSize + 4)); // distance to start of false parameter. +4 for skip after true
- attrSkipAfterTrue.setData((short)(falseParamSize + 4 + 4 - 1)); // 1 less than distance to end of if FuncVar(size=4). +4 for attr skip before
- attrSkipAfterFalse.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4).
-
- temp.setPlaceholder(ifAttrIndex, attrIf);
- temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue);
- temp.setPlaceholder(skipAfterFalseParamIndex, attrSkipAfterFalse);
- } else {
- // false parameter not present
- attrIf.setData((short)(trueParamSize + 4)); // distance to start of FuncVar. +4 for skip after true
- attrSkipAfterTrue.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4).
-
- temp.setPlaceholder(ifAttrIndex, attrIf);
- temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue);
- }
-
- temp.add(getToken());
- }
-
- private static boolean isIf(Ptg token) {
- if (token instanceof FuncVarPtg) {
- FuncVarPtg func = (FuncVarPtg) token;
- if (FunctionMetadataRegistry.FUNCTION_NAME_IF.equals(func.getName())) {
- return true;
- }
- }
- return false;
- }
-
- public Ptg getToken() {
- return _token;
- }
-
- public ParseNode[] getChildren() {
- return _children;
- }
-
- private static final class TokenCollector {
-
- private final Ptg[] _ptgs;
- private int _offset;
-
- public TokenCollector(int tokenCount) {
- _ptgs = new Ptg[tokenCount];
- _offset = 0;
- }
-
- public int sumTokenSizes(int fromIx, int toIx) {
- int result = 0;
- for (int i=fromIx; isetEncoding()
first.
+ * set a string value for the cell.
*
- * @param value value to set the cell to. For formulas we'll set the formula
- * string, for String cells we'll set its value. For other types we will
+ * @param value value to set the cell to. For formulas we'll set the formula
+ * cached string result, for String cells we'll set its value. For other types we will
* change the cell to a string cell and set its value.
* If value is null then we will change the cell to a Blank cell.
- * @deprecated Use setCellValue(HSSFRichTextString) instead.
*/
-
- public void setCellValue(String value)
- {
- HSSFRichTextString str = new HSSFRichTextString(value);
- setCellValue(str);
+ public void setCellValue(String value) {
+ HSSFRichTextString str = value == null ? null : new HSSFRichTextString(value);
+ setCellValue(str);
}
/**
@@ -597,12 +593,12 @@ public class HSSFCell implements Cell {
if (rec.getXFIndex() == (short)0) {
rec.setXFIndex((short) 0x0f);
}
- Ptg[] ptgs = FormulaParser.parse(formula, book);
+ Ptg[] ptgs = HSSFFormulaParser.parse(formula, book);
frec.setParsedExpression(ptgs);
}
public String getCellFormula() {
- return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
+ return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
}
/**
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java
index fc34b9da9c..29430b094f 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFConditionalFormattingRule.java
@@ -17,7 +17,7 @@
package org.apache.poi.hssf.usermodel;
-import org.apache.poi.hssf.model.FormulaParser;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.CFRuleRecord;
import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator;
import org.apache.poi.hssf.record.cf.BorderFormatting;
@@ -205,6 +205,6 @@ public final class HSSFConditionalFormattingRule
if(parsedExpression ==null) {
return null;
}
- return FormulaParser.toFormulaString(workbook, parsedExpression);
+ return HSSFFormulaParser.toFormulaString(workbook, parsedExpression);
}
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
index f9399ee8c5..39dbdcd069 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
@@ -1,17 +1,26 @@
package org.apache.poi.hssf.usermodel;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.Workbook;
+import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.ss.formula.EvaluationName;
+import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
/**
* Internal POI use only
*
* @author Josh Micich
*/
-public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook {
+public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {
+ private final HSSFWorkbook _uBook;
private final Workbook _iBook;
public static HSSFEvaluationWorkbook create(HSSFWorkbook book) {
@@ -22,9 +31,58 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook {
}
private HSSFEvaluationWorkbook(HSSFWorkbook book) {
+ _uBook = book;
_iBook = book.getWorkbook();
}
+ public int getExternalSheetIndex(String sheetName) {
+ int sheetIndex = _uBook.getSheetIndex(sheetName);
+ return _iBook.checkExternSheet(sheetIndex);
+ }
+
+ public EvaluationName getName(int index) {
+ return new Name(_iBook.getNameRecord(index), index);
+ }
+
+ public EvaluationName getName(String name) {
+ for(int i=0; i < _iBook.getNumNames(); i++) {
+ NameRecord nr = _iBook.getNameRecord(i);
+ if (name.equalsIgnoreCase(nr.getNameText())) {
+ return new Name(nr, i);
+ }
+ }
+ return null;
+ }
+
+ public int getSheetIndex(Sheet sheet) {
+ return _uBook.getSheetIndex(sheet);
+ }
+
+ public String getSheetName(int sheetIndex) {
+ return _uBook.getSheetName(sheetIndex);
+ }
+
+ public int getNameIndex(String name) {
+ return _uBook.getNameIndex(name);
+ }
+
+ public NameXPtg getNameXPtg(String name) {
+ return _iBook.getNameXPtg(name);
+ }
+
+ public Sheet getSheet(int sheetIndex) {
+ return _uBook.getSheetAt(sheetIndex);
+ }
+
+ public Sheet getSheetByExternSheetIndex(int externSheetIndex) {
+ int sheetIndex = _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
+ return _uBook.getSheetAt(sheetIndex);
+ }
+
+ public HSSFWorkbook getWorkbook() {
+ return _uBook;
+ }
+
public String resolveNameXText(NameXPtg n) {
return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex());
}
@@ -35,4 +93,45 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook {
public String getNameText(NamePtg namePtg) {
return _iBook.getNameRecord(namePtg.getIndex()).getNameText();
}
+ public EvaluationName getName(NamePtg namePtg) {
+ int ix = namePtg.getIndex();
+ return new Name(_iBook.getNameRecord(ix), ix);
+ }
+ public Ptg[] getFormulaTokens(Cell cell) {
+ return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);
+ }
+
+ private static final class Name implements EvaluationName {
+
+ private final NameRecord _nameRecord;
+ private final int _index;
+
+ public Name(NameRecord nameRecord, int index) {
+ _nameRecord = nameRecord;
+ _index = index;
+ }
+
+ public Ptg[] getNameDefinition() {
+ return _nameRecord.getNameDefinition();
+ }
+
+ public String getNameText() {
+ return _nameRecord.getNameText();
+ }
+
+ public boolean hasFormula() {
+ return _nameRecord.hasFormula();
+ }
+
+ public boolean isFunctionName() {
+ return _nameRecord.isFunctionName();
+ }
+
+ public boolean isRange() {
+ return _nameRecord.hasFormula(); // TODO - is this right?
+ }
+ public NamePtg createPtg() {
+ return new NamePtg(_index);
+ }
+ }
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
index 64ec2fd211..9003c1b3bc 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
@@ -1,75 +1,272 @@
-/*
-* 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 org.apache.poi.hssf.model.FormulaParser;
-import org.apache.poi.hssf.record.formula.OperationPtg;
-import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.ss.usermodel.FormulaEvaluator;
-import org.apache.poi.ss.usermodel.Workbook;
-
-/**
- * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
- *
- */
-public class HSSFFormulaEvaluator extends FormulaEvaluator {
- public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {
- super(sheet, workbook);
- }
- public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
- super(workbook);
- }
-
- /**
- * Returns an underlying FormulaParser, for the specified
- * Formula String and HSSFWorkbook.
- * This will allow you to generate the Ptgs yourself, if
- * your needs are more complex than just having the
- * formula evaluated.
- */
- public static FormulaParser getUnderlyingParser(Workbook workbook, String formula) {
- return new FormulaParser(formula, workbook);
- }
-
- /**
- * debug method
- */
- void inspectPtgs(String formula) {
- Ptg[] ptgs = FormulaParser.parse(formula, _workbook);
- System.out.println("+ * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); + *+ * Be aware that your cell will hold both the formula, + * and the result. If you want the cell replaced with + * the result of the formula, use {@link #evaluateInCell(HSSFCell)} + * @param cell The cell to evaluate + * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) + */ + public int evaluateFormulaCell(Cell cell) { + if (cell == null || cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { + return -1; + } + CellValue cv = evaluateFormulaCellValue(cell); + // cell remains a formula cell, but the cached value is changed + setCellValue(cell, cv); + return cv.getCellType(); + } + + /** + * If cell contains formula, it evaluates the formula, and + * puts the formula result back into the cell, in place + * of the old formula. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the same instance of HSSFCell is returned to + * allow chained calls like: + *
+ * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); + *+ * Be aware that your cell value will be changed to hold the + * result of the formula. If you simply want the formula + * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)} + * @param cell + */ + public HSSFCell evaluateInCell(Cell cell) { + if (cell == null) { + return null; + } + HSSFCell result = (HSSFCell) cell; + if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { + CellValue cv = evaluateFormulaCellValue(cell); + setCellType(cell, cv); // cell will no longer be a formula cell + setCellValue(cell, cv); + } + return result; + } + private static void setCellType(Cell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case HSSFCell.CELL_TYPE_BOOLEAN: + case HSSFCell.CELL_TYPE_ERROR: + case HSSFCell.CELL_TYPE_NUMERIC: + case HSSFCell.CELL_TYPE_STRING: + cell.setCellType(cellType); + return; + case HSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case HSSFCell.CELL_TYPE_FORMULA: + // this will never happen, we have already evaluated the formula + } + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + + private static void setCellValue(Cell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case HSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case HSSFCell.CELL_TYPE_ERROR: + cell.setCellErrorValue(cv.getErrorValue()); + break; + case HSSFCell.CELL_TYPE_NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case HSSFCell.CELL_TYPE_STRING: + cell.setCellValue(new HSSFRichTextString(cv.getStringValue())); + break; + case HSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case HSSFCell.CELL_TYPE_FORMULA: + // this will never happen, we have already evaluated the formula + default: + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + } + + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(HSSFWorkbook wb) { + HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb); + for(int i=0; i
true
return code, the caller should
+ * continue evaluation of the specified cell, and also be sure to call
+ * endEvaluate() when complete.false
return code, the caller should
+ * return an evaluation result of
+ * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate().
+ * true
if the specified cell has not been visited yet in the current
+ * evaluation. false
if the specified cell is already being evaluated.
+ */
+ public ValueEval startEvaluate(int sheetIndex, int srcRowNum, int srcColNum) {
+ CellEvaluationFrame cef = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum);
+ if (_evaluationFrames.contains(cef)) {
+ return ErrorEval.CIRCULAR_REF_ERROR;
+ }
+ ValueEval result = _cache.getValue(cef);
+ if (result == null) {
+ _evaluationFrames.add(cef);
+ }
+ return result;
+ }
+
+ /**
+ * Notifies this evaluation tracker that the evaluation of the specified
+ * cell is complete.
+ *
+ * Every successful call to startEvaluate must be followed by a
+ * call to endEvaluate (recommended in a finally block) to enable
+ * proper tracking of which cells are being evaluated at any point in time.
+ *
+ * Assuming a well behaved client, parameters to this method would not be
+ * required. However, they have been included to assert correct behaviour,
+ * and form more meaningful error messages.
+ * @param result
+ */
+ public void endEvaluate(int sheetIndex, int srcRowNum, int srcColNum, ValueEval result) {
+ int nFrames = _evaluationFrames.size();
+ if (nFrames < 1) {
+ throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
+ }
+
+ nFrames--;
+ CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames);
+ CellEvaluationFrame cefActual = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum);
+ if (!cefActual.equals(cefExpected)) {
+ throw new RuntimeException("Wrong cell specified. "
+ + "Corresponding startEvaluate() call was for cell {"
+ + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {"
+ + cefActual.formatAsString() + "}");
+ }
+ // else - no problems so pop current frame
+ _evaluationFrames.remove(nFrames);
+
+ _cache.setValue(cefActual, result);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
new file mode 100644
index 0000000000..adf69d0d51
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
@@ -0,0 +1,42 @@
+/* ====================================================================
+ 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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.NamePtg;
+import org.apache.poi.hssf.record.formula.NameXPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+/**
+ * Abstracts a workbook for the purpose of formula evaluation.null
if name cannot be split at a dot
+ */
+ private AreaReference parseArea(String name) {
+ int dotPos = name.indexOf('.');
+ if (dotPos < 0) {
+ return null;
+ }
+ int dotCount = 1;
+ while (dotCounttrue
if the specified name is a valid cell reference
+ */
+ private static boolean isValidCellReference(String str) {
+ return CellReference.classifyCellReference(str) == NameType.CELL;
+ }
+
+
+ /**
+ * Note - Excel function names are 'case aware but not case sensitive'. This method may end
+ * up creating a defined name record in the workbook if the specified name is not an internal
+ * Excel function, and has not been encountered before.
+ *
+ * @param name case preserved function name (as it was entered/appeared in the formula).
+ */
+ private ParseNode function(String name) {
+ Ptg nameToken = null;
+ if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
+ // user defined function
+ // in the token tree, the name is more or less the first argument
+
+ EvaluationName hName = book.getName(name);
+ if (hName == null) {
+
+ nameToken = book.getNameXPtg(name);
+ if (nameToken == null) {
+ throw new FormulaParseException("Name '" + name
+ + "' is completely unknown in the current workbook");
+ }
+ } else {
+ if (!hName.isFunctionName()) {
+ throw new FormulaParseException("Attempt to use name '" + name
+ + "' as a function, but defined name in workbook does not refer to a function");
+ }
+
+ // calls to user-defined functions within the workbook
+ // get a Name token which points to a defined name record
+ nameToken = hName.createPtg();
+ }
+ }
+
+ Match('(');
+ ParseNode[] args = Arguments();
+ Match(')');
+
+ return getFunction(name, nameToken, args);
+ }
+
+ /**
+ * Generates the variable function ptg for the formula.
+ *
+ * For IF Formulas, additional PTGs are added to the tokens
+ * @param name a {@link NamePtg} or {@link NameXPtg} or null
+ * @param numArgs
+ * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
+ */
+ private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
+
+ FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase());
+ int numArgs = args.length;
+ if(fm == null) {
+ if (namePtg == null) {
+ throw new IllegalStateException("NamePtg must be supplied for external functions");
+ }
+ // must be external function
+ ParseNode[] allArgs = new ParseNode[numArgs+1];
+ allArgs[0] = new ParseNode(namePtg);
+ System.arraycopy(args, 0, allArgs, 1, numArgs);
+ return new ParseNode(new FuncVarPtg(name, (byte)(numArgs+1)), allArgs);
+ }
+
+ if (namePtg != null) {
+ throw new IllegalStateException("NamePtg no applicable to internal functions");
+ }
+ boolean isVarArgs = !fm.hasFixedArgsLength();
+ int funcIx = fm.getIndex();
+ validateNumArgs(args.length, fm);
+
+ AbstractFunctionPtg retval;
+ if(isVarArgs) {
+ retval = new FuncVarPtg(name, (byte)numArgs);
+ } else {
+ retval = new FuncPtg(funcIx);
+ }
+ return new ParseNode(retval, args);
+ }
+
+ private void validateNumArgs(int numArgs, FunctionMetadata fm) {
+ if(numArgs < fm.getMinParams()) {
+ String msg = "Too few arguments to function '" + fm.getName() + "'. ";
+ if(fm.hasFixedArgsLength()) {
+ msg += "Expected " + fm.getMinParams();
+ } else {
+ msg += "At least " + fm.getMinParams() + " were expected";
+ }
+ msg += " but got " + numArgs + ".";
+ throw new FormulaParseException(msg);
+ }
+ if(numArgs > fm.getMaxParams()) {
+ String msg = "Too many arguments to function '" + fm.getName() + "'. ";
+ if(fm.hasFixedArgsLength()) {
+ msg += "Expected " + fm.getMaxParams();
+ } else {
+ msg += "At most " + fm.getMaxParams() + " were expected";
+ }
+ msg += " but got " + numArgs + ".";
+ throw new FormulaParseException(msg);
+ }
+ }
+
+ private static boolean isArgumentDelimiter(char ch) {
+ return ch == ',' || ch == ')';
+ }
+
+ /** get arguments to a function */
+ private ParseNode[] Arguments() {
+ //average 2 args per function
+ List temp = new ArrayList(2);
+ SkipWhite();
+ if(look == ')') {
+ return ParseNode.EMPTY_ARRAY;
+ }
+
+ boolean missedPrevArg = true;
+ int numArgs = 0;
+ while (true) {
+ SkipWhite();
+ if (isArgumentDelimiter(look)) {
+ if (missedPrevArg) {
+ temp.add(new ParseNode(MissingArgPtg.instance));
+ numArgs++;
+ }
+ if (look == ')') {
+ break;
+ }
+ Match(',');
+ missedPrevArg = true;
+ continue;
+ }
+ temp.add(comparisonExpression());
+ numArgs++;
+ missedPrevArg = false;
+ SkipWhite();
+ if (!isArgumentDelimiter(look)) {
+ throw expected("',' or ')'");
+ }
+ }
+ ParseNode[] result = new ParseNode[temp.size()];
+ temp.toArray(result);
+ return result;
+ }
+
+ /** Parse and Translate a Math Factor */
+ private ParseNode powerFactor() {
+ ParseNode result = percentFactor();
+ while(true) {
+ SkipWhite();
+ if(look != '^') {
+ return result;
+ }
+ Match('^');
+ ParseNode other = percentFactor();
+ result = new ParseNode(PowerPtg.instance, result, other);
+ }
+ }
+
+ private ParseNode percentFactor() {
+ ParseNode result = parseSimpleFactor();
+ while(true) {
+ SkipWhite();
+ if(look != '%') {
+ return result;
+ }
+ Match('%');
+ result = new ParseNode(PercentPtg.instance, result);
+ }
+ }
+
+
+ /**
+ * factors (without ^ or % )
+ */
+ private ParseNode parseSimpleFactor() {
+ SkipWhite();
+ switch(look) {
+ case '#':
+ return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
+ case '-':
+ Match('-');
+ return new ParseNode(UnaryMinusPtg.instance, powerFactor());
+ case '+':
+ Match('+');
+ return new ParseNode(UnaryPlusPtg.instance, powerFactor());
+ case '(':
+ Match('(');
+ ParseNode inside = comparisonExpression();
+ Match(')');
+ return new ParseNode(ParenthesisPtg.instance, inside);
+ case '"':
+ return new ParseNode(new StringPtg(parseStringLiteral()));
+ case '{':
+ Match('{');
+ ParseNode arrayNode = parseArray();
+ Match('}');
+ return arrayNode;
+ }
+ if (IsAlpha(look) || look == '\''){
+ return parseFunctionReferenceOrName();
+ }
+ // else - assume number
+ return new ParseNode(parseNumber());
+ }
+
+
+ private ParseNode parseArray() {
+ List rowsData = new ArrayList();
+ while(true) {
+ Object[] singleRowData = parseArrayRow();
+ rowsData.add(singleRowData);
+ if (look == '}') {
+ break;
+ }
+ if (look != ';') {
+ throw expected("'}' or ';'");
+ }
+ Match(';');
+ }
+ int nRows = rowsData.size();
+ Object[][] values2d = new Object[nRows][];
+ rowsData.toArray(values2d);
+ int nColumns = values2d[0].length;
+ checkRowLengths(values2d, nColumns);
+
+ return new ParseNode(new ArrayPtg(values2d));
+ }
+ private void checkRowLengths(Object[][] values2d, int nColumns) {
+ for (int i = 0; i < values2d.length; i++) {
+ int rowLen = values2d[i].length;
+ if (rowLen != nColumns) {
+ throw new FormulaParseException("Array row " + i + " has length " + rowLen
+ + " but row 0 has length " + nColumns);
+ }
+ }
+ }
+
+ private Object[] parseArrayRow() {
+ List temp = new ArrayList();
+ while (true) {
+ temp.add(parseArrayItem());
+ SkipWhite();
+ switch(look) {
+ case '}':
+ case ';':
+ break;
+ case ',':
+ Match(',');
+ continue;
+ default:
+ throw expected("'}' or ','");
+
+ }
+ break;
+ }
+
+ Object[] result = new Object[temp.size()];
+ temp.toArray(result);
+ return result;
+ }
+
+ private Object parseArrayItem() {
+ SkipWhite();
+ switch(look) {
+ case '"': return new UnicodeString(parseStringLiteral());
+ case '#': return ErrorConstant.valueOf(parseErrorLiteral());
+ case 'F': case 'f':
+ case 'T': case 't':
+ return parseBooleanLiteral();
+ }
+ // else assume number
+ return convertArrayNumber(parseNumber());
+ }
+
+ private Boolean parseBooleanLiteral() {
+ String iden = parseIdentifier();
+ if ("TRUE".equalsIgnoreCase(iden)) {
+ return Boolean.TRUE;
+ }
+ if ("FALSE".equalsIgnoreCase(iden)) {
+ return Boolean.FALSE;
+ }
+ throw expected("'TRUE' or 'FALSE'");
+ }
+
+ private static Double convertArrayNumber(Ptg ptg) {
+ if (ptg instanceof IntPtg) {
+ return new Double(((IntPtg)ptg).getValue());
+ }
+ if (ptg instanceof NumberPtg) {
+ return new Double(((NumberPtg)ptg).getValue());
+ }
+ throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
+ }
+
+ private Ptg parseNumber() {
+ String number2 = null;
+ String exponent = null;
+ String number1 = GetNum();
+
+ if (look == '.') {
+ GetChar();
+ number2 = GetNum();
+ }
+
+ if (look == 'E') {
+ GetChar();
+
+ String sign = "";
+ if (look == '+') {
+ GetChar();
+ } else if (look == '-') {
+ GetChar();
+ sign = "-";
+ }
+
+ String number = GetNum();
+ if (number == null) {
+ throw expected("Integer");
+ }
+ exponent = sign + number;
+ }
+
+ if (number1 == null && number2 == null) {
+ throw expected("Integer");
+ }
+
+ return getNumberPtgFromString(number1, number2, exponent);
+ }
+
+
+ private int parseErrorLiteral() {
+ Match('#');
+ String part1 = parseIdentifier().toUpperCase();
+
+ switch(part1.charAt(0)) {
+ case 'V':
+ if(part1.equals("VALUE")) {
+ Match('!');
+ return HSSFErrorConstants.ERROR_VALUE;
+ }
+ throw expected("#VALUE!");
+ case 'R':
+ if(part1.equals("REF")) {
+ Match('!');
+ return HSSFErrorConstants.ERROR_REF;
+ }
+ throw expected("#REF!");
+ case 'D':
+ if(part1.equals("DIV")) {
+ Match('/');
+ Match('0');
+ Match('!');
+ return HSSFErrorConstants.ERROR_DIV_0;
+ }
+ throw expected("#DIV/0!");
+ case 'N':
+ if(part1.equals("NAME")) {
+ Match('?'); // only one that ends in '?'
+ return HSSFErrorConstants.ERROR_NAME;
+ }
+ if(part1.equals("NUM")) {
+ Match('!');
+ return HSSFErrorConstants.ERROR_NUM;
+ }
+ if(part1.equals("NULL")) {
+ Match('!');
+ return HSSFErrorConstants.ERROR_NULL;
+ }
+ if(part1.equals("N")) {
+ Match('/');
+ if(look != 'A' && look != 'a') {
+ throw expected("#N/A");
+ }
+ Match(look);
+ // Note - no '!' or '?' suffix
+ return HSSFErrorConstants.ERROR_NA;
+ }
+ throw expected("#NAME?, #NUM!, #NULL! or #N/A");
+
+ }
+ throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
+ }
+
+
+ /**
+ * Get a PTG for an integer from its string representation.
+ * return Int or Number Ptg based on size of input
+ */
+ private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
+ StringBuffer number = new StringBuffer();
+
+ if (number2 == null) {
+ number.append(number1);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+
+ String numberStr = number.toString();
+ int intVal;
+ try {
+ intVal = Integer.parseInt(numberStr);
+ } catch (NumberFormatException e) {
+ return new NumberPtg(numberStr);
+ }
+ if (IntPtg.isInRange(intVal)) {
+ return new IntPtg(intVal);
+ }
+ return new NumberPtg(numberStr);
+ }
+
+ if (number1 != null) {
+ number.append(number1);
+ }
+
+ number.append('.');
+ number.append(number2);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+
+ return new NumberPtg(number.toString());
+ }
+
+
+ private String parseStringLiteral() {
+ Match('"');
+
+ StringBuffer token = new StringBuffer();
+ while (true) {
+ if (look == '"') {
+ GetChar();
+ if (look != '"') {
+ break;
+ }
+ }
+ token.append(look);
+ GetChar();
+ }
+ return token.toString();
+ }
+
+ /** Parse and Translate a Math Term */
+ private ParseNode Term() {
+ ParseNode result = powerFactor();
+ while(true) {
+ SkipWhite();
+ Ptg operator;
+ switch(look) {
+ case '*':
+ Match('*');
+ operator = MultiplyPtg.instance;
+ break;
+ case '/':
+ Match('/');
+ operator = DividePtg.instance;
+ break;
+ default:
+ return result; // finished with Term
+ }
+ ParseNode other = powerFactor();
+ result = new ParseNode(operator, result, other);
+ }
+ }
+
+ private ParseNode comparisonExpression() {
+ ParseNode result = concatExpression();
+ while (true) {
+ SkipWhite();
+ switch(look) {
+ case '=':
+ case '>':
+ case '<':
+ Ptg comparisonToken = getComparisonToken();
+ ParseNode other = concatExpression();
+ result = new ParseNode(comparisonToken, result, other);
+ continue;
+ }
+ return result; // finished with predicate expression
+ }
+ }
+
+ private Ptg getComparisonToken() {
+ if(look == '=') {
+ Match(look);
+ return EqualPtg.instance;
+ }
+ boolean isGreater = look == '>';
+ Match(look);
+ if(isGreater) {
+ if(look == '=') {
+ Match('=');
+ return GreaterEqualPtg.instance;
+ }
+ return GreaterThanPtg.instance;
+ }
+ switch(look) {
+ case '=':
+ Match('=');
+ return LessEqualPtg.instance;
+ case '>':
+ Match('>');
+ return NotEqualPtg.instance;
+ }
+ return LessThanPtg.instance;
+ }
+
+
+ private ParseNode concatExpression() {
+ ParseNode result = additiveExpression();
+ while (true) {
+ SkipWhite();
+ if(look != '&') {
+ break; // finished with concat expression
+ }
+ Match('&');
+ ParseNode other = additiveExpression();
+ result = new ParseNode(ConcatPtg.instance, result, other);
+ }
+ return result;
+ }
+
+
+ /** Parse and Translate an Expression */
+ private ParseNode additiveExpression() {
+ ParseNode result = Term();
+ while (true) {
+ SkipWhite();
+ Ptg operator;
+ switch(look) {
+ case '+':
+ Match('+');
+ operator = AddPtg.instance;
+ break;
+ case '-':
+ Match('-');
+ operator = SubtractPtg.instance;
+ break;
+ default:
+ return result; // finished with additive expression
+ }
+ ParseNode other = Term();
+ result = new ParseNode(operator, result, other);
+ }
+ }
+
+ //{--------------------------------------------------------------}
+ //{ Parse and Translate an Assignment Statement }
+ /**
+procedure Assignment;
+var Name: string[8];
+begin
+ Name := GetName;
+ Match('=');
+ Expression;
+
+end;
+ **/
+
+
+ /**
+ * API call to execute the parsing of the formula
+ *
+ */
+ private void parse() {
+ pointer=0;
+ GetChar();
+ _rootNode = comparisonExpression();
+
+ if(pointer <= formulaLength) {
+ String msg = "Unused input [" + formulaString.substring(pointer-1)
+ + "] after attempting to parse the formula [" + formulaString + "]";
+ throw new FormulaParseException(msg);
+ }
+ }
+
+ private Ptg[] getRPNPtg(int formulaType) {
+ OperandClassTransformer oct = new OperandClassTransformer(formulaType);
+ // RVA is for 'operand class': 'reference', 'value', 'array'
+ oct.transformFormula(_rootNode);
+ return ParseNode.toTokenArray(_rootNode);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
new file mode 100644
index 0000000000..69431c2c22
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
@@ -0,0 +1,37 @@
+/* ====================================================================
+ 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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.NameXPtg;
+
+/**
+ * Abstracts a workbook for the purpose of formula parsing.
+ *
+ * For POI internal use only
+ *
+ * @author Josh Micich
+ */
+public interface FormulaParsingWorkbook {
+ /**
+ * named range name matching is case insensitive
+ */
+ EvaluationName getName(String name);
+
+ int getExternalSheetIndex(String sheetName);
+ NameXPtg getNameXPtg(String name);
+}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaType.java b/src/java/org/apache/poi/ss/formula/FormulaType.java
new file mode 100644
index 0000000000..3b47030d47
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/FormulaType.java
@@ -0,0 +1,40 @@
+/* ====================================================================
+ 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.ss.formula;
+
+/**
+ * Enumeration of various formula types.
+ *
+ * For POI internal use only
+ *
+ * @author Josh Micich
+ */
+public final class FormulaType {
+ private FormulaType() {
+ // no instances of this class
+ }
+ public static final int CELL = 0;
+ public static final int SHARED = 1;
+ public static final int ARRAY =2;
+ public static final int CONDFORMAT = 3;
+ public static final int NAMEDRANGE = 4;
+ // this constant is currently very specific. The exact differences from general data
+ // validation formulas or conditional format formulas is not known yet
+ public static final int DATAVALIDATION_LIST = 5;
+
+}
diff --git a/src/java/org/apache/poi/ss/formula/LazyAreaEval.java b/src/java/org/apache/poi/ss/formula/LazyAreaEval.java
new file mode 100644
index 0000000000..5816f0052b
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/LazyAreaEval.java
@@ -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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.AreaI;
+import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+import org.apache.poi.hssf.record.formula.eval.AreaEvalBase;
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.hssf.util.CellReference;
+
+/**
+ *
+ * @author Josh Micich
+ */
+final class LazyAreaEval extends AreaEvalBase {
+
+ private final Sheet _sheet;
+ private final CellEvaluator _evaluator;
+
+ public LazyAreaEval(AreaI ptg, Sheet sheet, CellEvaluator evaluator) {
+ super(ptg);
+ _sheet = sheet;
+ _evaluator = evaluator;
+ }
+
+ public ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex) {
+
+ int rowIx = (relativeRowIndex + getFirstRow() ) & 0xFFFF;
+ int colIx = (relativeColumnIndex + getFirstColumn() ) & 0x00FF;
+
+ Row row = _sheet.getRow(rowIx);
+ if (row == null) {
+ return BlankEval.INSTANCE;
+ }
+ Cell cell = row.getCell(colIx);
+ if (cell == null) {
+ return BlankEval.INSTANCE;
+ }
+ return _evaluator.getEvalForCell(cell);
+ }
+
+ public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
+ AreaI area = new OffsetArea(getFirstRow(), getFirstColumn(),
+ relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
+
+ return new LazyAreaEval(area, _sheet, _evaluator);
+ }
+ public String toString() {
+ CellReference crA = new CellReference(getFirstRow(), getFirstColumn());
+ CellReference crB = new CellReference(getLastRow(), getLastColumn());
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName()).append("[");
+ String sheetName = _evaluator.getSheetName(_sheet);
+ sb.append(sheetName);
+ sb.append('!');
+ sb.append(crA.formatAsString());
+ sb.append(':');
+ sb.append(crB.formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/LazyRefEval.java b/src/java/org/apache/poi/ss/formula/LazyRefEval.java
new file mode 100644
index 0000000000..2d80585fcb
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/LazyRefEval.java
@@ -0,0 +1,88 @@
+/* ====================================================================
+ 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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.AreaI;
+import org.apache.poi.hssf.record.formula.Ref3DPtg;
+import org.apache.poi.hssf.record.formula.RefPtg;
+import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
+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.RefEvalBase;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.hssf.util.CellReference;
+
+/**
+*
+* @author Josh Micich
+*/
+final class LazyRefEval extends RefEvalBase {
+
+ private final Sheet _sheet;
+ private final CellEvaluator _evaluator;
+
+
+ public LazyRefEval(RefPtg ptg, Sheet sheet, CellEvaluator evaluator) {
+ super(ptg.getRow(), ptg.getColumn());
+ _sheet = sheet;
+ _evaluator = evaluator;
+ }
+ public LazyRefEval(Ref3DPtg ptg, Sheet sheet, CellEvaluator evaluator) {
+ super(ptg.getRow(), ptg.getColumn());
+ _sheet = sheet;
+ _evaluator = evaluator;
+ }
+
+ public ValueEval getInnerValueEval() {
+ int rowIx = getRow();
+ int colIx = getColumn();
+
+ Row row = _sheet.getRow(rowIx);
+ if (row == null) {
+ return BlankEval.INSTANCE;
+ }
+ Cell cell = row.getCell(colIx);
+ if (cell == null) {
+ return BlankEval.INSTANCE;
+ }
+ return _evaluator.getEvalForCell(cell);
+ }
+
+ public AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx) {
+
+ AreaI area = new OffsetArea(getRow(), getColumn(),
+ relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
+
+ return new LazyAreaEval(area, _sheet, _evaluator);
+ }
+
+ public String toString() {
+ CellReference cr = new CellReference(getRow(), getColumn());
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName()).append("[");
+ String sheetName = _evaluator.getSheetName(_sheet);
+ sb.append(sheetName);
+ sb.append('!');
+ sb.append(cr.formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java
new file mode 100644
index 0000000000..79087e4d6d
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/OperandClassTransformer.java
@@ -0,0 +1,259 @@
+/* ====================================================================
+ 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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
+import org.apache.poi.hssf.record.formula.ControlPtg;
+import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+
+/**
+ * This class performs 'operand class' transformation. Non-base tokens are classified into three
+ * operand classes:
+ *
true
if one of the current node's parents is a
+ * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on
+ * the function return value).
+ */
+ private void transformNode(ParseNode node, byte desiredOperandClass,
+ boolean callerForceArrayFlag) {
+ Ptg token = node.getToken();
+ ParseNode[] children = node.getChildren();
+ boolean isSimpleValueFunc = isSimpleValueFunction(token);
+
+ if (isSimpleValueFunc) {
+ boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY;
+ for (int i = 0; i < children.length; i++) {
+ transformNode(children[i], desiredOperandClass, localForceArray);
+ }
+ setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag);
+ return;
+ }
+
+ if (token instanceof ValueOperatorPtg || token instanceof ControlPtg) {
+ // Value Operator Ptgs and Control are base tokens, so token will be unchanged
+ // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag
+
+ // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
+ // All direct operands of value operators that are initially 'R' type will
+ // be converted to 'V' type.
+ byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
+ for (int i = 0; i < children.length; i++) {
+ transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
+ }
+ return;
+ }
+ if (token instanceof AbstractFunctionPtg) {
+ transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
+ return;
+ }
+ if (children.length > 0) {
+ throw new IllegalStateException("Node should not have any children");
+ }
+
+ if (token.isBaseToken()) {
+ // nothing to do
+ return;
+ }
+ token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
+ }
+
+ private static boolean isSimpleValueFunction(Ptg token) {
+ if (token instanceof AbstractFunctionPtg) {
+ AbstractFunctionPtg aptg = (AbstractFunctionPtg) token;
+ if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) {
+ return false;
+ }
+ int numberOfOperands = aptg.getNumberOfOperands();
+ for (int i=numberOfOperands-1; i>=0; i--) {
+ if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private byte transformClass(byte currentOperandClass, byte desiredOperandClass,
+ boolean callerForceArrayFlag) {
+ switch (desiredOperandClass) {
+ case Ptg.CLASS_VALUE:
+ if (!callerForceArrayFlag) {
+ return Ptg.CLASS_VALUE;
+ }
+ // else fall through
+ case Ptg.CLASS_ARRAY:
+ return Ptg.CLASS_ARRAY;
+ case Ptg.CLASS_REF:
+ if (!callerForceArrayFlag) {
+ return currentOperandClass;
+ }
+ return Ptg.CLASS_REF;
+ }
+ throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")");
+ }
+
+ private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children,
+ byte desiredOperandClass, boolean callerForceArrayFlag) {
+
+ boolean localForceArrayFlag;
+ byte defaultReturnOperandClass = afp.getDefaultOperandClass();
+
+ if (callerForceArrayFlag) {
+ switch (defaultReturnOperandClass) {
+ case Ptg.CLASS_REF:
+ if (desiredOperandClass == Ptg.CLASS_REF) {
+ afp.setClass(Ptg.CLASS_REF);
+ } else {
+ afp.setClass(Ptg.CLASS_ARRAY);
+ }
+ localForceArrayFlag = false;
+ break;
+ case Ptg.CLASS_ARRAY:
+ afp.setClass(Ptg.CLASS_ARRAY);
+ localForceArrayFlag = false;
+ break;
+ case Ptg.CLASS_VALUE:
+ afp.setClass(Ptg.CLASS_ARRAY);
+ localForceArrayFlag = true;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected operand class ("
+ + defaultReturnOperandClass + ")");
+ }
+ } else {
+ if (defaultReturnOperandClass == desiredOperandClass) {
+ localForceArrayFlag = false;
+ // an alternative would have been to for non-base Ptgs to set their operand class
+ // from their default, but this would require the call in many subclasses because
+ // the default OC is not known until the end of the constructor
+ afp.setClass(defaultReturnOperandClass);
+ } else {
+ switch (desiredOperandClass) {
+ case Ptg.CLASS_VALUE:
+ // always OK to set functions to return 'value'
+ afp.setClass(Ptg.CLASS_VALUE);
+ localForceArrayFlag = false;
+ break;
+ case Ptg.CLASS_ARRAY:
+ switch (defaultReturnOperandClass) {
+ case Ptg.CLASS_REF:
+ afp.setClass(Ptg.CLASS_REF);
+// afp.setClass(Ptg.CLASS_ARRAY);
+ break;
+ case Ptg.CLASS_VALUE:
+ afp.setClass(Ptg.CLASS_ARRAY);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected operand class ("
+ + defaultReturnOperandClass + ")");
+ }
+ localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE);
+ break;
+ case Ptg.CLASS_REF:
+ switch (defaultReturnOperandClass) {
+ case Ptg.CLASS_ARRAY:
+ afp.setClass(Ptg.CLASS_ARRAY);
+ break;
+ case Ptg.CLASS_VALUE:
+ afp.setClass(Ptg.CLASS_VALUE);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected operand class ("
+ + defaultReturnOperandClass + ")");
+ }
+ localForceArrayFlag = false;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected operand class ("
+ + desiredOperandClass + ")");
+ }
+
+ }
+ }
+
+ for (int i = 0; i < children.length; i++) {
+ ParseNode child = children[i];
+ byte paramOperandClass = afp.getParameterClass(i);
+ transformNode(child, paramOperandClass, localForceArrayFlag);
+ }
+ }
+
+ private void setSimpleValueFuncClass(AbstractFunctionPtg afp,
+ byte desiredOperandClass, boolean callerForceArrayFlag) {
+
+ if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) {
+ afp.setClass(Ptg.CLASS_ARRAY);
+ } else {
+ afp.setClass(Ptg.CLASS_VALUE);
+ }
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java
new file mode 100755
index 0000000000..eaa57c114f
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java
@@ -0,0 +1,184 @@
+/* ====================================================================
+ 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.ss.formula;
+
+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 OperationEval instances to help evaluate OperationPtg
+ * formula tokens.
+ *
+ * @author Josh Micich
+ */
+final class OperationEvaluatorFactory {
+ private static final Class[] OPERATION_CONSTRUCTOR_CLASS_ARRAY = new Class[] { Ptg.class };
+ // TODO - use singleton instances directly instead of reflection
+ private static final Map _constructorsByPtgClass = initialiseConstructorsMap();
+ private static final Map _instancesByPtgClass = initialiseInstancesMap();
+
+ private OperationEvaluatorFactory() {
+ // no instances of this class
+ }
+
+ private static Map initialiseConstructorsMap() {
+ Map m = new HashMap(32);
+ add(m, ConcatPtg.class, ConcatEval.class);
+ add(m, FuncPtg.class, FuncVarEval.class);
+ add(m, FuncVarPtg.class, FuncVarEval.class);
+ return m;
+ }
+ private static Map initialiseInstancesMap() {
+ Map m = new HashMap(32);
+ add(m, EqualPtg.class, EqualEval.instance);
+ add(m, GreaterEqualPtg.class, GreaterEqualEval.instance);
+ add(m, GreaterThanPtg.class, GreaterThanEval.instance);
+ add(m, LessEqualPtg.class, LessEqualEval.instance);
+ add(m, LessThanPtg.class, LessThanEval.instance);
+ add(m, NotEqualPtg.class, NotEqualEval.instance);
+
+ add(m, AddPtg.class, AddEval.instance);
+ add(m, DividePtg.class, DivideEval.instance);
+ add(m, MultiplyPtg.class, MultiplyEval.instance);
+ add(m, PercentPtg.class, PercentEval.instance);
+ add(m, PowerPtg.class, PowerEval.instance);
+ add(m, SubtractPtg.class, SubtractEval.instance);
+ add(m, UnaryMinusPtg.class, UnaryMinusEval.instance);
+ add(m, UnaryPlusPtg.class, UnaryPlusEval.instance);
+ return m;
+ }
+
+ private static void add(Map m, Class ptgClass, OperationEval evalInstance) {
+ if(!Ptg.class.isAssignableFrom(ptgClass)) {
+ throw new IllegalArgumentException("Expected Ptg subclass");
+ }
+ m.put(ptgClass, evalInstance);
+ }
+
+ 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");
+ }
+ Object result;
+
+ Class ptgClass = ptg.getClass();
+
+ result = _instancesByPtgClass.get(ptgClass);
+ if (result != null) {
+ return (OperationEval) result;
+ }
+
+
+ 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[] 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;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/ParseNode.java b/src/java/org/apache/poi/ss/formula/ParseNode.java
new file mode 100644
index 0000000000..75685dee64
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/ParseNode.java
@@ -0,0 +1,201 @@
+/* ====================================================================
+ 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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.AttrPtg;
+import org.apache.poi.hssf.record.formula.FuncVarPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
+/**
+ * Represents a syntactic element from a formula by encapsulating the corresponding Ptg
+ * token. Each ParseNode may have child ParseNodes in the case when the wrapped
+ * Ptg is non-atomic.
+ *
+ * @author Josh Micich
+ */
+final class ParseNode {
+
+ public static final ParseNode[] EMPTY_ARRAY = { };
+ private final Ptg _token;
+ private final ParseNode[] _children;
+ private boolean _isIf;
+ private final int _tokenCount;
+
+ public ParseNode(Ptg token, ParseNode[] children) {
+ _token = token;
+ _children = children;
+ _isIf = isIf(token);
+ int tokenCount = 1;
+ for (int i = 0; i < children.length; i++) {
+ tokenCount += children[i].getTokenCount();
+ }
+ if (_isIf) {
+ // there will be 2 or 3 extra tAttr tokens according to whether the false param is present
+ tokenCount += children.length;
+ }
+ _tokenCount = tokenCount;
+ }
+ public ParseNode(Ptg token) {
+ this(token, EMPTY_ARRAY);
+ }
+ public ParseNode(Ptg token, ParseNode child0) {
+ this(token, new ParseNode[] { child0, });
+ }
+ public ParseNode(Ptg token, ParseNode child0, ParseNode child1) {
+ this(token, new ParseNode[] { child0, child1, });
+ }
+ private int getTokenCount() {
+ return _tokenCount;
+ }
+
+ /**
+ * Collects the array of Ptg tokens for the specified tree.
+ */
+ public static Ptg[] toTokenArray(ParseNode rootNode) {
+ TokenCollector temp = new TokenCollector(rootNode.getTokenCount());
+ rootNode.collectPtgs(temp);
+ return temp.getResult();
+ }
+ private void collectPtgs(TokenCollector temp) {
+ if (isIf(getToken())) {
+ collectIfPtgs(temp);
+ return;
+ }
+ for (int i=0; i< getChildren().length; i++) {
+ getChildren()[i].collectPtgs(temp);
+ }
+ temp.add(getToken());
+ }
+ /**
+ * The IF() function gets marked up with two or three tAttr tokens.
+ * Similar logic will be required for CHOOSE() when it is supported
+ *
+ * See excelfileformat.pdf sec 3.10.5 "tAttr (19H)
+ */
+ private void collectIfPtgs(TokenCollector temp) {
+
+ // condition goes first
+ getChildren()[0].collectPtgs(temp);
+
+ // placeholder for tAttrIf
+ int ifAttrIndex = temp.createPlaceholder();
+
+ // true parameter
+ getChildren()[1].collectPtgs(temp);
+
+ // placeholder for first skip attr
+ int skipAfterTrueParamIndex = temp.createPlaceholder();
+ int trueParamSize = temp.sumTokenSizes(ifAttrIndex+1, skipAfterTrueParamIndex);
+
+ AttrPtg attrIf = new AttrPtg();
+ attrIf.setOptimizedIf(true);
+ AttrPtg attrSkipAfterTrue = new AttrPtg();
+ attrSkipAfterTrue.setGoto(true);
+
+ if (getChildren().length > 2) {
+ // false param present
+
+ // false parameter
+ getChildren()[2].collectPtgs(temp);
+
+ int skipAfterFalseParamIndex = temp.createPlaceholder();
+
+ AttrPtg attrSkipAfterFalse = new AttrPtg();
+ attrSkipAfterFalse.setGoto(true);
+
+ int falseParamSize = temp.sumTokenSizes(skipAfterTrueParamIndex+1, skipAfterFalseParamIndex);
+
+ attrIf.setData((short)(trueParamSize + 4)); // distance to start of false parameter. +4 for skip after true
+ attrSkipAfterTrue.setData((short)(falseParamSize + 4 + 4 - 1)); // 1 less than distance to end of if FuncVar(size=4). +4 for attr skip before
+ attrSkipAfterFalse.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4).
+
+ temp.setPlaceholder(ifAttrIndex, attrIf);
+ temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue);
+ temp.setPlaceholder(skipAfterFalseParamIndex, attrSkipAfterFalse);
+ } else {
+ // false parameter not present
+ attrIf.setData((short)(trueParamSize + 4)); // distance to start of FuncVar. +4 for skip after true
+ attrSkipAfterTrue.setData((short)(4 - 1)); // 1 less than distance to end of if FuncVar(size=4).
+
+ temp.setPlaceholder(ifAttrIndex, attrIf);
+ temp.setPlaceholder(skipAfterTrueParamIndex, attrSkipAfterTrue);
+ }
+
+ temp.add(getToken());
+ }
+
+ private static boolean isIf(Ptg token) {
+ if (token instanceof FuncVarPtg) {
+ FuncVarPtg func = (FuncVarPtg) token;
+ if (FunctionMetadataRegistry.FUNCTION_NAME_IF.equals(func.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Ptg getToken() {
+ return _token;
+ }
+
+ public ParseNode[] getChildren() {
+ return _children;
+ }
+
+ private static final class TokenCollector {
+
+ private final Ptg[] _ptgs;
+ private int _offset;
+
+ public TokenCollector(int tokenCount) {
+ _ptgs = new Ptg[tokenCount];
+ _offset = 0;
+ }
+
+ public int sumTokenSizes(int fromIx, int toIx) {
+ int result = 0;
+ for (int i=fromIx; inull
, never {@link BlankEval}
+ */
+ /* package */ ValueEval internalEvaluate(Cell srcCell, EvaluationTracker tracker) {
+ int srcRowNum = srcCell.getRowIndex();
+ int srcColNum = srcCell.getCellNum();
+
+ ValueEval result;
+
+ int sheetIndex = getSheetIndex(srcCell.getSheet());
+ result = tracker.startEvaluate(sheetIndex, srcRowNum, srcColNum);
+ if (result != null) {
+ return result;
+ }
+ _evaluationCounter.value++;
+ _evaluationCounter.depth++;
+
+ try {
+ Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
+ result = evaluateCell(sheetIndex, srcRowNum, (short)srcColNum, ptgs, tracker);
+ } finally {
+ tracker.endEvaluate(sheetIndex, srcRowNum, srcColNum, result);
+ _evaluationCounter.depth--;
+ }
+ if (isDebugLogEnabled()) {
+ String sheetName = getSheetName(sheetIndex);
+ CellReference cr = new CellReference(srcRowNum, srcColNum);
+ logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
+ }
+ return result;
+ }
+ private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
+
+ Stack stack = new Stack();
+ for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
+
+ // since we don't know how to handle these yet :(
+ Ptg ptg = ptgs[i];
+ if (ptg instanceof ControlPtg) {
+ // skip Parentheses, Attr, etc
+ 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;
+
+ if (optg instanceof UnionPtg) { continue; }
+
+ OperationEval operation = OperationEvaluatorFactory.create(optg);
+
+ int numops = operation.getNumberOfOperands();
+ Eval[] ops = new Eval[numops];
+
+ // storing the ops in reverse order since they are popping
+ for (int j = numops - 1; j >= 0; j--) {
+ Eval p = (Eval) stack.pop();
+ ops[j] = p;
+ }
+// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
+ opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum);
+ } else {
+ opResult = getEvalForPtg(ptg, sheetIndex, tracker);
+ }
+ if (opResult == null) {
+ throw new RuntimeException("Evaluation result must not be null");
+ }
+// logDebug("push " + opResult);
+ stack.push(opResult);
+ }
+
+ ValueEval value = ((ValueEval) stack.pop());
+ if (!stack.isEmpty()) {
+ throw new IllegalStateException("evaluation stack not empty");
+ }
+ value = dereferenceValue(value, srcRowNum, srcColNum);
+ if (value == BlankEval.INSTANCE) {
+ // Note Excel behaviour here. A blank final final value is converted to zero.
+ return NumberEval.ZERO;
+ // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
+ // blank, the actual value is empty string. This can be verified with ISBLANK().
+ }
+ return value;
+ }
+
+ /**
+ * Dereferences a single value from any AreaEval or RefEval evaluation result.
+ * If the supplied evaluationResult is just a plain value, it is returned as-is.
+ * @return a NumberEval, StringEval, BoolEval,
+ * BlankEval or ErrorEval. Never null
.
+ */
+ private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
+ if (evaluationResult instanceof RefEval) {
+ RefEval rv = (RefEval) evaluationResult;
+ return rv.getInnerValueEval();
+ }
+ if (evaluationResult instanceof AreaEval) {
+ AreaEval ae = (AreaEval) evaluationResult;
+ if (ae.isRow()) {
+ if(ae.isColumn()) {
+ return ae.getRelativeValue(0, 0);
+ }
+ return ae.getValueAt(ae.getFirstRow(), srcColNum);
+ }
+ if (ae.isColumn()) {
+ return ae.getValueAt(srcRowNum, ae.getFirstColumn());
+ }
+ return ErrorEval.VALUE_INVALID;
+ }
+ return evaluationResult;
+ }
+
+ private static Eval invokeOperation(OperationEval operation, Eval[] ops,
+ EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
+
+ if(operation instanceof FunctionEval) {
+ FunctionEval fe = (FunctionEval) operation;
+ if(fe.isFreeRefFunction()) {
+ return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum);
+ }
+ }
+ return operation.evaluate(ops, srcRowNum, (short)srcColNum);
+ }
+
+ private Sheet getOtherSheet(int externSheetIndex) {
+ return _workbook.getSheetByExternSheetIndex(externSheetIndex);
+ }
+
+ /**
+ * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
+ * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,
+ * StringPtg, BoolPtg true
return code, the caller should
- * continue evaluation of the specified cell, and also be sure to call
- * endEvaluate() when complete.false
return code, the caller should
- * return an evaluation result of
- * ErrorEval.CIRCULAR_REF_ERROR, and not call endEvaluate().
- * true
if the specified cell has not been visited yet in the current
- * evaluation. false
if the specified cell is already being evaluated.
- */
- public boolean startEvaluate(Workbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
- CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum);
- if (_evaluationFrames.contains(cef)) {
- return false;
- }
- _evaluationFrames.add(cef);
- return true;
- }
-
- /**
- * Notifies this evaluation tracker that the evaluation of the specified
- * cell is complete.
- *
- * Every successful call to startEvaluate must be followed by a
- * call to endEvaluate (recommended in a finally block) to enable
- * proper tracking of which cells are being evaluated at any point in time.
- *
- * Assuming a well behaved client, parameters to this method would not be
- * required. However, they have been included to assert correct behaviour,
- * and form more meaningful error messages.
- */
- public void endEvaluate(Workbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
- int nFrames = _evaluationFrames.size();
- if (nFrames < 1) {
- throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
- }
-
- nFrames--;
- CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames);
- CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum);
- if (!cefActual.equals(cefExpected)) {
- throw new RuntimeException("Wrong cell specified. "
- + "Corresponding startEvaluate() call was for cell {"
- + cefExpected.formatAsString() + "} this endEvaluate() call is for cell {"
- + cefActual.formatAsString() + "}");
- }
- // else - no problems so pop current frame
- _evaluationFrames.remove(nFrames);
- }
-}
diff --git a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java b/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java
deleted file mode 100755
index d83d187002..0000000000
--- a/src/java/org/apache/poi/ss/usermodel/EvaluationCycleDetectorManager.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/* ====================================================================
- 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.ss.usermodel;
-
-/**
- * This class makes an EvaluationCycleDetector instance available to
- * each thread via a ThreadLocal in order to avoid adding a parameter
- * to a few protected methods within HSSFFormulaEvaluator.
- *
- * @author Josh Micich
- */
-final class EvaluationCycleDetectorManager {
-
- ThreadLocal tl = null;
- private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() {
- protected synchronized Object initialValue() {
- return new EvaluationCycleDetector();
- }
- };
-
- /**
- * @return
- */
- public static EvaluationCycleDetector getTracker() {
- return (EvaluationCycleDetector) _tlEvaluationTracker.get();
- }
-
- private EvaluationCycleDetectorManager() {
- // no instances of this class
- }
-}
diff --git a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
index 0f1b7bc47c..1234c9564f 100644
--- a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
+++ b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
@@ -17,43 +17,6 @@
package org.apache.poi.ss.usermodel;
-import java.util.Iterator;
-import java.util.Stack;
-
-import org.apache.poi.hssf.model.FormulaParser;
-import org.apache.poi.hssf.record.NameRecord;
-import org.apache.poi.hssf.record.formula.Area3DPtg;
-import org.apache.poi.hssf.record.formula.AreaPtg;
-import org.apache.poi.hssf.record.formula.BoolPtg;
-import org.apache.poi.hssf.record.formula.ControlPtg;
-import org.apache.poi.hssf.record.formula.ErrPtg;
-import org.apache.poi.hssf.record.formula.IntPtg;
-import org.apache.poi.hssf.record.formula.MemErrPtg;
-import org.apache.poi.hssf.record.formula.MissingArgPtg;
-import org.apache.poi.hssf.record.formula.NamePtg;
-import org.apache.poi.hssf.record.formula.NameXPtg;
-import org.apache.poi.hssf.record.formula.NumberPtg;
-import org.apache.poi.hssf.record.formula.OperationPtg;
-import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.hssf.record.formula.Ref3DPtg;
-import org.apache.poi.hssf.record.formula.RefPtg;
-import org.apache.poi.hssf.record.formula.StringPtg;
-import org.apache.poi.hssf.record.formula.UnionPtg;
-import org.apache.poi.hssf.record.formula.UnknownPtg;
-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.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.NameEval;
-import org.apache.poi.hssf.record.formula.eval.NameXEval;
-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.RefEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.util.CellReference;
/**
* Evaluates formula cells.
@@ -65,85 +28,15 @@ import org.apache.poi.hssf.util.CellReference;
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
* @author Josh Micich
*/
-public class FormulaEvaluator {
-
- /**
- * used to track the number of evaluations
- */
- private static final class Counter {
- public int value;
- public int depth;
- public Counter() {
- value = 0;
- }
- }
-
- protected final Workbook _workbook;
- private final EvaluationCache _cache;
-
- private Counter _evaluationCounter;
-
- /**
- * @deprecated (Sep 2008) Sheet parameter is ignored
- */
- public FormulaEvaluator(Sheet sheet, Workbook workbook) {
- this(workbook);
- if (false) {
- sheet.toString(); // suppress unused parameter compiler warning
- }
- }
- public FormulaEvaluator(Workbook workbook) {
- this(workbook, new EvaluationCache(), new Counter());
- }
-
- private FormulaEvaluator(Workbook workbook, EvaluationCache cache, Counter evaluationCounter) {
- _workbook = workbook;
- _cache = cache;
- _evaluationCounter = evaluationCounter;
- }
-
- /**
- * for debug use. Used in toString methods
- */
- public String getSheetName(Sheet sheet) {
- return _workbook.getSheetName(_workbook.getSheetIndex(sheet));
- }
- /**
- * for debug/test use
- */
- public int getEvaluationCount() {
- return _evaluationCounter.value;
- }
-
- private static boolean isDebugLogEnabled() {
- return false;
- }
- private static void logDebug(String s) {
- if (isDebugLogEnabled()) {
- System.out.println(s);
- }
- }
-
- /**
- * Does nothing
- * @deprecated (Aug 2008) - not needed, since the current row can be derived from the cell
- */
- public void setCurrentRow(Row row) {
- // do nothing
- if (false) {
- row.getClass(); // suppress unused parameter compiler warning
- }
- }
+public interface FormulaEvaluator {
/**
* Should be called whenever there are changes to input cells in the evaluated workbook.
* Failure to call this method after changing cell values will cause incorrect behaviour
* of the evaluate~ methods of this class
*/
- public void clearCache() {
- _cache.clear();
- }
-
+ void clearAllCachedResultValues();
+ void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex);
/**
* If cell contains a formula, the formula is evaluated and returned,
* else the CellValue simply copies the appropriate cell value from
@@ -152,25 +45,7 @@ public class FormulaEvaluator {
* original cell.
* @param cell
*/
- public CellValue evaluate(Cell cell) {
- if (cell == null) {
- return null;
- }
-
- switch (cell.getCellType()) {
- case Cell.CELL_TYPE_BOOLEAN:
- return CellValue.valueOf(cell.getBooleanCellValue());
- case Cell.CELL_TYPE_ERROR:
- return CellValue.getError(cell.getErrorCellValue());
- case Cell.CELL_TYPE_FORMULA:
- return evaluateFormulaCellValue(cell);
- case Cell.CELL_TYPE_NUMERIC:
- return new CellValue(cell.getNumericCellValue(), _workbook.getCreationHelper());
- case Cell.CELL_TYPE_STRING:
- return new CellValue(cell.getRichStringCellValue().getString(), _workbook.getCreationHelper());
- }
- throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
- }
+ CellValue evaluate(Cell cell);
/**
@@ -191,15 +66,7 @@ public class FormulaEvaluator {
* @param cell The cell to evaluate
* @return The type of the formula result (the cell's type remains as Cell.CELL_TYPE_FORMULA however)
*/
- public int evaluateFormulaCell(Cell cell) {
- if (cell == null || cell.getCellType() != Cell.CELL_TYPE_FORMULA) {
- return -1;
- }
- CellValue cv = evaluateFormulaCellValue(cell);
- // cell remains a formula cell, but the cached value is changed
- setCellValue(cell, cv);
- return cv.getCellType();
- }
+ int evaluateFormulaCell(Cell cell);
/**
* If cell contains formula, it evaluates the formula, and
@@ -217,462 +84,5 @@ public class FormulaEvaluator {
* value computed for you, use {@link #evaluateFormulaCell(Cell)}
* @param cell
*/
- public Cell evaluateInCell(Cell cell) {
- if (cell == null) {
- return null;
- }
- if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
- CellValue cv = evaluateFormulaCellValue(cell);
- setCellType(cell, cv); // cell will no longer be a formula cell
- setCellValue(cell, cv);
- }
- return cell;
- }
- private static void setCellType(Cell cell, CellValue cv) {
- int cellType = cv.getCellType();
- switch (cellType) {
- case Cell.CELL_TYPE_BOOLEAN:
- case Cell.CELL_TYPE_ERROR:
- case Cell.CELL_TYPE_NUMERIC:
- case Cell.CELL_TYPE_STRING:
- cell.setCellType(cellType);
- return;
- case Cell.CELL_TYPE_BLANK:
- // never happens - blanks eventually get translated to zero
- case Cell.CELL_TYPE_FORMULA:
- // this will never happen, we have already evaluated the formula
- }
- throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");
- }
-
- private static void setCellValue(Cell cell, CellValue cv) {
- int cellType = cv.getCellType();
- switch (cellType) {
- case Cell.CELL_TYPE_BOOLEAN:
- cell.setCellValue(cv.getBooleanValue());
- break;
- case Cell.CELL_TYPE_ERROR:
- cell.setCellErrorValue(cv.getErrorValue());
- break;
- case Cell.CELL_TYPE_NUMERIC:
- cell.setCellValue(cv.getNumberValue());
- break;
- case Cell.CELL_TYPE_STRING:
- cell.setCellValue(cv.getRichTextStringValue());
- break;
- case Cell.CELL_TYPE_BLANK:
- // never happens - blanks eventually get translated to zero
- case Cell.CELL_TYPE_FORMULA:
- // this will never happen, we have already evaluated the formula
- default:
- throw new IllegalStateException("Unexpected cell value type (" + cellType + ")");
- }
- }
-
- /**
- * Loops over all cells in all sheets of the supplied
- * workbook.
- * For cells that contain formulas, their formulas are
- * evaluated, and the results are saved. These cells
- * remain as formula cells.
- * For cells that do not contain formulas, no changes
- * are made.
- * This is a helpful wrapper around looping over all
- * cells, and calling evaluateFormulaCell on each one.
- */
- public static void evaluateAllFormulaCells(Workbook wb) {
- FormulaEvaluator evaluator = new FormulaEvaluator(wb);
- for(int i=0; inull
.
- */
- private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
- if (evaluationResult instanceof RefEval) {
- RefEval rv = (RefEval) evaluationResult;
- return rv.getInnerValueEval();
- }
- if (evaluationResult instanceof AreaEval) {
- AreaEval ae = (AreaEval) evaluationResult;
- if (ae.isRow()) {
- if(ae.isColumn()) {
- return ae.getRelativeValue(0, 0);
- }
- return ae.getValueAt(ae.getFirstRow(), srcColNum);
- }
- if (ae.isColumn()) {
- return ae.getValueAt(srcRowNum, ae.getFirstColumn());
- }
- return ErrorEval.VALUE_INVALID;
- }
- return evaluationResult;
- }
-
- private static Eval invokeOperation(OperationEval operation, Eval[] ops,
- Workbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
-
- if(operation instanceof FunctionEval) {
- FunctionEval fe = (FunctionEval) operation;
- if(fe.isFreeRefFunction()) {
- return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum);
- }
- }
- return operation.evaluate(ops, srcRowNum, (short)srcColNum);
- }
-
- private Sheet getOtherSheet(int externSheetIndex) {
- return _workbook.getSheetAt(_workbook.getSheetIndexFromExternSheetIndex(externSheetIndex));
- }
-
- /**
- * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
- * one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,
- * StringPtg, BoolPtg diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java new file mode 100644 index 0000000000..2d442e8f4a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java @@ -0,0 +1,167 @@ +package org.apache.poi.xssf.usermodel; + +import org.apache.poi.hssf.record.formula.NamePtg; +import org.apache.poi.hssf.record.formula.NameXPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.ss.formula.EvaluationName; +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.formula.FormulaParser; +import org.apache.poi.ss.formula.FormulaParsingWorkbook; +import org.apache.poi.ss.formula.FormulaRenderingWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName; + +/** + * Internal POI use only + * + * @author Josh Micich + */ +public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { + + private final XSSFWorkbook _uBook; + + public static XSSFEvaluationWorkbook create(XSSFWorkbook book) { + if (book == null) { + return null; + } + return new XSSFEvaluationWorkbook(book); + } + + private XSSFEvaluationWorkbook(XSSFWorkbook book) { + _uBook = book; + } + + private int convertFromExternalSheetIndex(int externSheetIndex) { + return externSheetIndex; + } + /** + * @returns the external sheet index of the sheet with the given internal + * index, creating one if needed. Used by some of the more obscure + * formula and named range things. Fairly easy on XSSF (we + * think...) since the internal and external indicies are the same + */ + private int convertToExternalSheetIndex(int sheetIndex) { + return sheetIndex; + } + + public int getExternalSheetIndex(String sheetName) { + int sheetIndex = _uBook.getSheetIndex(sheetName); + return convertToExternalSheetIndex(sheetIndex); + } + + public EvaluationName getName(int index) { + return new Name(_uBook.getNameAt(index), index, this); + } + + public EvaluationName getName(String name) { + for(int i=0; i < _uBook.getNumberOfNames(); i++) { + String nameText = _uBook.getNameName(i); + if (name.equalsIgnoreCase(nameText)) { + return new Name(_uBook.getNameAt(i), i, this); + } + } + return null; + } + + public int getSheetIndex(Sheet sheet) { + return _uBook.getSheetIndex(sheet); + } + + public String getSheetName(int sheetIndex) { + return _uBook.getSheetName(sheetIndex); + } + + public int getNameIndex(String name) { + return _uBook.getNameIndex(name); + } + + public NameXPtg getNameXPtg(String name) { + // may require to return null to make tests pass + throw new RuntimeException("Not implemented yet"); + } + + public Sheet getSheet(int sheetIndex) { + return _uBook.getSheetAt(sheetIndex); + } + + /** + * Doesn't do anything - returns the same index + * TODO - figure out if this is a ole2 specific thing, or + * if we need to do something proper here too! + */ + public Sheet getSheetByExternSheetIndex(int externSheetIndex) { + int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); + return _uBook.getSheetAt(sheetIndex); + } + + public Workbook getWorkbook() { + return _uBook; + } + + /** + * TODO - figure out what the hell this methods does in + * HSSF... + */ + public String resolveNameXText(NameXPtg n) { + throw new RuntimeException("method not implemented yet"); + } + + public String getSheetNameByExternSheet(int externSheetIndex) { + int sheetIndex = convertFromExternalSheetIndex(externSheetIndex); + return _uBook.getSheetName(sheetIndex); + } + + public String getNameText(NamePtg namePtg) { + return _uBook.getNameAt(namePtg.getIndex()).getNameName(); + } + public EvaluationName getName(NamePtg namePtg) { + int ix = namePtg.getIndex(); + return new Name(_uBook.getNameAt(ix), ix, this); + } + public Ptg[] getFormulaTokens(Cell cell) { + XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook); + return FormulaParser.parse(cell.getCellFormula(), frBook); + } + + private static final class Name implements EvaluationName { + + private final XSSFName _nameRecord; + private final int _index; + private final FormulaParsingWorkbook _fpBook; + + public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) { + _nameRecord = name; + _index = index; + _fpBook = fpBook; + } + + public Ptg[] getNameDefinition() { + + return FormulaParser.parse(_nameRecord.getReference(), _fpBook); + } + + public String getNameText() { + return _nameRecord.getNameName(); + } + + public boolean hasFormula() { + // TODO - no idea if this is right + CTDefinedName ctn = _nameRecord.getCTName(); + String strVal = ctn.getStringValue(); + return !ctn.getFunction() && strVal != null && strVal.length() > 0; + } + + public boolean isFunctionName() { + return _nameRecord.isFunctionName(); + } + + public boolean isRange() { + return hasFormula(); // TODO - is this right? + } + public NamePtg createPtg() { + return new NamePtg(_index); + } + } +} diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java new file mode 100644 index 0000000000..3dcedf8f5e --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFFormulaEvaluator.java @@ -0,0 +1,256 @@ +/* ==================================================================== + 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.xssf.usermodel; + +import java.util.Iterator; + +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.NumberEval; +import org.apache.poi.hssf.record.formula.eval.StringEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.ss.formula.WorkbookEvaluator; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; + +/** + * Evaluates formula cells.
+ * + * For performance reasons, this class keeps a cache of all previously calculated intermediate + * cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between + * calls to evaluate~ methods on this class. + * + * @author Amol S. Deshmukh < amolweb at ya hoo dot com > + * @author Josh Micich + */ +public class XSSFFormulaEvaluator implements FormulaEvaluator { + + private WorkbookEvaluator _bookEvaluator; + + public XSSFFormulaEvaluator(XSSFWorkbook workbook) { + _bookEvaluator = new WorkbookEvaluator(XSSFEvaluationWorkbook.create(workbook)); + } + + /** + * TODO for debug/test use + */ + /* package */ int getEvaluationCount() { + return _bookEvaluator.getEvaluationCount(); + } + + + /** + * Should be called whenever there are major changes (e.g. moving sheets) to input cells + * in the evaluated workbook. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void clearAllCachedResultValues() { + _bookEvaluator.clearAllCachedResultValues(); + } + /** + * Should be called whenever there are changes to individual input cells in the evaluated workbook. + * Failure to call this method after changing cell values will cause incorrect behaviour + * of the evaluate~ methods of this class + */ + public void clearCachedResultValue(Sheet sheet, int rowIndex, int columnIndex) { + _bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex); + } + + /** + * If cell contains a formula, the formula is evaluated and returned, + * else the CellValue simply copies the appropriate cell value from + * the cell and also its cell type. This method should be preferred over + * evaluateInCell() when the call should not modify the contents of the + * original cell. + * @param cell + */ + public CellValue evaluate(Cell cell) { + if (cell == null) { + return null; + } + + switch (cell.getCellType()) { + case XSSFCell.CELL_TYPE_BOOLEAN: + return CellValue.valueOf(cell.getBooleanCellValue()); + case XSSFCell.CELL_TYPE_ERROR: + return CellValue.getError(cell.getErrorCellValue()); + case XSSFCell.CELL_TYPE_FORMULA: + return evaluateFormulaCellValue(cell); + case XSSFCell.CELL_TYPE_NUMERIC: + return new CellValue(cell.getNumericCellValue()); + case XSSFCell.CELL_TYPE_STRING: + return new CellValue(cell.getRichStringCellValue().getString()); + } + throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")"); + } + + + /** + * If cell contains formula, it evaluates the formula, + * and saves the result of the formula. The cell + * remains as a formula cell. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the type of the formula result is returned, + * so you know what kind of value is also stored with + * the formula. + *+ * int evaluatedCellType = evaluator.evaluateFormulaCell(cell); + *+ * Be aware that your cell will hold both the formula, + * and the result. If you want the cell replaced with + * the result of the formula, use {@link #evaluateInCell(HSSFCell)} + * @param cell The cell to evaluate + * @return The type of the formula result (the cell's type remains as HSSFCell.CELL_TYPE_FORMULA however) + */ + public int evaluateFormulaCell(Cell cell) { + if (cell == null || cell.getCellType() != XSSFCell.CELL_TYPE_FORMULA) { + return -1; + } + CellValue cv = evaluateFormulaCellValue(cell); + // cell remains a formula cell, but the cached value is changed + setCellValue(cell, cv); + return cv.getCellType(); + } + + /** + * If cell contains formula, it evaluates the formula, and + * puts the formula result back into the cell, in place + * of the old formula. + * Else if cell does not contain formula, this method leaves + * the cell unchanged. + * Note that the same instance of HSSFCell is returned to + * allow chained calls like: + *
+ * int evaluatedCellType = evaluator.evaluateInCell(cell).getCellType(); + *+ * Be aware that your cell value will be changed to hold the + * result of the formula. If you simply want the formula + * value computed for you, use {@link #evaluateFormulaCell(HSSFCell)} + * @param cell + */ + public XSSFCell evaluateInCell(Cell cell) { + if (cell == null) { + return null; + } + XSSFCell result = (XSSFCell) cell; + if (cell.getCellType() == XSSFCell.CELL_TYPE_FORMULA) { + CellValue cv = evaluateFormulaCellValue(cell); + setCellType(cell, cv); // cell will no longer be a formula cell + setCellValue(cell, cv); + } + return result; + } + private static void setCellType(Cell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case XSSFCell.CELL_TYPE_BOOLEAN: + case XSSFCell.CELL_TYPE_ERROR: + case XSSFCell.CELL_TYPE_NUMERIC: + case XSSFCell.CELL_TYPE_STRING: + cell.setCellType(cellType); + return; + case XSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case XSSFCell.CELL_TYPE_FORMULA: + // this will never happen, we have already evaluated the formula + } + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + + private static void setCellValue(Cell cell, CellValue cv) { + int cellType = cv.getCellType(); + switch (cellType) { + case XSSFCell.CELL_TYPE_BOOLEAN: + cell.setCellValue(cv.getBooleanValue()); + break; + case XSSFCell.CELL_TYPE_ERROR: + cell.setCellErrorValue(cv.getErrorValue()); + break; + case XSSFCell.CELL_TYPE_NUMERIC: + cell.setCellValue(cv.getNumberValue()); + break; + case XSSFCell.CELL_TYPE_STRING: + cell.setCellValue(new XSSFRichTextString(cv.getStringValue())); + break; + case XSSFCell.CELL_TYPE_BLANK: + // never happens - blanks eventually get translated to zero + case XSSFCell.CELL_TYPE_FORMULA: + // this will never happen, we have already evaluated the formula + default: + throw new IllegalStateException("Unexpected cell value type (" + cellType + ")"); + } + } + + /** + * Loops over all cells in all sheets of the supplied + * workbook. + * For cells that contain formulas, their formulas are + * evaluated, and the results are saved. These cells + * remain as formula cells. + * For cells that do not contain formulas, no changes + * are made. + * This is a helpful wrapper around looping over all + * cells, and calling evaluateFormulaCell on each one. + */ + public static void evaluateAllFormulaCells(XSSFWorkbook wb) { + XSSFFormulaEvaluator evaluator = new XSSFFormulaEvaluator(wb); + for(int i=0; i
null
to test all functions
*/
private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
- FormulaEvaluator evaluator = new FormulaEvaluator(workbook);
+ HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(workbook);
int rowIndex = startRowIndex;
while (true) {
@@ -219,7 +219,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
* @return a constant from the local Result class denoting whether there were any evaluation
* cases, and whether they all succeeded.
*/
- private int processFunctionRow(FormulaEvaluator evaluator, String targetFunctionName,
+ private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName,
Row formulasRow, Row expectedValuesRow) {
int result = Result.NO_EVALUATIONS_FOUND; // so far
@@ -232,7 +232,7 @@ public final class TestFormulasFromSpreadsheet extends TestCase {
continue;
}
- FormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
+ CellValue actualValue = evaluator.evaluate(c);
Cell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum);
try {
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java
index bac2e68862..750f71ba01 100755
--- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestPercentEval.java
@@ -27,7 +27,7 @@ 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.ss.usermodel.FormulaEvaluator.CellValue;
+import org.apache.poi.ss.usermodel.CellValue;
/**
* Test for percent operator evaluator.
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java
index 7030c5a508..95ad2ef535 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/function/TestParseMissingBuiltInFuncs.java
@@ -20,7 +20,7 @@ package org.apache.poi.hssf.record.formula.function;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
-import org.apache.poi.hssf.model.FormulaParser;
+import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
@@ -36,7 +36,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase {
private static Ptg[] parse(String formula) {
HSSFWorkbook book = new HSSFWorkbook();
- return FormulaParser.parse(formula, book);
+ return HSSFFormulaParser.parse(formula, book);
}
private static void confirmFunc(String formula, int expPtgArraySize, boolean isVarArgFunc, int funcIx) {
Ptg[] ptgs = parse(formula);
@@ -58,7 +58,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase {
// check that parsed Ptg array converts back to formula text OK
HSSFWorkbook book = new HSSFWorkbook();
- String reRenderedFormula = FormulaParser.toFormulaString(book, ptgs);
+ String reRenderedFormula = HSSFFormulaParser.toFormulaString(book, ptgs);
assertEquals(formula, reRenderedFormula);
}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
index 8e5a8f32a4..98be3db43e 100755
--- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
@@ -34,7 +34,7 @@ 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.ss.usermodel.FormulaEvaluator.CellValue;
+import org.apache.poi.ss.usermodel.CellValue;
/**
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java
index 0c08ef5a54..b93eb7a6c7 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestDate.java
@@ -76,7 +76,7 @@ public final class TestDate extends TestCase {
private void confirm(String formulaText, double expectedResult) {
cell11.setCellFormula(formulaText);
- evaluator.clearCache();
+ evaluator.clearAllCachedResultValues();
double actualValue = evaluator.evaluate(cell11).getNumberValue();
assertEquals(expectedResult, actualValue, 0);
}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java
index f28a6462f8..2b0f776f73 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndexFunctionFromSpreadsheet.java
@@ -31,7 +31,7 @@ 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.util.CellReference;
-import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue;
+import org.apache.poi.ss.usermodel.CellValue;
/**
* Tests INDEX() as loaded from a test data spreadsheet.
@@ -66,7 +66,7 @@ public final class TestIndexFunctionFromSpreadsheet extends TestCase {
- private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
+ private static void confirmExpectedResult(String msg, HSSFCell expected, CellValue actual) {
if (expected == null) {
throw new AssertionFailedError(msg + " - Bad setup data expected value is null");
}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java
index 50f92dfecf..5168cd9224 100755
--- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java
@@ -24,7 +24,7 @@ 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.ss.usermodel.FormulaEvaluator.CellValue;
+import org.apache.poi.ss.usermodel.CellValue;
/**
* Tests for Excel function ISBLANK()
*
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java
index 49d67a8d4e..5bacdbfca9 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestLookupFunctionsFromSpreadsheet.java
@@ -30,8 +30,8 @@ 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.ss.usermodel.FormulaEvaluator.CellValue;
import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.ss.usermodel.CellValue;
/**
* Tests lookup functions (VLOOKUP, HLOOKUP, LOOKUP, MATCH) as loaded from a test data spreadsheet.
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
index a43764e852..73ee3f8486 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java
@@ -25,8 +25,8 @@ import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue;
import org.apache.poi.hssf.util.CellReference;
+import org.apache.poi.ss.usermodel.CellValue;
/**
*
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java
index d1c511feb2..484d9af94e 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java
@@ -17,10 +17,10 @@
package org.apache.poi.hssf.usermodel;
-import org.apache.poi.hssf.HSSFTestDataSamples;
-import org.apache.poi.ss.usermodel.FormulaEvaluator.CellValue;
-
import junit.framework.TestCase;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.ss.usermodel.CellValue;
/**
*
* @author Josh Micich
diff --git a/src/testcases/org/apache/poi/ss/formula/FormulaParserTestHelper.java b/src/testcases/org/apache/poi/ss/formula/FormulaParserTestHelper.java
new file mode 100644
index 0000000000..144002279d
--- /dev/null
+++ b/src/testcases/org/apache/poi/ss/formula/FormulaParserTestHelper.java
@@ -0,0 +1,48 @@
+/* ====================================================================
+ 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.ss.formula;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+import org.apache.poi.ss.formula.FormulaParser;
+import org.apache.poi.ss.formula.FormulaParser.FormulaParseException;
+/**
+ * Avoids making {@link FormulaParser#FormulaParseException} public
+ *
+ * @author Josh Micich
+ */
+public class FormulaParserTestHelper {
+ public static void confirmParseException(RuntimeException e, String expectedMessage) {
+ checkType(e);
+ Assert.assertEquals(expectedMessage, e.getMessage());
+ }
+ public static void confirmParseException(RuntimeException e) {
+ checkType(e);
+ Assert.assertNotNull(e.getMessage());
+ }
+ private static void checkType(RuntimeException e) throws AssertionFailedError {
+ if (!(e instanceof FormulaParseException)) {
+ String failMsg = "Expected FormulaParseException, but got ("
+ + e.getClass().getName() + "):";
+ System.err.println(failMsg);
+ e.printStackTrace();
+ throw new AssertionFailedError(failMsg);
+ }
+ }
+}