2008-05-28 02:19:31 -04:00
|
|
|
/* ====================================================================
|
|
|
|
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.
|
|
|
|
==================================================================== */
|
|
|
|
|
2008-09-18 22:19:58 -04:00
|
|
|
package org.apache.poi.ss.formula;
|
2008-05-28 02:19:31 -04:00
|
|
|
|
2010-11-24 11:50:47 -05:00
|
|
|
import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.AttrPtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.ControlPtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.FuncVarPtg;
|
2015-08-19 06:10:08 -04:00
|
|
|
import org.apache.poi.ss.formula.ptg.IntersectionPtg;
|
2010-11-24 11:50:47 -05:00
|
|
|
import org.apache.poi.ss.formula.ptg.MemAreaPtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.MemFuncPtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.Ptg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.RangePtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.UnionPtg;
|
|
|
|
import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
|
2008-05-28 02:19:31 -04:00
|
|
|
|
|
|
|
/**
|
2009-11-13 21:41:24 -05:00
|
|
|
* This class performs 'operand class' transformation. Non-base tokens are classified into three
|
2008-05-28 02:19:31 -04:00
|
|
|
* operand classes:
|
|
|
|
* <ul>
|
2009-11-13 21:41:24 -05:00
|
|
|
* <li>reference</li>
|
|
|
|
* <li>value</li>
|
|
|
|
* <li>array</li>
|
2008-05-28 02:19:31 -04:00
|
|
|
* </ul>
|
|
|
|
* <p/>
|
2009-11-13 21:41:24 -05:00
|
|
|
*
|
2008-05-28 02:19:31 -04:00
|
|
|
* The final operand class chosen for each token depends on the formula type and the token's place
|
|
|
|
* in the formula. If POI gets the operand class wrong, Excel <em>may</em> interpret the formula
|
|
|
|
* incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!',
|
|
|
|
* but resolves correctly when the user presses F2, enter.<p/>
|
2009-11-13 21:41:24 -05:00
|
|
|
*
|
2008-05-28 02:19:31 -04:00
|
|
|
* The logic implemented here was partially inspired by the description in
|
|
|
|
* "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there
|
|
|
|
* seems to be inconsistent with observed Excel behaviour (These differences have not been fully
|
|
|
|
* investigated). The implementation in this class has been heavily modified in order to satisfy
|
|
|
|
* concrete examples of how Excel performs the same logic (see TestRVA).<p/>
|
2009-11-13 21:41:24 -05:00
|
|
|
*
|
|
|
|
* Hopefully, as additional important test cases are identified and added to the test suite,
|
2008-05-28 02:19:31 -04:00
|
|
|
* patterns might become more obvious in this code and allow for simplification.
|
2009-11-13 21:41:24 -05:00
|
|
|
*
|
2008-05-28 02:19:31 -04:00
|
|
|
* @author Josh Micich
|
|
|
|
*/
|
|
|
|
final class OperandClassTransformer {
|
|
|
|
|
2016-07-04 01:37:06 -04:00
|
|
|
private final FormulaType _formulaType;
|
2008-05-28 02:19:31 -04:00
|
|
|
|
2016-07-04 01:37:06 -04:00
|
|
|
/** @deprecated POI 3.15 beta 3. */
|
2008-05-28 02:19:31 -04:00
|
|
|
public OperandClassTransformer(int formulaType) {
|
2016-07-04 01:37:06 -04:00
|
|
|
this(FormulaType.forInt(formulaType));
|
|
|
|
}
|
|
|
|
public OperandClassTransformer(FormulaType formulaType) {
|
2008-05-28 02:19:31 -04:00
|
|
|
_formulaType = formulaType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Traverses the supplied formula parse tree, calling <tt>Ptg.setClass()</tt> for each non-base
|
|
|
|
* token to set its operand class.
|
|
|
|
*/
|
|
|
|
public void transformFormula(ParseNode rootNode) {
|
|
|
|
byte rootNodeOperandClass;
|
|
|
|
switch (_formulaType) {
|
2016-07-04 01:37:06 -04:00
|
|
|
case CELL:
|
2008-05-28 02:19:31 -04:00
|
|
|
rootNodeOperandClass = Ptg.CLASS_VALUE;
|
|
|
|
break;
|
2016-07-04 01:37:06 -04:00
|
|
|
case ARRAY:
|
2009-12-25 18:04:04 -05:00
|
|
|
rootNodeOperandClass = Ptg.CLASS_ARRAY;
|
|
|
|
break;
|
2016-07-04 01:37:06 -04:00
|
|
|
case NAMEDRANGE:
|
|
|
|
case DATAVALIDATION_LIST:
|
2008-08-03 18:11:26 -04:00
|
|
|
rootNodeOperandClass = Ptg.CLASS_REF;
|
|
|
|
break;
|
2008-05-28 02:19:31 -04:00
|
|
|
default:
|
2009-11-13 21:41:24 -05:00
|
|
|
throw new RuntimeException("Incomplete code - formula type ("
|
2008-05-28 02:19:31 -04:00
|
|
|
+ _formulaType + ") not supported yet");
|
2009-11-13 21:41:24 -05:00
|
|
|
|
2008-05-28 02:19:31 -04:00
|
|
|
}
|
2008-09-09 16:25:16 -04:00
|
|
|
transformNode(rootNode, rootNodeOperandClass, false);
|
2008-05-28 02:19:31 -04:00
|
|
|
}
|
|
|
|
|
2008-07-08 21:45:33 -04:00
|
|
|
/**
|
2009-11-13 21:41:24 -05:00
|
|
|
* @param callerForceArrayFlag <code>true</code> if one of the current node's parents is a
|
2008-07-08 21:45:33 -04:00
|
|
|
* function Ptg which has been changed from default 'V' to 'A' type (due to requirements on
|
|
|
|
* the function return value).
|
|
|
|
*/
|
2008-05-28 02:19:31 -04:00
|
|
|
private void transformNode(ParseNode node, byte desiredOperandClass,
|
2008-09-09 16:25:16 -04:00
|
|
|
boolean callerForceArrayFlag) {
|
2008-05-28 02:19:31 -04:00
|
|
|
Ptg token = node.getToken();
|
|
|
|
ParseNode[] children = node.getChildren();
|
2008-09-09 16:25:16 -04:00
|
|
|
boolean isSimpleValueFunc = isSimpleValueFunction(token);
|
2009-11-13 21:41:24 -05:00
|
|
|
|
2008-09-09 16:25:16 -04:00
|
|
|
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;
|
|
|
|
}
|
2009-11-13 21:41:24 -05:00
|
|
|
|
2009-02-02 18:53:22 -05:00
|
|
|
if (isSingleArgSum(token)) {
|
|
|
|
// Need to process the argument of SUM with transformFunctionNode below
|
|
|
|
// so make a dummy FuncVarPtg for that call.
|
2009-11-13 21:41:24 -05:00
|
|
|
token = FuncVarPtg.SUM;
|
|
|
|
// Note - the tAttrSum token (node.getToken()) is a base
|
2009-02-02 18:53:22 -05:00
|
|
|
// token so does not need to have its operand class set
|
|
|
|
}
|
2008-11-18 21:01:58 -05:00
|
|
|
if (token instanceof ValueOperatorPtg || token instanceof ControlPtg
|
|
|
|
|| token instanceof MemFuncPtg
|
2009-04-06 04:22:25 -04:00
|
|
|
|| token instanceof MemAreaPtg
|
2015-08-19 06:10:08 -04:00
|
|
|
|| token instanceof UnionPtg
|
|
|
|
|| token instanceof IntersectionPtg) {
|
2008-05-28 02:19:31 -04:00
|
|
|
// Value Operator Ptgs and Control are base tokens, so token will be unchanged
|
|
|
|
// but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag
|
2009-11-13 21:41:24 -05:00
|
|
|
|
2008-09-09 16:25:16 -04:00
|
|
|
// As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
|
2009-11-13 21:41:24 -05:00
|
|
|
// All direct operands of value operators that are initially 'R' type will
|
2008-09-09 16:25:16 -04:00
|
|
|
// be converted to 'V' type.
|
|
|
|
byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
|
2008-05-28 02:19:31 -04:00
|
|
|
for (int i = 0; i < children.length; i++) {
|
2008-09-09 16:25:16 -04:00
|
|
|
transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
|
2008-05-28 02:19:31 -04:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (token instanceof AbstractFunctionPtg) {
|
2008-09-09 16:25:16 -04:00
|
|
|
transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
|
2008-05-28 02:19:31 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (children.length > 0) {
|
2008-09-26 16:25:45 -04:00
|
|
|
if (token == RangePtg.instance) {
|
|
|
|
// TODO is any token transformation required under the various ref operators?
|
|
|
|
return;
|
|
|
|
}
|
2008-05-28 02:19:31 -04:00
|
|
|
throw new IllegalStateException("Node should not have any children");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (token.isBaseToken()) {
|
|
|
|
// nothing to do
|
|
|
|
return;
|
|
|
|
}
|
2008-09-09 16:25:16 -04:00
|
|
|
token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
|
|
|
|
}
|
|
|
|
|
2009-02-02 18:53:22 -05:00
|
|
|
private static boolean isSingleArgSum(Ptg token) {
|
|
|
|
if (token instanceof AttrPtg) {
|
|
|
|
AttrPtg attrPtg = (AttrPtg) token;
|
2009-11-13 21:41:24 -05:00
|
|
|
return attrPtg.isSum();
|
2009-02-02 18:53:22 -05:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2008-09-09 16:25:16 -04:00
|
|
|
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;
|
|
|
|
}
|
2008-07-08 21:45:33 -04:00
|
|
|
}
|
2008-09-09 16:25:16 -04:00
|
|
|
return true;
|
2008-07-08 21:45:33 -04:00
|
|
|
}
|
2008-09-09 16:25:16 -04:00
|
|
|
return false;
|
2008-07-08 21:45:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2009-11-13 21:41:24 -05:00
|
|
|
return Ptg.CLASS_ARRAY;
|
2008-07-08 21:45:33 -04:00
|
|
|
case Ptg.CLASS_REF:
|
|
|
|
if (!callerForceArrayFlag) {
|
|
|
|
return currentOperandClass;
|
|
|
|
}
|
2009-11-13 21:41:24 -05:00
|
|
|
return Ptg.CLASS_REF;
|
2008-07-08 21:45:33 -04:00
|
|
|
}
|
|
|
|
throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")");
|
2008-05-28 02:19:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2009-11-13 21:41:24 -05:00
|
|
|
// an alternative would have been to for non-base Ptgs to set their operand class
|
2008-05-28 02:19:31 -04:00
|
|
|
// 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
|
2009-11-13 21:41:24 -05:00
|
|
|
afp.setClass(defaultReturnOperandClass);
|
2008-05-28 02:19:31 -04:00
|
|
|
} else {
|
|
|
|
switch (desiredOperandClass) {
|
|
|
|
case Ptg.CLASS_VALUE:
|
|
|
|
// always OK to set functions to return 'value'
|
2009-11-13 21:41:24 -05:00
|
|
|
afp.setClass(Ptg.CLASS_VALUE);
|
2008-05-28 02:19:31 -04:00
|
|
|
localForceArrayFlag = false;
|
|
|
|
break;
|
|
|
|
case Ptg.CLASS_ARRAY:
|
|
|
|
switch (defaultReturnOperandClass) {
|
|
|
|
case Ptg.CLASS_REF:
|
|
|
|
afp.setClass(Ptg.CLASS_REF);
|
2008-09-09 16:25:16 -04:00
|
|
|
// afp.setClass(Ptg.CLASS_ARRAY);
|
2008-05-28 02:19:31 -04:00
|
|
|
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);
|
2008-09-09 16:25:16 -04:00
|
|
|
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 {
|
2009-11-13 21:41:24 -05:00
|
|
|
afp.setClass(Ptg.CLASS_VALUE);
|
2008-05-28 02:19:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|