diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 1381dd8d28..5d7dce206f 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -46,6 +46,9 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 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/documentation/content/xdocs/index.xml b/src/documentation/content/xdocs/index.xml index 09fb0709f8..b881cc0a2a 100644 --- a/src/documentation/content/xdocs/index.xml +++ b/src/documentation/content/xdocs/index.xml @@ -40,9 +40,9 @@ People interested should follow the dev list to track progress.

-
POI 3.1-BETA1 Released (2008-04028) +
POI 3.1-BETA2 Released (2008-05-28)

- The POI team is pleased to announce the release of 3.1 BETA1 which is one of the final steps before 3.1 FINAL. + The POI team is pleased to announce the release of 3.1 BETA2 which is one of the final steps before 3.1 FINAL. The status of this release is a beta, meaning that we encourage users to try it out. If you find any bugs, please report them to the POI bug database or to the POI Developer List. @@ -54,7 +54,7 @@

The release is also available from the central Maven repository - under Group ID "org.apache.poi" and Version "3.1-beta1". + under Group ID "org.apache.poi" and Version "3.1-beta2".

POI 3.0.2 Released diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 35e3ab7514..68028d539d 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -43,6 +43,9 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 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 0000000000..3c49fc2572 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls differ 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 0000000000..327edbb4cb Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/testRVA.xls differ 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