+ 45087 - Correctly detect date formats like [Black]YYYY as being date based
+ 45060 - Improved token class transformation during formula parsing
+ 44840 - Improved handling of HSSFObjectData, especially for entries with data held not in POIFS
45043 - Support for getting excel cell comments when extracting text
Extend the support for specifying a policy to HSSF on missing / blank cells when fetching, to be able to specify the policy at the HSSFWorkbook level
45025 - improved FormulaParser parse error messages
diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java
index 085a140ad9..d45e8741ba 100644
--- a/src/java/org/apache/poi/hssf/model/FormulaParser.java
+++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java
@@ -18,7 +18,6 @@
package org.apache.poi.hssf.model;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
@@ -61,17 +60,17 @@ public final class FormulaParser {
}
}
- public static int FORMULA_TYPE_CELL = 0;
- public static int FORMULA_TYPE_SHARED = 1;
- public static int FORMULA_TYPE_ARRAY =2;
- public static int FORMULA_TYPE_CONDFOMRAT = 3;
- public static int FORMULA_TYPE_NAMEDRANGE = 4;
+ public static final int FORMULA_TYPE_CELL = 0;
+ public static final int FORMULA_TYPE_SHARED = 1;
+ public static final int FORMULA_TYPE_ARRAY =2;
+ public static final int FORMULA_TYPE_CONDFOMRAT = 3;
+ public static final int FORMULA_TYPE_NAMEDRANGE = 4;
private final String formulaString;
private final int formulaLength;
private int pointer;
- private final List tokens = new Stack();
+ private ParseNode _rootNode;
/**
* Used for spotting if we have a cell reference,
@@ -221,14 +220,15 @@ public final class FormulaParser {
return value.length() == 0 ? null : value.toString();
}
- /** Parse and Translate a String Identifier */
- private Ptg parseIdent() {
- String name;
- name = GetName();
+ private ParseNode parseFunctionOrIdentifier() {
+ String name = GetName();
if (look == '('){
//This is a function
return function(name);
}
+ return new ParseNode(parseIdentifier(name));
+ }
+ private Ptg parseIdentifier(String name) {
if (look == ':' || look == '.') { // this is a AreaReference
GetChar();
@@ -287,14 +287,6 @@ public final class FormulaParser {
+ name + "\", but that named range wasn't defined!");
}
- /**
- * Adds a pointer to the last token to the latest function argument list.
- * @param obj
- */
- private void addArgumentPointer(List argumentPointers) {
- argumentPointers.add(tokens.get(tokens.size()-1));
- }
-
/**
* 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
@@ -302,58 +294,23 @@ public final class FormulaParser {
*
* @param name case preserved function name (as it was entered/appeared in the formula).
*/
- private Ptg function(String name) {
- int numArgs =0 ;
+ private ParseNode function(String name) {
+ NamePtg nameToken = null;
// Note regarding parameter -
if(!AbstractFunctionPtg.isInternalFunctionName(name)) {
// external functions get a Name token which points to a defined name record
- NamePtg nameToken = new NamePtg(name, this.book);
+ nameToken = new NamePtg(name, this.book);
// in the token tree, the name is more or less the first argument
- numArgs++;
- tokens.add(nameToken);
}
- //average 2 args per function
- List argumentPointers = new ArrayList(2);
Match('(');
- numArgs += Arguments(argumentPointers);
+ ParseNode[] args = Arguments();
Match(')');
- return getFunction(name, numArgs, argumentPointers);
+ return getFunction(name, nameToken, args);
}
- /**
- * Adds the size of all the ptgs after the provided index (inclusive).
- *
- * Initially used to count a goto
- * @param index
- * @return int
- */
- private int getPtgSize(int index) {
- int count = 0;
-
- Iterator ptgIterator = tokens.listIterator(index);
- while (ptgIterator.hasNext()) {
- Ptg ptg = (Ptg)ptgIterator.next();
- count+=ptg.getSize();
- }
-
- return count;
- }
-
- private int getPtgSize(int start, int end) {
- int count = 0;
- int index = start;
- Iterator ptgIterator = tokens.listIterator(index);
- while (ptgIterator.hasNext() && index <= end) {
- Ptg ptg = (Ptg)ptgIterator.next();
- count+=ptg.getSize();
- index++;
- }
-
- return count;
- }
/**
* Generates the variable function ptg for the formula.
*
@@ -362,84 +319,35 @@ public final class FormulaParser {
* @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 AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) {
+ private ParseNode getFunction(String name, NamePtg namePtg, ParseNode[] args) {
- boolean isVarArgs;
- int funcIx;
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
- isVarArgs = true;
- funcIx = FunctionMetadataRegistry.FUNCTION_INDEX_EXTERNAL;
- } else {
- isVarArgs = !fm.hasFixedArgsLength();
- funcIx = fm.getIndex();
- validateNumArgs(numArgs, fm);
+ 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);
}
- if (!name.equals(AbstractFunctionPtg.FUNCTION_NAME_IF)) {
- // early return for everything else besides IF()
- return retval;
- }
-
-
- AttrPtg ifPtg = new AttrPtg();
- ifPtg.setData((short)7); //mirroring excel output
- ifPtg.setOptimizedIf(true);
-
- if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
- throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
- }
-
- //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
- //tracked in the argument pointers
- //The beginning first argument pointer is the last ptg of the condition
- int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
- tokens.add(ifIndex, ifPtg);
-
- //we now need a goto ptgAttr to skip to the end of the formula after a true condition
- //the true condition is should be inserted after the last ptg in the first argument
-
- int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
-
- AttrPtg goto1Ptg = new AttrPtg();
- goto1Ptg.setGoto(true);
-
-
- tokens.add(gotoIndex, goto1Ptg);
-
-
- if (numArgs > 2) { //only add false jump if there is a false condition
-
- //second goto to skip past the function ptg
- AttrPtg goto2Ptg = new AttrPtg();
- goto2Ptg.setGoto(true);
- goto2Ptg.setData((short)(retval.getSize()-1));
- //Page 472 of the Microsoft Excel Developer's kit states that:
- //The b(or w) field specifies the number byes (or words to skip, minus 1
-
- tokens.add(goto2Ptg); //this goes after all the arguments are defined
- }
-
- //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
- //count the number of bytes after the ifPtg to the False Subexpression
- //doesn't specify -1 in the documentation
- ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
-
- //count all the additional (goto) ptgs but dont count itself
- int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
- if (ptgCount > Short.MAX_VALUE) {
- throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
- }
-
- goto1Ptg.setData((short)(ptgCount-1));
-
- return retval;
+ return new ParseNode(retval, args);
}
private void validateNumArgs(int numArgs, FunctionMetadata fm) {
@@ -470,10 +378,12 @@ public final class FormulaParser {
}
/** get arguments to a function */
- private int Arguments(List argumentPointers) {
+ private ParseNode[] Arguments() {
+ //average 2 args per function
+ List temp = new ArrayList(2);
SkipWhite();
if(look == ')') {
- return 0;
+ return ParseNode.EMPTY_ARRAY;
}
boolean missedPrevArg = true;
@@ -482,8 +392,7 @@ public final class FormulaParser {
SkipWhite();
if (isArgumentDelimiter(look)) {
if (missedPrevArg) {
- tokens.add(new MissingArgPtg());
- addArgumentPointer(argumentPointers);
+ temp.add(new ParseNode(new MissingArgPtg()));
numArgs++;
}
if (look == ')') {
@@ -493,8 +402,7 @@ public final class FormulaParser {
missedPrevArg = true;
continue;
}
- comparisonExpression();
- addArgumentPointer(argumentPointers);
+ temp.add(comparisonExpression());
numArgs++;
missedPrevArg = false;
SkipWhite();
@@ -502,32 +410,34 @@ public final class FormulaParser {
throw expected("',' or ')'");
}
}
- return numArgs;
+ ParseNode[] result = new ParseNode[temp.size()];
+ temp.toArray(result);
+ return result;
}
/** Parse and Translate a Math Factor */
- private void powerFactor() {
- percentFactor();
+ private ParseNode powerFactor() {
+ ParseNode result = percentFactor();
while(true) {
SkipWhite();
if(look != '^') {
- return;
+ return result;
}
Match('^');
- percentFactor();
- tokens.add(new PowerPtg());
+ ParseNode other = percentFactor();
+ result = new ParseNode(new PowerPtg(), result, other);
}
}
- private void percentFactor() {
- tokens.add(parseSimpleFactor());
+ private ParseNode percentFactor() {
+ ParseNode result = parseSimpleFactor();
while(true) {
SkipWhite();
if(look != '%') {
- return;
+ return result;
}
Match('%');
- tokens.add(new PercentPtg());
+ result = new ParseNode(new PercentPtg(), result);
}
}
@@ -535,32 +445,30 @@ public final class FormulaParser {
/**
* factors (without ^ or % )
*/
- private Ptg parseSimpleFactor() {
+ private ParseNode parseSimpleFactor() {
SkipWhite();
switch(look) {
case '#':
- return parseErrorLiteral();
+ return new ParseNode(parseErrorLiteral());
case '-':
Match('-');
- powerFactor();
- return new UnaryMinusPtg();
+ return new ParseNode(new UnaryMinusPtg(), powerFactor());
case '+':
Match('+');
- powerFactor();
- return new UnaryPlusPtg();
+ return new ParseNode(new UnaryPlusPtg(), powerFactor());
case '(':
Match('(');
- comparisonExpression();
+ ParseNode inside = comparisonExpression();
Match(')');
- return new ParenthesisPtg();
+ return new ParseNode(new ParenthesisPtg(), inside);
case '"':
- return parseStringLiteral();
+ return new ParseNode(parseStringLiteral());
}
if (IsAlpha(look) || look == '\''){
- return parseIdent();
+ return parseFunctionOrIdentifier();
}
// else - assume number
- return parseNumber();
+ return new ParseNode(parseNumber());
}
@@ -716,28 +624,30 @@ public final class FormulaParser {
}
/** Parse and Translate a Math Term */
- private void Term() {
- powerFactor();
+ private ParseNode Term() {
+ ParseNode result = powerFactor();
while(true) {
SkipWhite();
+ Ptg operator;
switch(look) {
case '*':
Match('*');
- powerFactor();
- tokens.add(new MultiplyPtg());
- continue;
+ operator = new MultiplyPtg();
+ break;
case '/':
Match('/');
- powerFactor();
- tokens.add(new DividePtg());
- continue;
+ operator = new DividePtg();
+ break;
+ default:
+ return result; // finished with Term
}
- return; // finished with Term
+ ParseNode other = powerFactor();
+ result = new ParseNode(operator, result, other);
}
}
- private void comparisonExpression() {
- concatExpression();
+ private ParseNode comparisonExpression() {
+ ParseNode result = concatExpression();
while (true) {
SkipWhite();
switch(look) {
@@ -745,11 +655,11 @@ public final class FormulaParser {
case '>':
case '<':
Ptg comparisonToken = getComparisonToken();
- concatExpression();
- tokens.add(comparisonToken);
+ ParseNode other = concatExpression();
+ result = new ParseNode(comparisonToken, result, other);
continue;
}
- return; // finished with predicate expression
+ return result; // finished with predicate expression
}
}
@@ -779,38 +689,41 @@ public final class FormulaParser {
}
- private void concatExpression() {
- additiveExpression();
+ private ParseNode concatExpression() {
+ ParseNode result = additiveExpression();
while (true) {
SkipWhite();
if(look != '&') {
break; // finished with concat expression
}
Match('&');
- additiveExpression();
- tokens.add(new ConcatPtg());
+ ParseNode other = additiveExpression();
+ result = new ParseNode(new ConcatPtg(), result, other);
}
+ return result;
}
/** Parse and Translate an Expression */
- private void additiveExpression() {
- Term();
+ private ParseNode additiveExpression() {
+ ParseNode result = Term();
while (true) {
SkipWhite();
+ Ptg operator;
switch(look) {
case '+':
Match('+');
- Term();
- tokens.add(new AddPtg());
- continue;
+ operator = new AddPtg();
+ break;
case '-':
Match('-');
- Term();
- tokens.add(new SubtractPtg());
- continue;
+ operator = new SubtractPtg();
+ break;
+ default:
+ return result; // finished with additive expression
}
- return; // finished with additive expression
+ ParseNode other = Term();
+ result = new ParseNode(operator, result, other);
}
}
@@ -835,7 +748,7 @@ end;
public void parse() {
pointer=0;
GetChar();
- comparisonExpression();
+ _rootNode = comparisonExpression();
if(pointer <= formulaLength) {
String msg = "Unused input [" + formulaString.substring(pointer-1)
@@ -858,87 +771,12 @@ end;
}
public Ptg[] getRPNPtg(int formulaType) {
- Node node = createTree();
+ OperandClassTransformer oct = new OperandClassTransformer(formulaType);
// RVA is for 'operand class': 'reference', 'value', 'array'
- setRootLevelRVA(node, formulaType);
- setParameterRVA(node,formulaType);
- return (Ptg[]) tokens.toArray(new Ptg[0]);
+ oct.transformFormula(_rootNode);
+ return ParseNode.toTokenArray(_rootNode);
}
- private void setRootLevelRVA(Node n, int formulaType) {
- //Pg 16, excelfileformat.pdf @ openoffice.org
- Ptg p = n.getValue();
- if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
- if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
- setClass(n,Ptg.CLASS_REF);
- } else {
- setClass(n,Ptg.CLASS_ARRAY);
- }
- } else {
- setClass(n,Ptg.CLASS_VALUE);
- }
-
- }
-
- private void setParameterRVA(Node n, int formulaType) {
- Ptg p = n.getValue();
- int numOperands = n.getNumChildren();
- if (p instanceof AbstractFunctionPtg) {
- for (int i =0;i= 0; j--) { // reverse iteration because args were pushed in-order
- if(stack.isEmpty()) {
- String msg = "Too few arguments suppled to operation token ("
- + o.getClass().getName() + "). Expected (" + nOperands
- + ") operands but got (" + (nOperands - j - 1) + ")";
- throw new IllegalStateException(msg);
- }
- operands[j] = (String) stack.pop();
+ if (! (ptg instanceof OperationPtg)) {
+ stack.push(ptg.toFormulaString(book));
+ continue;
}
+
+ OperationPtg o = (OperationPtg) ptg;
+ String[] operands = getOperands(stack, o.getNumberOfOperands());
stack.push(o.toFormulaString(operands));
}
if(stack.isEmpty()) {
@@ -1042,6 +877,20 @@ end;
}
return result;
}
+
+ private static String[] getOperands(Stack stack, int nOperands) {
+ String[] operands = new String[nOperands];
+
+ for (int j = nOperands-1; j >= 0; j--) { // reverse iteration because args were pushed in-order
+ if(stack.isEmpty()) {
+ String msg = "Too few arguments supplied to operation. Expected (" + nOperands
+ + ") operands but got (" + (nOperands - j - 1) + ")";
+ throw new IllegalStateException(msg);
+ }
+ operands[j] = (String) stack.pop();
+ }
+ return operands;
+ }
/**
* Static method to convert an array of Ptgs in RPN order
* to a human readable string format in infix mode. Works
@@ -1052,59 +901,4 @@ end;
public String toFormulaString(Ptg[] ptgs) {
return toFormulaString(book, ptgs);
}
-
-
- /** Create a tree representation of the RPN token array
- *used to run the class(RVA) change algo
- */
- private Node createTree() {
- Stack stack = new Stack();
- int numPtgs = tokens.size();
- OperationPtg o;
- int numOperands;
- Node[] operands;
- for (int i=0;i
+ * reference
+ * value
+ * array
+ *
+ *
+ *
+ * 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 may 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.
+ *
+ * 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).
+ *
+ * Hopefully, as additional important test cases are identified and added to the test suite,
+ * patterns might become more obvious in this code and allow for simplification.
+ *
+ * @author Josh Micich
+ */
+final class OperandClassTransformer {
+
+ private final int _formulaType;
+
+ public OperandClassTransformer(int formulaType) {
+ _formulaType = formulaType;
+ }
+
+ /**
+ * Traverses the supplied formula parse tree, calling Ptg.setClass() for each non-base
+ * token to set its operand class.
+ */
+ public void transformFormula(ParseNode rootNode) {
+ byte rootNodeOperandClass;
+ switch (_formulaType) {
+ case FormulaParser.FORMULA_TYPE_CELL:
+ rootNodeOperandClass = Ptg.CLASS_VALUE;
+ break;
+ default:
+ throw new RuntimeException("Incomplete code - formula type ("
+ + _formulaType + ") not supported yet");
+
+ }
+ transformNode(rootNode, rootNodeOperandClass, false);
+ }
+
+ private void transformNode(ParseNode node, byte desiredOperandClass,
+ boolean callerForceArrayFlag) {
+ Ptg token = node.getToken();
+ ParseNode[] children = node.getChildren();
+ 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
+ for (int i = 0; i < children.length; i++) {
+ ParseNode child = children[i];
+ transformNode(child, desiredOperandClass, 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;
+ }
+ if (callerForceArrayFlag) {
+ switch (desiredOperandClass) {
+ case Ptg.CLASS_VALUE:
+ case Ptg.CLASS_ARRAY:
+ token.setClass(Ptg.CLASS_ARRAY);
+ break;
+ case Ptg.CLASS_REF:
+ token.setClass(Ptg.CLASS_REF);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected operand class ("
+ + desiredOperandClass + ")");
+ }
+ } else {
+ token.setClass(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);
+ 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);
+ }
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/model/ParseNode.java b/src/java/org/apache/poi/hssf/model/ParseNode.java
new file mode 100644
index 0000000000..acd8cb12be
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/model/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.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; i= token_1_columns) {
throw new IllegalArgumentException("Specified colIx (" + colIx
@@ -104,7 +112,7 @@ public class ArrayPtg extends Ptg {
throw new IllegalArgumentException("Specified rowIx (" + rowIx
+ ") is outside the allowed range (0.." + (token_2_rows-1) + ")");
}
- return rowIx * token_1_columns + colIx;
+ return rowIx + token_2_rows * colIx;
}
public void writeBytes(byte[] data, int offset) {
diff --git a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
index 263c1728ba..8ebbdc3316 100644
--- a/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
@@ -15,7 +15,6 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.usermodel.Workbook;
@@ -32,8 +31,7 @@ import org.apache.poi.util.BitFieldFactory;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public final class AttrPtg extends OperationPtg {
+public final class AttrPtg extends ControlPtg {
public final static byte sid = 0x19;
private final static int SIZE = 4;
private byte field_1_options;
@@ -289,12 +287,6 @@ public final class AttrPtg extends OperationPtg {
}
return "UNKNOWN ATTRIBUTE";
}
-
-
-
- public byte getDefaultOperandClass() {
- return Ptg.CLASS_VALUE;
- }
public Object clone() {
int[] jt;
diff --git a/src/java/org/apache/poi/hssf/record/formula/BoolPtg.java b/src/java/org/apache/poi/hssf/record/formula/BoolPtg.java
index a738653951..401bf4df02 100644
--- a/src/java/org/apache/poi/hssf/record/formula/BoolPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/BoolPtg.java
@@ -27,10 +27,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class BoolPtg
- extends Ptg
-{
+public final class BoolPtg extends ScalarConstantPtg {
public final static int SIZE = 2;
public final static byte sid = 0x1d;
private boolean field_1_value;
@@ -75,8 +72,6 @@ public class BoolPtg
return field_1_value ? "TRUE" : "FALSE";
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
BoolPtg ptg = new BoolPtg();
ptg.field_1_value = field_1_value;
diff --git a/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java b/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java
index 366a2ad3c2..6473c7f989 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java
@@ -15,7 +15,6 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.usermodel.Workbook;
@@ -26,10 +25,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class ConcatPtg
- extends OperationPtg
-{
+public final class ConcatPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x08;
diff --git a/src/java/org/apache/poi/hssf/record/formula/ControlPtg.java b/src/java/org/apache/poi/hssf/record/formula/ControlPtg.java
index 52c6836198..6c97bd1a37 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ControlPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ControlPtg.java
@@ -15,11 +15,24 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
-public abstract class ControlPtg
- extends Ptg
-{
+/**
+ * Common superclass for
+ * tExp
+ * tTbl
+ * tParen
+ * tNlr
+ * tAttr
+ * tSheet
+ * tEndSheet
+ */
+public abstract class ControlPtg extends Ptg {
+ public boolean isBaseToken() {
+ return true;
+ }
+ public final byte getDefaultOperandClass() {
+ throw new IllegalStateException("Control tokens are not classified");
+ }
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/DividePtg.java b/src/java/org/apache/poi/hssf/record/formula/DividePtg.java
index f471c8a3ce..960ff90320 100644
--- a/src/java/org/apache/poi/hssf/record/formula/DividePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/DividePtg.java
@@ -15,7 +15,6 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.usermodel.Workbook;
@@ -26,10 +25,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Andrew C. Oliver acoliver at apache dot org
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class DividePtg
- extends OperationPtg
-{
+public final class DividePtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x06;
diff --git a/src/java/org/apache/poi/hssf/record/formula/EqualPtg.java b/src/java/org/apache/poi/hssf/record/formula/EqualPtg.java
index b31a6fa14c..9ae08426b2 100644
--- a/src/java/org/apache/poi/hssf/record/formula/EqualPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/EqualPtg.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -25,10 +24,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
*
* @author andy
*/
-
-public class EqualPtg
- extends OperationPtg
-{
+public final class EqualPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x0b;
diff --git a/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java b/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java
index 3343238b99..9d81e3bc24 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ErrPtg.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -16,7 +15,6 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.usermodel.Workbook;
@@ -26,7 +24,7 @@ import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
/**
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public final class ErrPtg extends Ptg {
+public final class ErrPtg extends ScalarConstantPtg {
// convenient access to namespace
private static final HSSFErrorConstants EC = null;
@@ -78,10 +76,6 @@ public final class ErrPtg extends Ptg {
return SIZE;
}
- public byte getDefaultOperandClass() {
- return Ptg.CLASS_VALUE;
- }
-
public Object clone() {
return new ErrPtg(field_1_error_code);
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java b/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
index 05b0fbe864..91f4baf3b1 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
@@ -31,9 +31,7 @@ import org.apache.poi.util.LittleEndian;
* @author dmui (save existing implementation)
*/
-public class ExpPtg
- extends Ptg
-{
+public final class ExpPtg extends ControlPtg {
private final static int SIZE = 5;
public final static short sid = 0x1;
private short field_1_first_row;
@@ -52,7 +50,7 @@ public class ExpPtg
field_1_first_row = in.readShort();
field_2_first_col = in.readShort();
}
-
+
public void writeBytes(byte [] array, int offset)
{
array[offset+0]= (byte) (sid);
@@ -86,8 +84,6 @@ public class ExpPtg
return buffer.toString();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
ExpPtg result = new ExpPtg();
result.field_1_first_row = field_1_first_row;
diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
index 364ddf5a02..cea44ed430 100644
--- a/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
@@ -44,6 +44,8 @@ public final class FuncPtg extends AbstractFunctionPtg {
throw new RuntimeException("Invalid built-in function index (" + field_2_fnc_index + ")");
}
numParams = fm.getMinParams();
+ returnClass = fm.getReturnClassCode();
+ paramClass = fm.getParameterClassCodes();
}
public FuncPtg(int functionIndex) {
field_2_fnc_index = (short) functionIndex;
diff --git a/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java b/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
index 431dc5717b..e3d2e77310 100644
--- a/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
@@ -40,6 +40,15 @@ public final class FuncVarPtg extends AbstractFunctionPtg{
public FuncVarPtg(RecordInputStream in) {
field_1_num_args = in.readByte();
field_2_fnc_index = in.readShort();
+ FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByIndex(field_2_fnc_index);
+ if(fm == null) {
+ // Happens only as a result of a call to FormulaParser.parse(), with a non-built-in function name
+ returnClass = Ptg.CLASS_VALUE;
+ paramClass = new byte[] {Ptg.CLASS_VALUE};
+ } else {
+ returnClass = fm.getReturnClassCode();
+ paramClass = fm.getParameterClassCodes();
+ }
}
/**
diff --git a/src/java/org/apache/poi/hssf/record/formula/GreaterEqualPtg.java b/src/java/org/apache/poi/hssf/record/formula/GreaterEqualPtg.java
index 88cfa84d4f..91b9713a84 100755
--- a/src/java/org/apache/poi/hssf/record/formula/GreaterEqualPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/GreaterEqualPtg.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -16,22 +15,17 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
-
/**
* PTG class to implement greater or equal to
*
* @author fred at stsci dot edu
*/
-
-public class GreaterEqualPtg
- extends OperationPtg
-{
+public final class GreaterEqualPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x0c;
diff --git a/src/java/org/apache/poi/hssf/record/formula/GreaterThanPtg.java b/src/java/org/apache/poi/hssf/record/formula/GreaterThanPtg.java
index cfe45a404a..9bc83e5690 100644
--- a/src/java/org/apache/poi/hssf/record/formula/GreaterThanPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/GreaterThanPtg.java
@@ -15,16 +15,8 @@
limitations under the License.
==================================================================== */
-
-/*
- * GreaterThanPtg.java
- *
- * Created on January 23, 2003, 9:47 AM
- */
package org.apache.poi.hssf.record.formula;
-import java.util.List;
-
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
@@ -32,9 +24,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* Greater than operator PTG ">"
* @author Cameron Riley (criley at ekmail.com)
*/
-public class GreaterThanPtg
- extends OperationPtg
-{
+public final class GreaterThanPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x0D;
private final static String GREATERTHAN = ">";
@@ -117,15 +107,6 @@ public class GreaterThanPtg
return buffer.toString();
}
- /**
- * Get the default operands class value
- * @return byte the Ptg Class Value as a byte from the Ptg Parent object
- */
- public byte getDefaultOperandClass()
- {
- return Ptg.CLASS_VALUE;
- }
-
/**
* Implementation of clone method from Object
* @return Object a clone of this class as an Object
diff --git a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java
index a1753f5ac1..673ce4fade 100644
--- a/src/java/org/apache/poi/hssf/record/formula/IntPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/IntPtg.java
@@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public final class IntPtg extends Ptg {
+public final class IntPtg extends ScalarConstantPtg {
// 16 bit unsigned integer
private static final int MIN_VALUE = 0x0000;
private static final int MAX_VALUE = 0xFFFF;
@@ -75,9 +75,6 @@ public final class IntPtg extends Ptg {
public String toFormulaString(Workbook book) {
return String.valueOf(getValue());
}
- public byte getDefaultOperandClass() {
- return Ptg.CLASS_VALUE;
- }
public Object clone() {
return new IntPtg(field_1_value);
diff --git a/src/java/org/apache/poi/hssf/record/formula/IntersectionPtg.java b/src/java/org/apache/poi/hssf/record/formula/IntersectionPtg.java
index 61d02edaa7..e7a0ea7ce5 100644
--- a/src/java/org/apache/poi/hssf/record/formula/IntersectionPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/IntersectionPtg.java
@@ -23,8 +23,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public class IntersectionPtg extends OperationPtg
-{
+public final class IntersectionPtg extends OperationPtg {
public final static byte sid = 0x0f;
@@ -37,6 +36,9 @@ public class IntersectionPtg extends OperationPtg
// doesn't need anything
}
+ public final boolean isBaseToken() {
+ return true;
+ }
public int getSize()
{
diff --git a/src/java/org/apache/poi/hssf/record/formula/LessEqualPtg.java b/src/java/org/apache/poi/hssf/record/formula/LessEqualPtg.java
index e63fda02d3..f6e4919c45 100755
--- a/src/java/org/apache/poi/hssf/record/formula/LessEqualPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/LessEqualPtg.java
@@ -16,7 +16,6 @@
limitations under the License.
==================================================================== */
-
package org.apache.poi.hssf.record.formula;
@@ -29,9 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
*
* @author fred at stsci dot edu
*/
-public class LessEqualPtg
- extends OperationPtg
-{
+public final class LessEqualPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x0a;
diff --git a/src/java/org/apache/poi/hssf/record/formula/LessThanPtg.java b/src/java/org/apache/poi/hssf/record/formula/LessThanPtg.java
index 6f3c0b0a29..03241ef37e 100644
--- a/src/java/org/apache/poi/hssf/record/formula/LessThanPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/LessThanPtg.java
@@ -15,18 +15,8 @@
limitations under the License.
==================================================================== */
-
-/*
- * LessThanPtg.java
- *
- * Created on January 23, 2003, 9:47 AM
- */
package org.apache.poi.hssf.record.formula;
-//JDK
-import java.util.List;
-
-//POI
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
@@ -36,9 +26,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* Table 3.5.7
* @author Cameron Riley (criley at ekmail.com)
*/
-public class LessThanPtg
- extends OperationPtg
-{
+public final class LessThanPtg extends ValueOperatorPtg {
/** the size of the Ptg */
public final static int SIZE = 1;
@@ -125,15 +113,6 @@ public class LessThanPtg
return buffer.toString();
}
- /**
- * Get the default operands class value
- * @return byte the Ptg Class Value as a byte from the Ptg Parent object
- */
- public byte getDefaultOperandClass()
- {
- return Ptg.CLASS_VALUE;
- }
-
/**
* Implementation of clone method from Object
* @return Object a clone of this class as an Object
diff --git a/src/java/org/apache/poi/hssf/record/formula/MemAreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/MemAreaPtg.java
index add7b0461b..a2e075c228 100644
--- a/src/java/org/apache/poi/hssf/record/formula/MemAreaPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/MemAreaPtg.java
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -16,12 +15,6 @@
limitations under the License.
==================================================================== */
-
-/*
- * MemAreaPtg.java
- *
- * Created on November 21, 2001, 8:46 AM
- */
package org.apache.poi.hssf.record.formula;
import org.apache.poi.util.LittleEndian;
@@ -31,9 +24,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public class MemAreaPtg
- extends Ptg
-{
+public class MemAreaPtg extends OperandPtg {
public final static short sid = 0x26;
private final static int SIZE = 7;
private int field_1_reserved;
diff --git a/src/java/org/apache/poi/hssf/record/formula/MemFuncPtg.java b/src/java/org/apache/poi/hssf/record/formula/MemFuncPtg.java
index ac23e8aab7..a351cbc9ad 100644
--- a/src/java/org/apache/poi/hssf/record/formula/MemFuncPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/MemFuncPtg.java
@@ -30,8 +30,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Glen Stampoultzis (glens at apache.org)
*/
-public class MemFuncPtg extends ControlPtg
-{
+public class MemFuncPtg extends OperandPtg {
public final static byte sid = 0x29;
private short field_1_len_ref_subexpression = 0;
diff --git a/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java b/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java
index 32ba801406..890f9e895f 100644
--- a/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java
@@ -26,9 +26,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* Avik Sengupta <avik at apache.org>
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class MissingArgPtg
- extends Ptg
-{
+public final class MissingArgPtg extends ScalarConstantPtg {
private final static int SIZE = 1;
public final static byte sid = 0x16;
@@ -59,8 +57,6 @@ public class MissingArgPtg
{
return " ";
}
-
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
public Object clone() {
return new MissingArgPtg();
diff --git a/src/java/org/apache/poi/hssf/record/formula/MultiplyPtg.java b/src/java/org/apache/poi/hssf/record/formula/MultiplyPtg.java
index 04589c1a86..1c5c80eab1 100644
--- a/src/java/org/apache/poi/hssf/record/formula/MultiplyPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/MultiplyPtg.java
@@ -25,9 +25,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class MultiplyPtg
- extends OperationPtg
-{
+public final class MultiplyPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x05;
diff --git a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
index f501f2b3da..22215e2ec6 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NamePtg.java
@@ -27,10 +27,7 @@ import org.apache.poi.util.LittleEndian;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class NamePtg
- extends Ptg
-{
+public final class NamePtg extends OperandPtg {
public final static short sid = 0x23;
private final static int SIZE = 5;
/** one-based index to defined name record */
diff --git a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
index 39e05262a7..6d98ef2eb4 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
@@ -25,7 +25,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
*
* @author aviks
*/
-public final class NameXPtg extends Ptg {
+public final class NameXPtg extends OperandPtg {
public final static short sid = 0x39;
private final static int SIZE = 7;
private short field_1_ixals; // index to REF entry in externsheet record
diff --git a/src/java/org/apache/poi/hssf/record/formula/NotEqualPtg.java b/src/java/org/apache/poi/hssf/record/formula/NotEqualPtg.java
index 713e8fb046..95b68e87da 100755
--- a/src/java/org/apache/poi/hssf/record/formula/NotEqualPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NotEqualPtg.java
@@ -26,9 +26,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
*
* @author fred at stsci dot edu
*/
-public class NotEqualPtg
- extends OperationPtg
-{
+public final class NotEqualPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x0e;
diff --git a/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java b/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java
index f74faf0917..a6d9e92597 100644
--- a/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/NumberPtg.java
@@ -28,10 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Avik Sengupta
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class NumberPtg
- extends Ptg
-{
+public final class NumberPtg extends ScalarConstantPtg {
public final static int SIZE = 9;
public final static byte sid = 0x1f;
private double field_1_value;
@@ -82,7 +79,6 @@ public class NumberPtg
{
return "" + getValue();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
public Object clone() {
NumberPtg ptg = new NumberPtg();
diff --git a/src/java/org/apache/poi/hssf/record/formula/OperandPtg.java b/src/java/org/apache/poi/hssf/record/formula/OperandPtg.java
new file mode 100644
index 0000000000..02a708f64a
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/OperandPtg.java
@@ -0,0 +1,31 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula;
+
+/**
+ * @author Josh Micich
+ */
+public abstract class OperandPtg extends Ptg {
+
+ /**
+ * All Operand Ptgs are classifed ('relative', 'value', 'array')
+ */
+ public final boolean isBaseToken() {
+ return false;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ParenthesisPtg.java b/src/java/org/apache/poi/hssf/record/formula/ParenthesisPtg.java
index 9d977f5eb8..7781d061ce 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ParenthesisPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ParenthesisPtg.java
@@ -32,9 +32,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* Andrew C. Oliver (acoliver at apache dot org)
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class ParenthesisPtg
- extends OperationPtg
-{
+public final class ParenthesisPtg extends ControlPtg {
private final static int SIZE = 1;
public final static byte sid = 0x15;
@@ -61,16 +59,6 @@ public class ParenthesisPtg
return SIZE;
}
- public int getType()
- {
- return TYPE_BINARY;
- }
-
- public int getNumberOfOperands()
- {
- return 1;
- }
-
public String toFormulaString(Workbook book)
{
return "()";
@@ -80,8 +68,6 @@ public class ParenthesisPtg
public String toFormulaString(String[] operands) {
return "("+operands[0]+")";
}
-
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
public Object clone() {
return new ParenthesisPtg();
diff --git a/src/java/org/apache/poi/hssf/record/formula/PercentPtg.java b/src/java/org/apache/poi/hssf/record/formula/PercentPtg.java
index 3e1d650dcd..a8d6450b4e 100644
--- a/src/java/org/apache/poi/hssf/record/formula/PercentPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/PercentPtg.java
@@ -32,9 +32,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Daniel Noll (daniel at nuix.com.au)
*/
-public class PercentPtg
- extends OperationPtg
-{
+public final class PercentPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x14;
@@ -88,8 +86,6 @@ public class PercentPtg
return buffer.toString();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
return new PercentPtg();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/PowerPtg.java b/src/java/org/apache/poi/hssf/record/formula/PowerPtg.java
index 327f9c566a..dbd0f28581 100644
--- a/src/java/org/apache/poi/hssf/record/formula/PowerPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/PowerPtg.java
@@ -17,8 +17,6 @@
package org.apache.poi.hssf.record.formula;
-import java.util.List;
-
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.record.RecordInputStream;
@@ -27,10 +25,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class PowerPtg
- extends OperationPtg
-{
+public final class PowerPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x07;
diff --git a/src/java/org/apache/poi/hssf/record/formula/Ptg.java b/src/java/org/apache/poi/hssf/record/formula/Ptg.java
index 8816a2d683..7819c35bc8 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ptg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ptg.java
@@ -119,254 +119,14 @@ public abstract class Ptg
return stack;
}
- public static Ptg createPtg(RecordInputStream in)
- {
- byte id = in.readByte();
- Ptg retval = null;
-
- switch (id)
- {
- case ExpPtg.sid : // 0x01
- retval = new ExpPtg(in);
- break;
-
- case AddPtg.sid : // 0x03
- retval = new AddPtg(in);
- break;
-
- case SubtractPtg.sid : // 0x04
- retval = new SubtractPtg(in);
- break;
-
- case MultiplyPtg.sid : // 0x05
- retval = new MultiplyPtg(in);
- break;
-
- case DividePtg.sid : // 0x06
- retval = new DividePtg(in);
- break;
-
- case PowerPtg.sid : // 0x07
- retval = new PowerPtg(in);
- break;
-
- case ConcatPtg.sid : // 0x08
- retval = new ConcatPtg(in);
- break;
-
- case LessThanPtg.sid: // 0x09
- retval = new LessThanPtg(in);
- break;
-
- case LessEqualPtg.sid : // 0x0a
- retval = new LessEqualPtg(in);
- break;
-
- case EqualPtg.sid : // 0x0b
- retval = new EqualPtg(in);
- break;
-
- case GreaterEqualPtg.sid : // 0x0c
- retval = new GreaterEqualPtg(in);
- break;
-
- case GreaterThanPtg.sid : // 0x0d
- retval = new GreaterThanPtg(in);
- break;
-
- case NotEqualPtg.sid : // 0x0e
- retval = new NotEqualPtg(in);
- break;
-
- case IntersectionPtg.sid : // 0x0f
- retval = new IntersectionPtg(in);
- break;
- case UnionPtg.sid : // 0x10
- retval = new UnionPtg(in);
- break;
-
- case RangePtg.sid : // 0x11
- retval = new RangePtg(in);
- break;
-
- case UnaryPlusPtg.sid : // 0x12
- retval = new UnaryPlusPtg(in);
- break;
-
- case UnaryMinusPtg.sid : // 0x13
- retval = new UnaryMinusPtg(in);
- break;
-
- case PercentPtg.sid : // 0x14
- retval = new PercentPtg(in);
- break;
-
- case ParenthesisPtg.sid : // 0x15
- retval = new ParenthesisPtg(in);
- break;
-
- case MissingArgPtg.sid : // 0x16
- retval = new MissingArgPtg(in);
- break;
-
- case StringPtg.sid : // 0x17
- retval = new StringPtg(in);
- break;
-
- case AttrPtg.sid : // 0x19
- case 0x1a :
- retval = new AttrPtg(in);
- break;
-
- case ErrPtg.sid : // 0x1c
- retval = new ErrPtg(in);
- break;
-
- case BoolPtg.sid : // 0x1d
- retval = new BoolPtg(in);
- break;
-
- case IntPtg.sid : // 0x1e
- retval = new IntPtg(in);
- break;
-
- case NumberPtg.sid : // 0x1f
- retval = new NumberPtg(in);
- break;
-
- case ArrayPtg.sid : // 0x20
- retval = new ArrayPtg(in);
- break;
- case ArrayPtgV.sid : // 0x40
- retval = new ArrayPtgV(in);
- break;
- case ArrayPtgA.sid : // 0x60
- retval = new ArrayPtgA(in);
- break;
-
- case FuncPtg.sid : // 0x21
- case FuncPtg.sid + 0x20 : // 0x41
- case FuncPtg.sid + 0x40 : // 0x61
- retval = new FuncPtg(in);
- break;
-
- case FuncVarPtg.sid : // 0x22
- case FuncVarPtg.sid + 0x20 : // 0x42
- case FuncVarPtg.sid + 0x40 : // 0x62
- retval = new FuncVarPtg(in);
- break;
-
- case ReferencePtg.sid : // 0x24
- retval = new ReferencePtg(in);
- break;
- case RefAPtg.sid : // 0x64
- retval = new RefAPtg(in);
- break;
- case RefVPtg.sid : // 0x44
- retval = new RefVPtg(in);
- break;
- case RefNAPtg.sid : // 0x6C
- retval = new RefNAPtg(in);
- break;
- case RefNPtg.sid : // 0x2C
- retval = new RefNPtg(in);
- break;
- case RefNVPtg.sid : // 0x4C
- retval = new RefNVPtg(in);
- break;
-
- case AreaPtg.sid : // 0x25
- retval = new AreaPtg(in);
- break;
- case AreaVPtg.sid: // 0x45
- retval = new AreaVPtg(in);
- break;
- case AreaAPtg.sid: // 0x65
- retval = new AreaAPtg(in);
- break;
- case AreaNAPtg.sid : // 0x6D
- retval = new AreaNAPtg(in);
- break;
- case AreaNPtg.sid : // 0x2D
- retval = new AreaNPtg(in);
- break;
- case AreaNVPtg.sid : // 0x4D
- retval = new AreaNVPtg(in);
- break;
-
- case MemAreaPtg.sid : // 0x26
- case MemAreaPtg.sid + 0x40 : // 0x46
- case MemAreaPtg.sid + 0x20 : // 0x66
- retval = new MemAreaPtg(in);
- break;
-
- case MemErrPtg.sid : // 0x27
- case MemErrPtg.sid + 0x20 : // 0x47
- case MemErrPtg.sid + 0x40 : // 0x67
- retval = new MemErrPtg(in);
- break;
-
- case MemFuncPtg.sid : // 0x29
- retval = new MemFuncPtg(in);
- break;
-
- case RefErrorPtg.sid : // 0x2a
- case RefErrorPtg.sid + 0x20 : // 0x4a
- case RefErrorPtg.sid + 0x40 : // 0x6a
- retval = new RefErrorPtg(in);
- break;
-
- case AreaErrPtg.sid : // 0x2b
- case AreaErrPtg.sid + 0x20 : // 0x4b
- case AreaErrPtg.sid + 0x40 : // 0x6b
- retval = new AreaErrPtg(in);
- break;
-
- case NamePtg.sid : // 0x23
- case NamePtg.sid + 0x20 : // 0x43
- case NamePtg.sid + 0x40 : // 0x63
- retval = new NamePtg(in);
- break;
-
- case NameXPtg.sid : // 0x39
- case NameXPtg.sid + 0x20 : // 0x45
- case NameXPtg.sid + 0x40 : // 0x79
- retval = new NameXPtg(in);
- break;
-
- case Area3DPtg.sid : // 0x3b
- case Area3DPtg.sid + 0x20 : // 0x5b
- case Area3DPtg.sid + 0x40 : // 0x7b
- retval = new Area3DPtg(in);
- break;
-
- case Ref3DPtg.sid : // 0x3a
- case Ref3DPtg.sid + 0x20: // 0x5a
- case Ref3DPtg.sid + 0x40: // 0x7a
- retval = new Ref3DPtg(in);
- break;
-
- case DeletedRef3DPtg.sid: // 0x3c
- case DeletedRef3DPtg.sid + 0x20: // 0x5c
- case DeletedRef3DPtg.sid + 0x40: // 0x7c
- retval = new DeletedRef3DPtg(in);
- break;
-
- case DeletedArea3DPtg.sid : // 0x3d
- case DeletedArea3DPtg.sid + 0x20 : // 0x5d
- case DeletedArea3DPtg.sid + 0x40 : // 0x7d
- retval = new DeletedArea3DPtg(in);
- break;
-
- case 0x00:
- retval = new UnknownPtg();
- break;
-
- default :
- //retval = new UnknownPtg();
- throw new java.lang.UnsupportedOperationException(" Unknown Ptg in Formula: 0x"+
- Integer.toHexString(( int ) id) + " (" + ( int ) id + ")");
+ public static Ptg createPtg(RecordInputStream in) {
+ byte id = in.readByte();
+
+ if (id < 0x20) {
+ return createBasePtg(id, in);
}
+
+ Ptg retval = createClassifiedPtg(id, in);
if (id > 0x60) {
retval.setClass(CLASS_ARRAY);
@@ -380,6 +140,118 @@ public abstract class Ptg
}
+ private static Ptg createClassifiedPtg(byte id, RecordInputStream in) {
+
+ int baseId = id & 0x1F | 0x20;
+
+ switch (baseId) {
+ case FuncPtg.sid: return new FuncPtg(in); // 0x21, 0x41, 0x61
+ case FuncVarPtg.sid: return new FuncVarPtg(in); // 0x22, 0x42, 0x62
+ case NamePtg.sid: return new NamePtg(in); // 0x23, 0x43, 0x63
+
+ case MemAreaPtg.sid: return new MemAreaPtg(in); // 0x26, 0x46, 0x66
+ case MemErrPtg.sid: return new MemErrPtg(in); // 0x27, 0x47, 0x67
+ case MemFuncPtg.sid: return new MemFuncPtg(in); // 0x29, 0x49, 0x69
+ case RefErrorPtg.sid: return new RefErrorPtg(in);// 0x2a, 0x4a, 0x6a
+ case AreaErrPtg.sid: return new AreaErrPtg(in); // 0x2b, 0x4b, 0x6b
+
+ case NameXPtg.sid: return new NameXPtg(in); // 0x39, 0x49, 0x79
+ case Ref3DPtg.sid: return new Ref3DPtg(in); // 0x3a, 0x5a, 0x7a
+ case Area3DPtg.sid: return new Area3DPtg(in); // 0x3b, 0x5b, 0x7b
+ case DeletedRef3DPtg.sid: return new DeletedRef3DPtg(in); // 0x3c, 0x5c, 0x7c
+ case DeletedArea3DPtg.sid: return new DeletedArea3DPtg(in); // 0x3d, 0x5d, 0x7d
+ }
+
+
+ switch (id) {
+ // TODO - why are specific subclasses needed for these Ptgs?
+ case ArrayPtg.sid: return new ArrayPtg(in); // 0x20
+ case ArrayPtgV.sid: return new ArrayPtgV(in); // 0x40
+ case ArrayPtgA.sid: return new ArrayPtgA(in); // 0x60
+
+ case ReferencePtg.sid: return new ReferencePtg(in);// 0x24
+ case RefAPtg.sid: return new RefAPtg(in); // 0x64
+ case RefVPtg.sid: return new RefVPtg(in); // 0x44
+
+ case RefNAPtg.sid: return new RefNAPtg(in); // 0x6C
+ case RefNPtg.sid: return new RefNPtg(in); // 0x2C
+ case RefNVPtg.sid: return new RefNVPtg(in); // 0x4C
+
+ case AreaPtg.sid: return new AreaPtg(in); // 0x25
+ case AreaVPtg.sid: return new AreaVPtg(in); // 0x45
+ case AreaAPtg.sid: return new AreaAPtg(in); // 0x65
+
+ case AreaNAPtg.sid: return new AreaNAPtg(in); // 0x6D
+ case AreaNPtg.sid: return new AreaNPtg(in); // 0x2D
+ case AreaNVPtg.sid: return new AreaNVPtg(in); // 0x4D
+
+ }
+ throw new UnsupportedOperationException(" Unknown Ptg in Formula: 0x"+
+ Integer.toHexString(id) + " (" + ( int ) id + ")");
+ }
+
+ private static Ptg createBasePtg(byte id, RecordInputStream in) {
+ switch(id) {
+ case 0x00: return new UnknownPtg(); // TODO - not a real Ptg
+ case ExpPtg.sid: return new ExpPtg(in); // 0x01
+ case AddPtg.sid: return new AddPtg(in); // 0x03
+ case SubtractPtg.sid: return new SubtractPtg(in); // 0x04
+ case MultiplyPtg.sid: return new MultiplyPtg(in); // 0x05
+ case DividePtg.sid: return new DividePtg(in); // 0x06
+ case PowerPtg.sid: return new PowerPtg(in); // 0x07
+ case ConcatPtg.sid: return new ConcatPtg(in); // 0x08
+ case LessThanPtg.sid: return new LessThanPtg(in); // 0x09
+ case LessEqualPtg.sid: return new LessEqualPtg(in); // 0x0a
+ case EqualPtg.sid: return new EqualPtg(in); // 0x0b
+ case GreaterEqualPtg.sid: return new GreaterEqualPtg(in);// 0x0c
+ case GreaterThanPtg.sid: return new GreaterThanPtg(in); // 0x0d
+ case NotEqualPtg.sid: return new NotEqualPtg(in); // 0x0e
+ case IntersectionPtg.sid: return new IntersectionPtg(in);// 0x0f
+ case UnionPtg.sid: return new UnionPtg(in); // 0x10
+ case RangePtg.sid: return new RangePtg(in); // 0x11
+ case UnaryPlusPtg.sid: return new UnaryPlusPtg(in); // 0x12
+ case UnaryMinusPtg.sid: return new UnaryMinusPtg(in); // 0x13
+ case PercentPtg.sid: return new PercentPtg(in); // 0x14
+ case ParenthesisPtg.sid: return new ParenthesisPtg(in); // 0x15
+ case MissingArgPtg.sid: return new MissingArgPtg(in); // 0x16
+ case StringPtg.sid: return new StringPtg(in); // 0x17
+ case AttrPtg.sid:
+ case 0x1a: return new AttrPtg(in); // 0x19
+ case ErrPtg.sid: return new ErrPtg(in); // 0x1c
+ case BoolPtg.sid: return new BoolPtg(in); // 0x1d
+ case IntPtg.sid: return new IntPtg(in); // 0x1e
+ case NumberPtg.sid: return new NumberPtg(in); // 0x1f
+ }
+ throw new RuntimeException("Unexpected base token id (" + id + ")");
+ }
+ /**
+ *
+ *
+ */
+ public static int getEncodedSize(Stack ptgs) {
+ return getEncodedSize(toPtgArray(ptgs));
+ }
+ private static Ptg[] toPtgArray(List l) {
+ Ptg[] result = new Ptg[l.size()];
+ l.toArray(result);
+ return result;
+ }
+ private static Stack createStack(Ptg[] formulaTokens) {
+ Stack result = new Stack();
+ for (int i = 0; i < formulaTokens.length; i++) {
+ result.add(formulaTokens[i]);
+ }
+ return result;
+ }
+ // TODO - several duplicates of this code should be refactored here
+ public static int getEncodedSize(Ptg[] ptgs) {
+ int result = 0;
+ for (int i = 0; i < ptgs.length; i++) {
+ result += ptgs[i].getSize();
+ }
+ return result;
+ }
+
public static int serializePtgStack(Stack expression, byte[] array, int offset) {
int pos = 0;
int size = 0;
@@ -408,7 +280,15 @@ public abstract class Ptg
return pos;
}
+ /**
+ * @return the encoded length of this Ptg, including the initial Ptg type identifier byte.
+ */
public abstract int getSize();
+
+ /**
+ * @return the encoded length of this Ptg, not including the initial Ptg type identifier byte.
+ */
+// public abstract int getDataSize();
public final byte [] getBytes()
{
@@ -455,10 +335,15 @@ public abstract class Ptg
protected byte ptgClass = CLASS_REF; //base ptg
public void setClass(byte thePtgClass) {
+ if (isBaseToken()) {
+ throw new RuntimeException("setClass should not be called on a base token");
+ }
ptgClass = thePtgClass;
}
- /** returns the class (REF/VALUE/ARRAY) for this Ptg */
+ /**
+ * @return the 'operand class' (REF/VALUE/ARRAY) for this Ptg
+ */
public byte getPtgClass() {
return ptgClass;
}
@@ -468,5 +353,8 @@ public abstract class Ptg
public abstract Object clone();
-
+ /**
+ * @return false
if this token is classified as 'reference', 'value', or 'array'
+ */
+ public abstract boolean isBaseToken();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java
index 09bedaecf6..05cb6defe1 100644
--- a/src/java/org/apache/poi/hssf/record/formula/RangePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/RangePtg.java
@@ -23,8 +23,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Daniel Noll (daniel at nuix dot com dot au)
*/
-public class RangePtg extends OperationPtg
-{
+public final class RangePtg extends OperationPtg {
public final static int SIZE = 1;
public final static byte sid = 0x11;
@@ -37,6 +36,10 @@ public class RangePtg extends OperationPtg
// No contents
}
+ public final boolean isBaseToken() {
+ return true;
+ }
+
public int getSize()
{
diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
index 1ae56cf52e..fa1563c040 100644
--- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
@@ -35,8 +35,7 @@ import org.apache.poi.util.LittleEndian;
* @author Jason Height (jheight at chariot dot net dot au)
* @version 1.0-pre
*/
-
-public class Ref3DPtg extends Ptg {
+public class Ref3DPtg extends OperandPtg {
public final static byte sid = 0x3a;
private final static int SIZE = 7; // 6 + 1 for Ptg
private short field_1_index_extern_sheet;
diff --git a/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java b/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
index 031fa41120..fed32ddade 100755
--- a/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
@@ -28,9 +28,8 @@ import org.apache.poi.hssf.record.RecordInputStream;
* RefError - handles deleted cell reference
* @author Jason Height (jheight at chariot dot net dot au)
*/
+public final class RefErrorPtg extends OperandPtg {
-public class RefErrorPtg extends Ptg
-{
private final static int SIZE = 5;
public final static byte sid = 0x2a;
private int field_1_reserved;
diff --git a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
index 4486ec087c..d06507e50e 100644
--- a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
@@ -30,14 +30,14 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Andrew C. Oliver (acoliver@apache.org)
* @author Jason Height (jheight at chariot dot net dot au)
*/
-public class ReferencePtg extends Ptg {
+public class ReferencePtg extends OperandPtg {
/**
* TODO - (May-2008) fix subclasses of ReferencePtg 'RefN~' which are used in shared formulas.
* (See bugzilla 44921)
- * The 'RefN~' instances do not work properly, and are expected to be converted by
- * SharedFormulaRecord.convertSharedFormulas().
- * This conversion currently does not take place for formulas of named ranges, conditional
- * format rules and data validation rules.
+ * The 'RefN~' instances do not work properly, and are expected to be converted by
+ * SharedFormulaRecord.convertSharedFormulas().
+ * This conversion currently does not take place for formulas of named ranges, conditional
+ * format rules and data validation rules.
* Furthermore, conversion is probably not appropriate in those instances.
*/
protected final RuntimeException notImplemented() {
@@ -46,14 +46,14 @@ public class ReferencePtg extends Ptg {
private final static int SIZE = 5;
public final static byte sid = 0x24;
- private final static int MAX_ROW_NUMBER = 65536;
+ private final static int MAX_ROW_NUMBER = 65536;
/** The row index - zero based unsigned 16 bit value */
private int field_1_row;
- /** Field 2
- * - lower 8 bits is the zero based unsigned byte column index
+ /** Field 2
+ * - lower 8 bits is the zero based unsigned byte column index
* - bit 16 - isRowRelative
- * - bit 15 - isColumnRelative
+ * - bit 15 - isColumnRelative
*/
private int field_2_col;
private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000);
@@ -63,9 +63,9 @@ public class ReferencePtg extends Ptg {
protected ReferencePtg() {
//Required for clone methods
}
-
+
/**
- * Takes in a String represnetation of a cell reference and fills out the
+ * Takes in a String represnetation of a cell reference and fills out the
* numeric fields.
*/
public ReferencePtg(String cellref) {
@@ -75,13 +75,13 @@ public class ReferencePtg extends Ptg {
setColRelative(!c.isColAbsolute());
setRowRelative(!c.isRowAbsolute());
}
-
+
public ReferencePtg(int row, int column, boolean isRowRelative, boolean isColumnRelative) {
setRow(row);
setColumn(column);
setRowRelative(isRowRelative);
setColRelative(isColumnRelative);
- }
+ }
/** Creates new ValueReferencePtg */
@@ -90,22 +90,19 @@ public class ReferencePtg extends Ptg {
field_1_row = in.readUShort();
field_2_col = in.readUShort();
}
-
+
public String getRefPtgName() {
return "ReferencePtg";
- }
+ }
- public String toString()
- {
- StringBuffer buffer = new StringBuffer("[");
- buffer.append(getRefPtgName());
- buffer.append("]\n");
-
- buffer.append("row = ").append(getRow()).append("\n");
- buffer.append("col = ").append(getColumn()).append("\n");
- buffer.append("rowrelative = ").append(isRowRelative()).append("\n");
- buffer.append("colrelative = ").append(isColRelative()).append("\n");
- return buffer.toString();
+ public String toString() {
+ CellReference cr = new CellReference(getRow(), getColumn(), !isRowRelative(),!isColRelative());
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getName());
+ sb.append(" [");
+ sb.append(cr.formatAsString());
+ sb.append("]");
+ return sb.toString();
}
public void writeBytes(byte [] array, int offset)
@@ -147,16 +144,16 @@ public class ReferencePtg extends Ptg {
{
return rowRelative.isSet(field_2_col);
}
-
+
public void setRowRelative(boolean rel) {
field_2_col=rowRelative.setBoolean(field_2_col,rel);
}
-
+
public boolean isColRelative()
{
return colRelative.isSet(field_2_col);
}
-
+
public void setColRelative(boolean rel) {
field_2_col=colRelative.setBoolean(field_2_col,rel);
}
@@ -193,11 +190,11 @@ public class ReferencePtg extends Ptg {
//TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe!
return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString();
}
-
+
public byte getDefaultOperandClass() {
return Ptg.CLASS_REF;
}
-
+
public Object clone() {
ReferencePtg ptg = new ReferencePtg();
ptg.field_1_row = field_1_row;
diff --git a/src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java b/src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java
new file mode 100644
index 0000000000..43b8c13920
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java
@@ -0,0 +1,31 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula;
+
+/**
+ * @author Josh Micich
+ */
+abstract class ScalarConstantPtg extends Ptg {
+ public boolean isBaseToken() {
+ return true;
+ }
+ public final byte getDefaultOperandClass() {
+ return Ptg.CLASS_VALUE;
+ }
+
+}
diff --git a/src/java/org/apache/poi/hssf/record/formula/StringPtg.java b/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
index c90590d1b8..6cd65005e2 100644
--- a/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/StringPtg.java
@@ -31,7 +31,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Jason Height (jheight at chariot dot net dot au)
* @author Bernard Chesnoy
*/
-public final class StringPtg extends Ptg {
+public final class StringPtg extends ScalarConstantPtg {
public final static int SIZE = 9;
public final static byte sid = 0x17;
private static final BitField fHighByte = BitFieldFactory.getInstance(0x01);
@@ -124,10 +124,6 @@ public final class StringPtg extends Ptg {
return sb.toString();
}
- public byte getDefaultOperandClass() {
- return Ptg.CLASS_VALUE;
- }
-
public Object clone() {
StringPtg ptg = new StringPtg();
ptg.field_1_length = field_1_length;
diff --git a/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java b/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
index 6d1d1e860d..fc99293fdc 100644
--- a/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
@@ -26,10 +26,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class SubtractPtg
- extends OperationPtg
-{
+public final class SubtractPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x04;
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
index d85cc4913e..296cf25c59 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
@@ -28,8 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Avik Sengupta
*/
-public class UnaryMinusPtg extends OperationPtg
-{
+public final class UnaryMinusPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x13;
@@ -82,8 +81,6 @@ public class UnaryMinusPtg extends OperationPtg
return buffer.toString();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
return new UnaryPlusPtg();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
index 6ae89cf2c1..eef161e441 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
@@ -28,8 +28,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author Avik Sengupta
*/
-public class UnaryPlusPtg extends OperationPtg
-{
+public final class UnaryPlusPtg extends ValueOperatorPtg {
public final static int SIZE = 1;
public final static byte sid = 0x12;
@@ -82,8 +81,6 @@ public class UnaryPlusPtg extends OperationPtg
return buffer.toString();
}
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
-
public Object clone() {
return new UnaryPlusPtg();
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
index 3b671e22fd..4abc33b868 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
@@ -23,8 +23,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
/**
* @author Glen Stampoultzis (glens at apache.org)
*/
-public class UnionPtg extends OperationPtg
-{
+public final class UnionPtg extends OperationPtg {
public final static byte sid = 0x10;
@@ -37,6 +36,9 @@ public class UnionPtg extends OperationPtg
// doesn't need anything
}
+ public final boolean isBaseToken() {
+ return true;
+ }
public int getSize()
{
diff --git a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
index af5ebc8441..07749022ed 100644
--- a/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
+++ b/src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
@@ -24,10 +24,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
* @author andy
* @author Jason Height (jheight at chariot dot net dot au)
*/
-
-public class UnknownPtg
- extends Ptg
-{
+public class UnknownPtg extends Ptg {
private short size = 1;
/** Creates new UnknownPtg */
@@ -36,12 +33,13 @@ public class UnknownPtg
{
}
- public UnknownPtg(RecordInputStream in)
- {
-
+ public UnknownPtg(RecordInputStream in) {
// doesn't need anything
}
+ public boolean isBaseToken() {
+ return true;
+ }
public void writeBytes(byte [] array, int offset)
{
}
diff --git a/src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.java b/src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.java
new file mode 100644
index 0000000000..4ef6ab595d
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.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.hssf.record.formula;
+
+/**
+ * Common superclass of all value operators.
+ * Subclasses include all unary and binary operators except for the reference operators (IntersectionPtg, RangePtg, UnionPtg)
+ *
+ * @author Josh Micich
+ */
+public abstract class ValueOperatorPtg extends OperationPtg {
+
+ /**
+ * All Operator Ptgs are base tokens (i.e. are not RVA classifed)
+ */
+ public final boolean isBaseToken() {
+ return true;
+ }
+ public final byte getDefaultOperandClass() {
+ return Ptg.CLASS_VALUE;
+ }
+}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
index b1c5c66e08..697c33b9e2 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
@@ -55,36 +55,72 @@ public class HSSFObjectData
this.record = record;
this.poifs = poifs;
}
+
+ /**
+ * Returns the OLE2 Class Name of the object
+ */
+ public String getOLE2ClassName() {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+ return subRecord.field_5_ole_classname;
+ }
/**
- * Gets the object data.
+ * Gets the object data. Only call for ones that have
+ * data though. See {@link #hasDirectoryEntry()}
*
* @return the object data as an OLE2 directory.
* @throws IOException if there was an error reading the data.
*/
- public DirectoryEntry getDirectory() throws IOException
- {
- Iterator subRecordIter = record.getSubRecords().iterator();
- while (subRecordIter.hasNext())
- {
- Object subRecord = subRecordIter.next();
- if (subRecord instanceof EmbeddedObjectRefSubRecord)
- {
- int streamId = ((EmbeddedObjectRefSubRecord) subRecord).getStreamId();
- String streamName = "MBD" + HexDump.toHex(streamId);
+ public DirectoryEntry getDirectory() throws IOException {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
- Entry entry = poifs.getRoot().getEntry(streamName);
- if (entry instanceof DirectoryEntry)
- {
- return (DirectoryEntry) entry;
- }
- else
- {
- throw new IOException("Stream " + streamName + " was not an OLE2 directory");
- }
+ int streamId = ((EmbeddedObjectRefSubRecord) subRecord).getStreamId();
+ String streamName = "MBD" + HexDump.toHex(streamId);
+
+ Entry entry = poifs.getRoot().getEntry(streamName);
+ if (entry instanceof DirectoryEntry) {
+ return (DirectoryEntry) entry;
+ } else {
+ throw new IOException("Stream " + streamName + " was not an OLE2 directory");
+ }
+ }
+
+ /**
+ * Returns the data portion, for an ObjectData
+ * that doesn't have an associated POIFS Directory
+ * Entry
+ */
+ public byte[] getObjectData() {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+ return subRecord.remainingBytes;
+ }
+
+ /**
+ * Does this ObjectData have an associated POIFS
+ * Directory Entry?
+ * (Not all do, those that don't have a data portion)
+ */
+ public boolean hasDirectoryEntry() {
+ EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+
+ // Field 6 tells you
+ return (subRecord.field_6_stream_id != 0);
+ }
+
+ /**
+ * Finds the EmbeddedObjectRefSubRecord, or throws an
+ * Exception if there wasn't one
+ */
+ protected EmbeddedObjectRefSubRecord findObjectRecord() {
+ Iterator subRecordIter = record.getSubRecords().iterator();
+
+ while (subRecordIter.hasNext()) {
+ Object subRecord = subRecordIter.next();
+ if (subRecord instanceof EmbeddedObjectRefSubRecord) {
+ return (EmbeddedObjectRefSubRecord)subRecord;
}
}
-
+
throw new IllegalStateException("Object data does not contain a reference to an embedded object OLE2 directory");
}
}
diff --git a/src/java/org/apache/poi/ss/usermodel/DateUtil.java b/src/java/org/apache/poi/ss/usermodel/DateUtil.java
index 0a9bdcfe88..0215af0126 100644
--- a/src/java/org/apache/poi/ss/usermodel/DateUtil.java
+++ b/src/java/org/apache/poi/ss/usermodel/DateUtil.java
@@ -220,9 +220,13 @@ public class DateUtil
// switching stuff, which we can ignore
fs = fs.replaceAll(";@", "");
- // If it starts with [$-...], then it is a date, but
+ // If it starts with [$-...], then could be a date, but
// who knows what that starting bit is all about
- fs = fs.replaceAll("\\[\\$\\-.*?\\]", "");
+ fs = fs.replaceAll("^\\[\\$\\-.*?\\]", "");
+
+ // If it starts with something like [Black] or [Yellow],
+ // then it could be a date
+ fs = fs.replaceAll("^\\[[a-zA-Z]+\\]", "");
// Otherwise, check it's only made up, in any case, of:
// y m d h s - / , . :
diff --git a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
index 36916e9c81..0ecde63049 100644
--- a/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
+++ b/src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
@@ -330,31 +330,27 @@ public class FormulaEvaluator {
}
private static ValueEval evaluateCell(Workbook workbook, Sheet sheet,
int srcRowNum, short srcColNum, String cellFormulaText) {
-
- FormulaParser parser =
- new FormulaParser(cellFormulaText, workbook);
-
- parser.parse();
- Ptg[] ptgs = parser.getRPNPtg();
- // -- parsing over --
-
+ Ptg[] ptgs = FormulaParser.parse(cellFormulaText, workbook);
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) { continue; }
+ if (ptg instanceof ControlPtg) {
+ // skip Parentheses, Attr, etc
+ continue;
+ }
if (ptg instanceof MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) { continue; }
if (ptg instanceof NamePtg) {
- // named ranges, macro functions
+ // named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg;
stack.push(new NameEval(namePtg.getIndex()));
continue;
}
if (ptg instanceof NameXPtg) {
- // TODO - external functions
+ // TODO - external functions
continue;
}
if (ptg instanceof UnknownPtg) { continue; }
@@ -362,9 +358,6 @@ public class FormulaEvaluator {
if (ptg instanceof OperationPtg) {
OperationPtg optg = (OperationPtg) ptg;
- // parens can be ignored since we have RPN tokens
- if (optg instanceof ParenthesisPtg) { continue; }
- if (optg instanceof AttrPtg) { continue; }
if (optg instanceof UnionPtg) { continue; }
OperationEval operation = OperationEvaluatorFactory.create(optg);
diff --git a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
index 8a85f42841..31694d5d76 100644
--- a/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
+++ b/src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
@@ -15,6 +15,7 @@
# Created by (org.apache.poi.hssf.record.formula.function.ExcelFileFormatDocFunctionExtractor)
# from source file 'excelfileformat.odt' (size=356107, md5=0x8f789cb6e75594caf068f8e193004ef4)
+# ! + some manual edits !
#
#Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote )
@@ -78,8 +79,8 @@
58 NPER 3 5 V V V V V V
59 PMT 3 5 V V V V V V
60 RATE 3 6 V V V V V V V
-61 MIRR 3 3 V R V V
-62 IRR 1 2 V R V
+61 MIRR 3 3 V A V V
+62 IRR 1 2 V A V
63 RAND 0 0 V - x
64 MATCH 2 3 V V R R
65 DATE 3 3 V V V V
@@ -93,8 +94,8 @@
73 SECOND 1 1 V V
74 NOW 0 0 V - x
75 AREAS 1 1 V R
-76 ROWS 1 1 V R
-77 COLUMNS 1 1 V R
+76 ROWS 1 1 V A
+77 COLUMNS 1 1 V A
78 OFFSET 3 5 R R V V V V x
82 SEARCH 2 3 V V V V
83 TRANSPOSE 1 1 A A
diff --git a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
index 5b48501938..e3f5bb23e9 100644
--- a/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
+++ b/src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
@@ -213,6 +213,23 @@ public class HSSFChart
charts.toArray( new HSSFChart[charts.size()] );
}
+ /** Get the X offset of the chart */
+ public int getChartX() { return chartRecord.getX(); }
+ /** Get the Y offset of the chart */
+ public int getChartY() { return chartRecord.getY(); }
+ /** Get the width of the chart. {@link ChartRecord} */
+ public int getChartWidth() { return chartRecord.getWidth(); }
+ /** Get the height of the chart. {@link ChartRecord} */
+ public int getChartHeight() { return chartRecord.getHeight(); }
+
+ /** Sets the X offset of the chart */
+ public void setChartX(int x) { chartRecord.setX(x); }
+ /** Sets the Y offset of the chart */
+ public void setChartY(int y) { chartRecord.setY(y); }
+ /** Sets the width of the chart. {@link ChartRecord} */
+ public void setChartWidth(int width) { chartRecord.setWidth(width); }
+ /** Sets the height of the chart. {@link ChartRecord} */
+ public void setChartHeight(int height) { chartRecord.setHeight(height); }
/**
* Returns the series of the chart
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
index 184d46d2fe..d28b8a8777 100644
--- a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
+++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
@@ -53,6 +53,12 @@ public class TestHSSFChart extends TestCase {
assertEquals("1st Column", charts[0].getSeries()[0].getSeriesTitle());
assertEquals("2nd Column", charts[0].getSeries()[1].getSeriesTitle());
assertEquals(null, charts[0].getChartTitle());
+
+ // Check x, y, width, height
+ assertEquals(0, charts[0].getChartX());
+ assertEquals(0, charts[0].getChartY());
+ assertEquals(26492928, charts[0].getChartWidth());
+ assertEquals(15040512, charts[0].getChartHeight());
}
public void testTwoCharts() throws Exception {
diff --git a/src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls b/src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls
new file mode 100644
index 0000000000000000000000000000000000000000..3c49fc257214d0bfb88fe570aefa5e7cc4a0c28e
GIT binary patch
literal 16384
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
literal 0
HcmV?d00001
diff --git a/src/testcases/org/apache/poi/hssf/data/testRVA.xls b/src/testcases/org/apache/poi/hssf/data/testRVA.xls
new file mode 100644
index 0000000000000000000000000000000000000000..327edbb4cbf11e7ed0a2e6bf2cafdaa54a041198
GIT binary patch
literal 32768
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
literal 0
HcmV?d00001
diff --git a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
index 19ef437063..045e371a22 100755
--- a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
+++ b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
@@ -33,6 +33,9 @@ public final class AllModelTests {
result.addTestSuite(TestDrawingManager2.class);
result.addTestSuite(TestFormulaParser.class);
result.addTestSuite(TestFormulaParserEval.class);
+ result.addTestSuite(TestFormulaParserIf.class);
+ result.addTestSuite(TestOperandClassTransformer.class);
+ result.addTestSuite(TestRVA.class);
result.addTestSuite(TestSheet.class);
result.addTestSuite(TestSheetAdditional.class);
return result;
diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
index f2821140f3..929279a3ce 100644
--- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
+++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
@@ -33,12 +33,9 @@ import org.apache.poi.hssf.record.formula.ErrPtg;
import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
-import org.apache.poi.hssf.record.formula.LessEqualPtg;
-import org.apache.poi.hssf.record.formula.LessThanPtg;
import org.apache.poi.hssf.record.formula.MissingArgPtg;
import org.apache.poi.hssf.record.formula.MultiplyPtg;
import org.apache.poi.hssf.record.formula.NamePtg;
-import org.apache.poi.hssf.record.formula.NotEqualPtg;
import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.PercentPtg;
import org.apache.poi.hssf.record.formula.PowerPtg;
@@ -62,10 +59,8 @@ public final class TestFormulaParser extends TestCase {
/**
* @return parsed token array already confirmed not null
*/
- private static Ptg[] parseFormula(String s) {
- FormulaParser fp = new FormulaParser(s, null);
- fp.parse();
- Ptg[] result = fp.getRPNPtg();
+ /* package */ static Ptg[] parseFormula(String formula) {
+ Ptg[] result = FormulaParser.parse(formula, null);
assertNotNull("Ptg array should not be null", result);
return result;
}
@@ -105,83 +100,6 @@ public final class TestFormulaParser extends TestCase {
assertEquals(true, flag.getValue());
}
- public void testYN() {
- Ptg[] ptgs = parseFormula("IF(TRUE,\"Y\",\"N\")");
- assertEquals(7, ptgs.length);
-
- BoolPtg flag = (BoolPtg) ptgs[0];
- AttrPtg funif = (AttrPtg) ptgs[1];
- StringPtg y = (StringPtg) ptgs[2];
- AttrPtg goto1 = (AttrPtg) ptgs[3];
- StringPtg n = (StringPtg) ptgs[4];
-
-
- assertEquals(true, flag.getValue());
- assertEquals("Y", y.getValue());
- assertEquals("N", n.getValue());
- assertEquals("IF", funif.toFormulaString((HSSFWorkbook) null));
- assertTrue("Goto ptg exists", goto1.isGoto());
- }
-
- public void testSimpleIf() {
- String formula = "IF(1=1,0,1)";
-
- Class[] expectedClasses = {
- IntPtg.class,
- IntPtg.class,
- EqualPtg.class,
- AttrPtg.class,
- IntPtg.class,
- AttrPtg.class,
- IntPtg.class,
- AttrPtg.class,
- FuncVarPtg.class,
- };
- confirmTokenClasses(formula, expectedClasses);
-
- Ptg[] ptgs = parseFormula(formula);
-
- AttrPtg ifPtg = (AttrPtg) ptgs[3];
- AttrPtg ptgGoto= (AttrPtg) ptgs[5];
- assertEquals("Goto 1 Length", 10, ptgGoto.getData());
-
- AttrPtg ptgGoto2 = (AttrPtg) ptgs[7];
- assertEquals("Goto 2 Length", 3, ptgGoto2.getData());
- assertEquals("If FALSE offset", 7, ifPtg.getData());
- }
-
- /**
- * Make sure the ptgs are generated properly with two functions embedded
- *
- */
- public void testNestedFunctionIf() {
- Ptg[] ptgs = parseFormula("IF(A1=B1,AVERAGE(A1:B1),AVERAGE(A2:B2))");
- assertEquals(11, ptgs.length);
-
- assertTrue("IF Attr set correctly", (ptgs[3] instanceof AttrPtg));
- AttrPtg ifFunc = (AttrPtg)ptgs[3];
- assertTrue("It is not an if", ifFunc.isOptimizedIf());
-
- assertTrue("Average Function set correctly", (ptgs[5] instanceof FuncVarPtg));
- }
-
- public void testIfSingleCondition(){
- Ptg[] ptgs = parseFormula("IF(1=1,10)");
- assertEquals(7, ptgs.length);
-
- assertTrue("IF Attr set correctly", (ptgs[3] instanceof AttrPtg));
- AttrPtg ifFunc = (AttrPtg)ptgs[3];
- assertTrue("It is not an if", ifFunc.isOptimizedIf());
-
- assertTrue("Single Value is not an IntPtg", (ptgs[4] instanceof IntPtg));
- IntPtg intPtg = (IntPtg)ptgs[4];
- assertEquals("Result", (short)10, intPtg.getValue());
-
- assertTrue("Ptg is not a Variable Function", (ptgs[6] instanceof FuncVarPtg));
- FuncVarPtg funcPtg = (FuncVarPtg)ptgs[6];
- assertEquals("Arguments", 2, funcPtg.getNumberOfOperands());
- }
-
public void testSumIf() {
Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)");
assertEquals(4, ptgs.length);
@@ -203,33 +121,9 @@ public final class TestFormulaParser extends TestCase {
//the PTG order isn't 100% correct but it still works - dmui
}
- public void testSimpleLogical() {
- Ptg[] ptgs = parseFormula("IF(A1=1,\"*\",IF(4<>1,\"first\",\"second\"))");
- assertEquals(17, ptgs.length);
-
- assertEquals("6th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[5].getClass());
- assertEquals("9th Ptg is not a not equal ptg",NotEqualPtg.class,ptgs[8].getClass());
- assertEquals("15th Ptg is not the inner IF variable function ptg",FuncVarPtg.class,ptgs[14].getClass());
- }
-
public void testMacroFunction() {
HSSFWorkbook w = new HSSFWorkbook();
- FormulaParser fp = new FormulaParser("FOO()", w);
- fp.parse();
- Ptg[] ptg = fp.getRPNPtg();
+ Ptg[] ptg = FormulaParser.parse("FOO()", w);
// the name gets encoded as the first arg
NamePtg tname = (NamePtg) ptg[0];
@@ -597,7 +491,7 @@ public final class TestFormulaParser extends TestCase {
confirmTokenClasses("2^200%", expClss);
}
- private static void confirmTokenClasses(String formula, Class[] expectedClasses) {
+ /* package */ static Ptg[] confirmTokenClasses(String formula, Class[] expectedClasses) {
Ptg[] ptgs = parseFormula(formula);
assertEquals(expectedClasses.length, ptgs.length);
for (int i = 0; i < expectedClasses.length; i++) {
@@ -607,6 +501,7 @@ public final class TestFormulaParser extends TestCase {
+ ptgs[i].getClass().getName() + ")");
}
}
+ return ptgs;
}
public void testPower() {
@@ -644,8 +539,16 @@ public final class TestFormulaParser extends TestCase {
Class[] expClss;
- expClss = new Class[] { ReferencePtg.class, MissingArgPtg.class, ReferencePtg.class,
- FuncVarPtg.class, };
+ expClss = new Class[] {
+ ReferencePtg.class,
+ AttrPtg.class, // tAttrIf
+ MissingArgPtg.class,
+ AttrPtg.class, // tAttrSkip
+ ReferencePtg.class,
+ AttrPtg.class, // tAttrSkip
+ FuncVarPtg.class,
+ };
+
confirmTokenClasses("if(A1, ,C1)", expClss);
expClss = new Class[] { MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class,
@@ -814,7 +717,7 @@ public final class TestFormulaParser extends TestCase {
fail("Expected exception was not thrown");
} catch (IllegalStateException e) {
// expected during successful test
- assertTrue(e.getMessage().startsWith("Too few arguments suppled to operation token"));
+ assertTrue(e.getMessage().startsWith("Too few arguments supplied to operation"));
}
}
/**
diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParserIf.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParserIf.java
new file mode 100644
index 0000000000..ba05c16211
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParserIf.java
@@ -0,0 +1,239 @@
+/* ====================================================================
+ 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 junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.AddPtg;
+import org.apache.poi.hssf.record.formula.AttrPtg;
+import org.apache.poi.hssf.record.formula.BoolPtg;
+import org.apache.poi.hssf.record.formula.FuncPtg;
+import org.apache.poi.hssf.record.formula.FuncVarPtg;
+import org.apache.poi.hssf.record.formula.IntPtg;
+import org.apache.poi.hssf.record.formula.LessEqualPtg;
+import org.apache.poi.hssf.record.formula.LessThanPtg;
+import org.apache.poi.hssf.record.formula.MultiplyPtg;
+import org.apache.poi.hssf.record.formula.NotEqualPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.record.formula.StringPtg;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Tests FormulaParser specifically with respect to IF() functions
+ */
+public final class TestFormulaParserIf extends TestCase {
+ private static Ptg[] parseFormula(String formula) {
+ return TestFormulaParser.parseFormula(formula);
+ }
+
+ private static Ptg[] confirmTokenClasses(String formula, Class[] expectedClasses) {
+ return TestFormulaParser.confirmTokenClasses(formula, expectedClasses);
+ }
+
+ private static void confirmAttrData(Ptg[] ptgs, int i, int expectedData) {
+ Ptg ptg = ptgs[i];
+ if (!(ptg instanceof AttrPtg)) {
+ throw new AssertionFailedError("Token[" + i + "] was not AttrPtg as expected");
+ }
+ AttrPtg attrPtg = (AttrPtg) ptg;
+ assertEquals(expectedData, attrPtg.getData());
+ }
+
+ public void testSimpleIf() {
+
+ Class[] expClss;
+
+ expClss = new Class[] {
+ ReferencePtg.class,
+ AttrPtg.class, // tAttrIf
+ IntPtg.class,
+ AttrPtg.class, // tAttrSkip
+ IntPtg.class,
+ AttrPtg.class, // tAttrSkip
+ FuncVarPtg.class,
+ };
+
+ Ptg[] ptgs = confirmTokenClasses("if(A1,1,2)", expClss);
+
+ confirmAttrData(ptgs, 1, 7);
+ confirmAttrData(ptgs, 3, 10);
+ confirmAttrData(ptgs, 5, 3);
+ }
+
+ public void testSimpleIfNoFalseParam() {
+
+ Class[] expClss;
+
+ expClss = new Class[] {
+ ReferencePtg.class,
+ AttrPtg.class, // tAttrIf
+ ReferencePtg.class,
+ AttrPtg.class, // tAttrSkip
+ FuncVarPtg.class,
+ };
+
+ Ptg[] ptgs = confirmTokenClasses("if(A1,B1)", expClss);
+
+ confirmAttrData(ptgs, 1, 9);
+ confirmAttrData(ptgs, 3, 3);
+ }
+
+ public void testIfWithLargeParams() {
+
+ Class[] expClss;
+
+ expClss = new Class[] {
+ ReferencePtg.class,
+ AttrPtg.class, // tAttrIf
+
+ ReferencePtg.class,
+ IntPtg.class,
+ MultiplyPtg.class,
+ ReferencePtg.class,
+ IntPtg.class,
+ AddPtg.class,
+ FuncPtg.class,
+ AttrPtg.class, // tAttrSkip
+
+ ReferencePtg.class,
+ ReferencePtg.class,
+ FuncPtg.class,
+
+ AttrPtg.class, // tAttrSkip
+ FuncVarPtg.class,
+ };
+
+ Ptg[] ptgs = confirmTokenClasses("if(A1,round(B1*100,C1+2),round(B1,C1))", expClss);
+
+ confirmAttrData(ptgs, 1, 25);
+ confirmAttrData(ptgs, 9, 20);
+ confirmAttrData(ptgs, 13, 3);
+ }
+
+ public void testNestedIf() {
+
+ Class[] expClss;
+
+ expClss = new Class[] {
+
+ ReferencePtg.class,
+ AttrPtg.class, // A tAttrIf
+ ReferencePtg.class,
+ AttrPtg.class, // B tAttrIf
+ IntPtg.class,
+ AttrPtg.class, // B tAttrSkip
+ IntPtg.class,
+ AttrPtg.class, // B tAttrSkip
+ FuncVarPtg.class,
+ AttrPtg.class, // A tAttrSkip
+ ReferencePtg.class,
+ AttrPtg.class, // C tAttrIf
+ IntPtg.class,
+ AttrPtg.class, // C tAttrSkip
+ IntPtg.class,
+ AttrPtg.class, // C tAttrSkip
+ FuncVarPtg.class,
+ AttrPtg.class, // A tAttrSkip
+ FuncVarPtg.class,
+ };
+
+ Ptg[] ptgs = confirmTokenClasses("if(A1,if(B1,1,2),if(C1,3,4))", expClss);
+ confirmAttrData(ptgs, 1, 31);
+ confirmAttrData(ptgs, 3, 7);
+ confirmAttrData(ptgs, 5, 10);
+ confirmAttrData(ptgs, 7, 3);
+ confirmAttrData(ptgs, 9, 34);
+ confirmAttrData(ptgs, 11, 7);
+ confirmAttrData(ptgs, 13, 10);
+ confirmAttrData(ptgs, 15, 3);
+ confirmAttrData(ptgs, 17, 3);
+ }
+
+ public void testEmbeddedIf() {
+ Ptg[] ptgs = parseFormula("IF(3>=1,\"*\",IF(4<>1,\"first\",\"second\"))");
+ assertEquals(17, ptgs.length);
+
+ assertEquals("6th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[5].getClass());
+ assertEquals("9th Ptg is not a not equal ptg",NotEqualPtg.class,ptgs[8].getClass());
+ assertEquals("15th Ptg is not the inner IF variable function ptg",FuncVarPtg.class,ptgs[14].getClass());
+ }
+
+
+ public void testSimpleLogical() {
+ Ptg[] ptgs = parseFormula("IF(A1OperandClassTransformer.
+ *
+ * @author Josh Micich
+ */
+public final class TestOperandClassTransformer extends TestCase {
+
+ public void testMdeterm() {
+ String formula = "MDETERM(ABS(A1))";
+ Ptg[] ptgs = FormulaParser.parse(formula, null);
+
+ confirmTokenClass(ptgs, 0, Ptg.CLASS_ARRAY);
+ confirmFuncClass(ptgs, 1, "ABS", Ptg.CLASS_ARRAY);
+ confirmFuncClass(ptgs, 2, "MDETERM", Ptg.CLASS_VALUE);
+ }
+
+ /**
+ * In the example: INDEX(PI(),1)
, Excel encodes PI() as 'array'. It is not clear
+ * what rule justifies this. POI currently encodes it as 'value' which Excel(2007) seems to
+ * tolerate. Changing the metadata for INDEX to have first parameter as 'array' class breaks
+ * other formulas involving INDEX. It seems like a special case needs to be made. Perhaps an
+ * important observation is that INDEX is one of very few functions that returns 'reference' type.
+ *
+ * This test has been added but disabled in order to document this issue.
+ */
+ public void DISABLED_testIndexPi1() {
+ String formula = "INDEX(PI(),1)";
+ Ptg[] ptgs = FormulaParser.parse(formula, null);
+
+ confirmFuncClass(ptgs, 1, "PI", Ptg.CLASS_ARRAY); // fails as of POI 3.1
+ confirmFuncClass(ptgs, 2, "INDEX", Ptg.CLASS_VALUE);
+ }
+
+ public void testComplexIRR_bug45041() {
+ String formula = "(1+IRR(SUMIF(A:A,ROW(INDIRECT(MIN(A:A)&\":\"&MAX(A:A))),B:B),0))^365-1";
+ Ptg[] ptgs = FormulaParser.parse(formula, null);
+
+ FuncVarPtg rowFunc = (FuncVarPtg) ptgs[10];
+ FuncVarPtg sumifFunc = (FuncVarPtg) ptgs[12];
+ assertEquals("ROW", rowFunc.getName());
+ assertEquals("SUMIF", sumifFunc.getName());
+
+ if (rowFunc.getPtgClass() == Ptg.CLASS_VALUE || sumifFunc.getPtgClass() == Ptg.CLASS_VALUE) {
+ throw new AssertionFailedError("Identified bug 45041");
+ }
+ confirmTokenClass(ptgs, 1, Ptg.CLASS_REF);
+ confirmTokenClass(ptgs, 2, Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 3, "MIN", Ptg.CLASS_VALUE);
+ confirmTokenClass(ptgs, 6, Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 7, "MAX", Ptg.CLASS_VALUE);
+ confirmFuncClass(ptgs, 9, "INDIRECT", Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 10, "ROW", Ptg.CLASS_ARRAY);
+ confirmTokenClass(ptgs, 11, Ptg.CLASS_REF);
+ confirmFuncClass(ptgs, 12, "SUMIF", Ptg.CLASS_ARRAY);
+ confirmFuncClass(ptgs, 14, "IRR", Ptg.CLASS_VALUE);
+ }
+
+ private void confirmFuncClass(Ptg[] ptgs, int i, String expectedFunctionName, byte operandClass) {
+ confirmTokenClass(ptgs, i, operandClass);
+ AbstractFunctionPtg afp = (AbstractFunctionPtg) ptgs[i];
+ assertEquals(expectedFunctionName, afp.getName());
+ }
+
+ private void confirmTokenClass(Ptg[] ptgs, int i, byte operandClass) {
+ Ptg ptg = ptgs[i];
+ if (operandClass != ptg.getPtgClass()) {
+ throw new AssertionFailedError("Wrong operand class for function ptg ("
+ + ptg.toString() + "). Expected " + getOperandClassName(operandClass)
+ + " but got " + getOperandClassName(ptg.getPtgClass()));
+ }
+ }
+
+ private static String getOperandClassName(byte ptgClass) {
+ switch (ptgClass) {
+ case Ptg.CLASS_REF:
+ return "R";
+ case Ptg.CLASS_VALUE:
+ return "V";
+ case Ptg.CLASS_ARRAY:
+ return "A";
+ }
+ throw new RuntimeException("Unknown operand class (" + ptgClass + ")");
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/model/TestRVA.java b/src/testcases/org/apache/poi/hssf/model/TestRVA.java
new file mode 100644
index 0000000000..cb51c17bd7
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/model/TestRVA.java
@@ -0,0 +1,156 @@
+/* ====================================================================
+ 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 junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.record.formula.AttrPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.ReferencePtg;
+import org.apache.poi.hssf.usermodel.FormulaExtractor;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Tests 'operand class' transformation performed by
+ * OperandClassTransformer by comparing its results with those
+ * directly produced by Excel (in a sample spreadsheet).
+ *
+ * @author Josh Micich
+ */
+public final class TestRVA extends TestCase {
+
+ private static final String NEW_LINE = System.getProperty("line.separator");
+
+ public void testFormulas() {
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("testRVA.xls");
+ HSSFSheet sheet = wb.getSheetAt(0);
+
+ int countFailures = 0;
+ int countErrors = 0;
+
+ int rowIx = 0;
+ while (rowIx < 65535) {
+ HSSFRow row = sheet.getRow(rowIx);
+ if (row == null) {
+ break;
+ }
+ HSSFCell cell = row.getCell(0);
+ if (cell == null || cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
+ break;
+ }
+ String formula = cell.getCellFormula();
+ try {
+ confirmCell(cell, formula);
+ } catch (AssertionFailedError e) {
+ System.err.println("Problem with row[" + rowIx + "] formula '" + formula + "'");
+ System.err.println(e.getMessage());
+ countFailures++;
+ } catch (RuntimeException e) {
+ System.err.println("Problem with row[" + rowIx + "] formula '" + formula + "'");
+ countErrors++;
+ e.printStackTrace();
+ }
+ rowIx++;
+ }
+ if (countErrors + countFailures > 0) {
+ String msg = "One or more RVA tests failed: countFailures=" + countFailures
+ + " countFailures=" + countErrors + ". See stderr for details.";
+ throw new AssertionFailedError(msg);
+ }
+ }
+
+ private void confirmCell(HSSFCell formulaCell, String formula) {
+ Ptg[] excelPtgs = FormulaExtractor.getPtgs(formulaCell);
+ Ptg[] poiPtgs = FormulaParser.parse(formula, null);
+ int nExcelTokens = excelPtgs.length;
+ int nPoiTokens = poiPtgs.length;
+ if (nExcelTokens != nPoiTokens) {
+ if (nExcelTokens == nPoiTokens + 1 && excelPtgs[0].getClass() == AttrPtg.class) {
+ // compensate for missing tAttrVolatile, which belongs in any formula
+ // involving OFFSET() et al. POI currently does not insert where required
+ Ptg[] temp = new Ptg[nExcelTokens];
+ temp[0] = excelPtgs[0];
+ System.arraycopy(poiPtgs, 0, temp, 1, nPoiTokens);
+ poiPtgs = temp;
+ } else {
+ throw new RuntimeException("Expected " + nExcelTokens + " tokens but got "
+ + nPoiTokens);
+ }
+ }
+ boolean hasMismatch = false;
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < nExcelTokens; i++) {
+ Ptg poiPtg = poiPtgs[i];
+ Ptg excelPtg = excelPtgs[i];
+ if (!areTokenClassesSame(poiPtg, excelPtg)) {
+ hasMismatch = true;
+ sb.append(" mismatch token type[" + i + "] " + getShortClassName(excelPtg) + " "
+ + getOperandClassName(excelPtg) + " - " + getShortClassName(poiPtg) + " "
+ + getOperandClassName(poiPtg));
+ sb.append(NEW_LINE);
+ continue;
+ }
+ if (poiPtg.isBaseToken()) {
+ continue;
+ }
+ sb.append(" token[" + i + "] " + excelPtg.toString() + " "
+ + getOperandClassName(excelPtg));
+
+ if (excelPtg.getPtgClass() != poiPtg.getPtgClass()) {
+ hasMismatch = true;
+ sb.append(" - was " + getOperandClassName(poiPtg));
+ }
+ sb.append(NEW_LINE);
+ }
+ if (hasMismatch) {
+ throw new AssertionFailedError(sb.toString());
+ }
+ }
+
+ private boolean areTokenClassesSame(Ptg poiPtg, Ptg excelPtg) {
+ if (excelPtg.getClass() == poiPtg.getClass()) {
+ return true;
+ }
+ if (poiPtg.getClass() == ReferencePtg.class) {
+ // TODO - remove funny subclasses of ReferencePtg
+ return excelPtg instanceof ReferencePtg;
+ }
+ return false;
+ }
+
+ private String getShortClassName(Object o) {
+ String cn = o.getClass().getName();
+ int pos = cn.lastIndexOf('.');
+ return cn.substring(pos + 1);
+ }
+
+ private static String getOperandClassName(Ptg ptg) {
+ byte ptgClass = ptg.getPtgClass();
+ switch (ptgClass) {
+ case Ptg.CLASS_REF: return "R";
+ case Ptg.CLASS_VALUE: return "V";
+ case Ptg.CLASS_ARRAY: return "A";
+ }
+ throw new RuntimeException("Unknown operand class (" + ptgClass + ")");
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java b/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
index 16f80bb791..39464b5e03 100644
--- a/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
+++ b/src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
@@ -19,8 +19,10 @@ package org.apache.poi.hssf.record.formula;
import java.util.Arrays;
+import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.TestcaseRecordInputStream;
import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
@@ -77,7 +79,7 @@ public final class TestArrayPtg extends TestCase {
}
/**
- * make sure constant elements are stored row by row
+ * Excel stores array elements column by column. This test makes sure POI does the same.
*/
public void testElementOrdering() {
ArrayPtg ptg = new ArrayPtgV(new TestcaseRecordInputStream(ArrayPtgV.sid, ENCODED_PTG_DATA));
@@ -86,10 +88,27 @@ public final class TestArrayPtg extends TestCase {
assertEquals(2, ptg.getRowCount());
assertEquals(0, ptg.getValueIndex(0, 0));
- assertEquals(1, ptg.getValueIndex(1, 0));
- assertEquals(2, ptg.getValueIndex(2, 0));
- assertEquals(3, ptg.getValueIndex(0, 1));
- assertEquals(4, ptg.getValueIndex(1, 1));
+ assertEquals(2, ptg.getValueIndex(1, 0));
+ assertEquals(4, ptg.getValueIndex(2, 0));
+ assertEquals(1, ptg.getValueIndex(0, 1));
+ assertEquals(3, ptg.getValueIndex(1, 1));
assertEquals(5, ptg.getValueIndex(2, 1));
}
+
+ /**
+ * Test for a bug which was temporarily introduced by the fix for bug 42564.
+ * A spreadsheet was added to make the ordering clearer.
+ */
+ public void testElementOrderingInSpreadsheet() {
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("ex42564-elementOrder.xls");
+
+ // The formula has an array with 3 rows and 5 column
+ String formula = wb.getSheetAt(0).getRow(0).getCell((short)0).getCellFormula();
+ // TODO - These number literals should not have '.0'. Excel has different number rendering rules
+
+ if (formula.equals("SUM({1.0,6.0,11.0;2.0,7.0,12.0;3.0,8.0,13.0;4.0,9.0,14.0;5.0,10.0,15.0})")) {
+ throw new AssertionFailedError("Identified bug 42564 b");
+ }
+ assertEquals("SUM({1.0,2.0,3.0;4.0,5.0,6.0;7.0,8.0,9.0;10.0,11.0,12.0;13.0,14.0,15.0})", formula);
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java b/src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java
new file mode 100644
index 0000000000..d657647eae
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java
@@ -0,0 +1,49 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import java.util.List;
+
+import org.apache.poi.hssf.record.CellValueRecordInterface;
+import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
+import org.apache.poi.hssf.record.formula.Ptg;
+
+/**
+ * Test utility class to get Ptg arrays out of formula cells
+ *
+ * @author Josh Micich
+ */
+public final class FormulaExtractor {
+
+ private FormulaExtractor() {
+ // no instances of this class
+ }
+
+ public static Ptg[] getPtgs(HSSFCell cell) {
+ CellValueRecordInterface vr = cell.getCellValueRecord();
+ if (!(vr instanceof FormulaRecordAggregate)) {
+ throw new IllegalArgumentException("Not a formula cell");
+ }
+ FormulaRecordAggregate fra = (FormulaRecordAggregate) vr;
+ List tokens = fra.getFormulaRecord().getParsedExpression();
+ Ptg[] result = new Ptg[tokens.size()];
+ tokens.toArray(result);
+ return result;
+ }
+
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
index 0e0a656341..419bc33bb4 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
@@ -18,9 +18,11 @@
package org.apache.poi.hssf.usermodel;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
+import java.util.List;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
@@ -28,6 +30,7 @@ import junit.framework.TestCase;
import org.apache.poi.ss.util.Region;
import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord;
import org.apache.poi.util.TempFile;
/**
@@ -951,4 +954,40 @@ public final class TestBugs extends TestCase {
writeOutAndReadBack(wb);
assertTrue("no errors writing sample xls", true);
}
+
+ /**
+ * Problems with extracting check boxes from
+ * HSSFObjectData
+ * @throws Exception
+ */
+ public void test44840() throws Exception {
+ HSSFWorkbook wb = openSample("WithCheckBoxes.xls");
+
+ // Take a look at the embeded objects
+ List objects = wb.getAllEmbeddedObjects();
+ assertEquals(1, objects.size());
+
+ HSSFObjectData obj = (HSSFObjectData)objects.get(0);
+ assertNotNull(obj);
+
+ // Peek inside the underlying record
+ EmbeddedObjectRefSubRecord rec = obj.findObjectRecord();
+ assertNotNull(rec);
+
+ assertEquals(32, rec.field_1_stream_id_offset);
+ assertEquals(0, rec.field_6_stream_id); // WRONG!
+ assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname);
+ assertEquals(12, rec.remainingBytes.length);
+
+ // Doesn't have a directory
+ assertFalse(obj.hasDirectoryEntry());
+ assertNotNull(obj.getObjectData());
+ assertEquals(12, obj.getObjectData().length);
+ assertEquals("Forms.CheckBox.1", obj.getOLE2ClassName());
+
+ try {
+ obj.getDirectory();
+ fail();
+ } catch(FileNotFoundException e) {}
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
index 76c098da2d..4f526b61c0 100644
--- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java
@@ -257,9 +257,15 @@ public class TestHSSFDateUtil extends TestCase {
// (who knows what they mean though...)
"[$-F800]dddd\\,\\ mmm\\ dd\\,\\ yyyy",
"[$-F900]ddd/mm/yyy",
+ // These ones specify colours, who knew that was allowed?
+ "[BLACK]dddd/mm/yy",
+ "[yeLLow]yyyy-mm-dd"
};
for(int i=0; i