]> source.dussan.org Git - poi.git/commitdiff
Merged revisions 638786-638802,638805-638811,638813-638814,638816-639230,639233-63924...
authorNick Burch <nick@apache.org>
Wed, 28 May 2008 11:06:34 +0000 (11:06 +0000)
committerNick Burch <nick@apache.org>
Wed, 28 May 2008 11:06:34 +0000 (11:06 +0000)
https://svn.apache.org/repos/asf/poi/trunk

........
  r659575 | nick | 2008-05-23 16:55:08 +0100 (Fri, 23 May 2008) | 1 line

  Help for bug #44840 - Improved handling of HSSFObjectData, especially for entries with data held not in POIFS
........
  r660256 | josh | 2008-05-26 19:02:23 +0100 (Mon, 26 May 2008) | 1 line

  Follow-on fix for bug 42564 (r653668). Array elements are stored internally column by column.
........
  r660263 | josh | 2008-05-26 19:25:02 +0100 (Mon, 26 May 2008) | 1 line

  Small fix for FormulaParser. Need case-insentive match for IF function name
........
  r660280 | josh | 2008-05-26 20:36:56 +0100 (Mon, 26 May 2008) | 1 line

  Added test cases for parsing IF expressions.  Segregated IF test cases into a new class
........
  r660344 | josh | 2008-05-27 01:57:23 +0100 (Tue, 27 May 2008) | 1 line

  Changed class hierarchy of Ptg to improve 'operand class' transformation.
........
  r660474 | nick | 2008-05-27 12:44:49 +0100 (Tue, 27 May 2008) | 1 line

  X, Y, Width and Height getters/setters on HSSFChart
........
  r660828 | josh | 2008-05-28 07:19:31 +0100 (Wed, 28 May 2008) | 1 line

  Fix for 45060 (and 45041) - Improved token class transformation during formula parsing
........
  r660834 | yegor | 2008-05-28 07:50:35 +0100 (Wed, 28 May 2008) | 1 line

  bump 3.1-beta2 announcement
........
  r660889 | nick | 2008-05-28 11:03:00 +0100 (Wed, 28 May 2008) | 1 line

  Fix bug #45087 - Correctly detect date formats like [Black]YYYY as being date based
........

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@660905 13f79535-47bb-0310-9956-ffa450edef68

70 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/index.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/FormulaParser.java
src/java/org/apache/poi/hssf/model/OperandClassTransformer.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/model/ParseNode.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java
src/java/org/apache/poi/hssf/record/formula/AddPtg.java
src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
src/java/org/apache/poi/hssf/record/formula/AreaPtg.java
src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java
src/java/org/apache/poi/hssf/record/formula/AttrPtg.java
src/java/org/apache/poi/hssf/record/formula/BoolPtg.java
src/java/org/apache/poi/hssf/record/formula/ConcatPtg.java
src/java/org/apache/poi/hssf/record/formula/ControlPtg.java
src/java/org/apache/poi/hssf/record/formula/DividePtg.java
src/java/org/apache/poi/hssf/record/formula/EqualPtg.java
src/java/org/apache/poi/hssf/record/formula/ErrPtg.java
src/java/org/apache/poi/hssf/record/formula/ExpPtg.java
src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
src/java/org/apache/poi/hssf/record/formula/FuncVarPtg.java
src/java/org/apache/poi/hssf/record/formula/GreaterEqualPtg.java
src/java/org/apache/poi/hssf/record/formula/GreaterThanPtg.java
src/java/org/apache/poi/hssf/record/formula/IntPtg.java
src/java/org/apache/poi/hssf/record/formula/IntersectionPtg.java
src/java/org/apache/poi/hssf/record/formula/LessEqualPtg.java
src/java/org/apache/poi/hssf/record/formula/LessThanPtg.java
src/java/org/apache/poi/hssf/record/formula/MemAreaPtg.java
src/java/org/apache/poi/hssf/record/formula/MemFuncPtg.java
src/java/org/apache/poi/hssf/record/formula/MissingArgPtg.java
src/java/org/apache/poi/hssf/record/formula/MultiplyPtg.java
src/java/org/apache/poi/hssf/record/formula/NamePtg.java
src/java/org/apache/poi/hssf/record/formula/NameXPtg.java
src/java/org/apache/poi/hssf/record/formula/NotEqualPtg.java
src/java/org/apache/poi/hssf/record/formula/NumberPtg.java
src/java/org/apache/poi/hssf/record/formula/OperandPtg.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/formula/ParenthesisPtg.java
src/java/org/apache/poi/hssf/record/formula/PercentPtg.java
src/java/org/apache/poi/hssf/record/formula/PowerPtg.java
src/java/org/apache/poi/hssf/record/formula/Ptg.java
src/java/org/apache/poi/hssf/record/formula/RangePtg.java
src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java
src/java/org/apache/poi/hssf/record/formula/RefErrorPtg.java
src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java
src/java/org/apache/poi/hssf/record/formula/ScalarConstantPtg.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/formula/StringPtg.java
src/java/org/apache/poi/hssf/record/formula/SubtractPtg.java
src/java/org/apache/poi/hssf/record/formula/UnaryMinusPtg.java
src/java/org/apache/poi/hssf/record/formula/UnaryPlusPtg.java
src/java/org/apache/poi/hssf/record/formula/UnionPtg.java
src/java/org/apache/poi/hssf/record/formula/UnknownPtg.java
src/java/org/apache/poi/hssf/record/formula/ValueOperatorPtg.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
src/java/org/apache/poi/ss/usermodel/DateUtil.java
src/java/org/apache/poi/ss/usermodel/FormulaEvaluator.java
src/resources/main/org/apache/poi/hssf/record/formula/function/functionMetadata.txt
src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFChart.java
src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestHSSFChart.java
src/testcases/org/apache/poi/hssf/data/ex42564-elementOrder.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/data/testRVA.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/model/AllModelTests.java
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
src/testcases/org/apache/poi/hssf/model/TestFormulaParserIf.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/model/TestRVA.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java
src/testcases/org/apache/poi/hssf/usermodel/FormulaExtractor.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java

index 1381dd8d28f1094e9cd62e98e93f5fa1dc16eda3..5d7dce206f3f74521d8f43971be26f25bf9cb07e 100644 (file)
@@ -46,6 +46,9 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </release>
         <release version="3.1-final" date="2008-06-??">
+           <action dev="POI-DEVELOPERS" type="fix">45087 - Correctly detect date formats like [Black]YYYY as being date based</action>
+           <action dev="POI-DEVELOPERS" type="add">45060 - Improved token class transformation during formula parsing</action>
+           <action dev="POI-DEVELOPERS" type="add">44840 - Improved handling of HSSFObjectData, especially for entries with data held not in POIFS</action>
            <action dev="POI-DEVELOPERS" type="add">45043 - Support for getting excel cell comments when extracting text</action>
            <action dev="POI-DEVELOPERS" type="add">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</action>
            <action dev="POI-DEVELOPERS" type="fix">45025 - improved FormulaParser parse error messages</action>
index 09fb0709f8aa6500324b18919f00146d3b1bb066..b881cc0a2a1c9218cbafaaea35c2a23069c56cd9 100644 (file)
@@ -40,9 +40,9 @@
        People interested should follow the
        <link href="mailinglists.html">dev list</link> to track progress.</p>
     </section>
-    <section><title>POI 3.1-BETA1 Released (2008-04028)</title>
+    <section><title>POI 3.1-BETA2 Released (2008-05-28)</title>
       <p>
-        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 <link href="https://issues.apache.org/bugzilla/buglist.cgi?product=POI">bug database</link> or to
         the <link href="./mailinglists.html">POI Developer List</link>.
@@ -54,7 +54,7 @@
       </p>
       <p>
         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".
       </p>  
     </section>
     <section><title>POI 3.0.2 Released</title>
index 35e3ab751494e4638e8102b8d960b9bc6b0eb6c6..68028d539dfd79010da219294822daacac830e4d 100644 (file)
@@ -43,6 +43,9 @@
            <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action>
         </release>
         <release version="3.1-final" date="2008-06-??">
+           <action dev="POI-DEVELOPERS" type="fix">45087 - Correctly detect date formats like [Black]YYYY as being date based</action>
+           <action dev="POI-DEVELOPERS" type="add">45060 - Improved token class transformation during formula parsing</action>
+           <action dev="POI-DEVELOPERS" type="add">44840 - Improved handling of HSSFObjectData, especially for entries with data held not in POIFS</action>
            <action dev="POI-DEVELOPERS" type="add">45043 - Support for getting excel cell comments when extracting text</action>
            <action dev="POI-DEVELOPERS" type="add">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</action>
            <action dev="POI-DEVELOPERS" type="fix">45025 - improved FormulaParser parse error messages</action>
index 085a140ad9af226ee44d7b6694447b6ffb2505ac..d45e8741ba101112061396f2bac5b0e1f41060c2 100644 (file)
@@ -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);
-    }
-
-    /**
-     * Adds the size of all the ptgs after the provided index (inclusive).
-     * <p>
-     * 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;
+        return getFunction(name, nameToken, args);
     }
 
-    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.
      * <p>
@@ -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<numOperands;i++) {
-                setParameterRVA(n.getChild(i),((AbstractFunctionPtg)p).getParameterClass(i),formulaType);
-//                if (n.getChild(i).getValue() instanceof AbstractFunctionPtg) {
-//                    setParameterRVA(n.getChild(i),formulaType);
-//                }
-                setParameterRVA(n.getChild(i),formulaType);
-            }
-        } else {
-            for (int i =0;i<numOperands;i++) {
-                setParameterRVA(n.getChild(i),formulaType);
-            }
-        }
-    }
-    private void setParameterRVA(Node n, int expectedClass,int formulaType) {
-        Ptg p = n.getValue();
-        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
-            if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
-                setClass(n, Ptg.CLASS_REF);
-            }
-            if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE) {
-                if (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED) {
-                    setClass(n,Ptg.CLASS_VALUE);
-                } else {
-                    setClass(n,Ptg.CLASS_ARRAY);
-                }
-            }
-            if (p.getDefaultOperandClass() == Ptg.CLASS_ARRAY ) {
-                setClass(n, Ptg.CLASS_ARRAY);
-            }
-        } else if (expectedClass == Ptg.CLASS_VALUE) { //pg 15, table 2
-            if (formulaType == FORMULA_TYPE_NAMEDRANGE) {
-                setClass(n,Ptg.CLASS_ARRAY) ;
-            } else {
-                setClass(n,Ptg.CLASS_VALUE);
-            }
-        } else { //Array class, pg 16.
-            if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
-                 (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
-                 setClass(n,Ptg.CLASS_VALUE);
-            } else {
-                setClass(n,Ptg.CLASS_ARRAY);
-            }
-        }
-    }
-
-     private void setClass(Node n, byte theClass) {
-        Ptg p = n.getValue();
-        if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
-            p.setClass(theClass);
-        } else {
-            for (int i =0;i<n.getNumChildren();i++) {
-                setClass(n.getChild(i),theClass);
-            }
-        }
-     }
     /**
      * Convenience method which takes in a list then passes it to the
      *  other toFormulaString signature.
@@ -988,11 +826,11 @@ end;
                 // TODO - put comment and throw exception in toFormulaString() of these classes
                 continue;
             }
-            if (! (ptg instanceof OperationPtg)) {
-                stack.push(ptg.toFormulaString(book));
+            if (ptg instanceof ParenthesisPtg) {
+                String contents = (String)stack.pop();
+                stack.push ("(" + contents + ")");
                 continue;
             }
-
             if (ptg instanceof AttrPtg) {
                 AttrPtg attrPtg = ((AttrPtg) ptg);
                 if (attrPtg.isOptimizedIf() || attrPtg.isOptimizedChoose() || attrPtg.isGoto()) {
@@ -1009,24 +847,21 @@ end;
                     // similar to tAttrSpace - RPN is violated
                     continue;
                 }
-                if (!attrPtg.isSum()) {
-                    throw new RuntimeException("Unexpected tAttr: " + attrPtg.toString());
+                if (attrPtg.isSum()) {
+                    String[] operands = getOperands(stack, attrPtg.getNumberOfOperands());
+                    stack.push(attrPtg.toFormulaString(operands));
+                    continue;
                 }
+                throw new RuntimeException("Unexpected tAttr: " + attrPtg.toString());
             }
 
-            final OperationPtg o = (OperationPtg) ptg;
-            int nOperands = o.getNumberOfOperands();
-            final 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 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<numPtgs;i++) {
-            if (tokens.get(i) instanceof OperationPtg) {
-
-                o = (OperationPtg) tokens.get(i);
-                numOperands = o.getNumberOfOperands();
-                operands = new Node[numOperands];
-                for (int j=0;j<numOperands;j++) {
-                    operands[numOperands-j-1] = (Node) stack.pop();
-                }
-                Node result = new Node(o);
-                result.setChildren(operands);
-                stack.push(result);
-            } else {
-                stack.push(new Node((Ptg)tokens.get(i)));
-            }
-        }
-        return (Node) stack.pop();
-    }
-
-    /** toString on the parser instance returns the RPN ordered list of tokens
-     *   Useful for testing
-     */
-    public String toString() {
-        StringBuffer buf = new StringBuffer();
-           for (int i=0;i<tokens.size();i++) {
-            buf.append( ( (Ptg)tokens.get(i)).toFormulaString(book));
-            buf.append(' ');
-        }
-        return buf.toString();
-    }
-
-    /** Private helper class, used to create a tree representation of the formula*/
-    private static final class Node {
-        private Ptg value=null;
-        private Node[] children=new Node[0];
-        private int numChild=0;
-        public Node(Ptg val) {
-            value = val;
-        }
-        public void setChildren(Node[] child) {children = child;numChild=child.length;}
-        public int getNumChildren() {return numChild;}
-        public Node getChild(int number) {return children[number];}
-        public Ptg getValue() {return value;}
-    }
 }
diff --git a/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java b/src/java/org/apache/poi/hssf/model/OperandClassTransformer.java
new file mode 100644 (file)
index 0000000..5358324
--- /dev/null
@@ -0,0 +1,206 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.model;
+
+import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
+import org.apache.poi.hssf.record.formula.ControlPtg;
+import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+
+/**
+ * This class performs 'operand class' transformation. Non-base tokens are classified into three 
+ * operand classes:
+ * <ul>
+ * <li>reference</li> 
+ * <li>value</li> 
+ * <li>array</li> 
+ * </ul>
+ * <p/>
+ * 
+ * The final operand class chosen for each token depends on the formula type and the token's place
+ * in the formula. If POI gets the operand class wrong, Excel <em>may</em> interpret the formula
+ * incorrectly.  This condition is typically manifested as a formula cell that displays as '#VALUE!',
+ * but resolves correctly when the user presses F2, enter.<p/>
+ * 
+ * The logic implemented here was partially inspired by the description in
+ * "OpenOffice.org's Documentation of the Microsoft Excel File Format".  The model presented there
+ * seems to be inconsistent with observed Excel behaviour (These differences have not been fully
+ * investigated). The implementation in this class has been heavily modified in order to satisfy
+ * concrete examples of how Excel performs the same logic (see TestRVA).<p/>
+ * 
+ * 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 <tt>Ptg.setClass()</tt> 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 (file)
index 0000000..acd8cb1
--- /dev/null
@@ -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 <tt>Ptg</tt>
+ * token.  Each <tt>ParseNode</tt> may have child <tt>ParseNode</tt>s in the case when the wrapped
+ * <tt>Ptg</tt> 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 <tt>Ptg</tt> 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<toIx; i++) {
+                               result += _ptgs[i].getSize();
+                       }
+                       return result;
+               }
+
+               public int createPlaceholder() {
+                       return _offset++;
+               }
+
+               public void add(Ptg token) {
+                       if (token == null) {
+                               throw new IllegalArgumentException("token must not be null");
+                       }
+                       _ptgs[_offset] = token;
+                       _offset++;
+               }
+
+               public void setPlaceholder(int index, Ptg token) {
+                       if (_ptgs[index] != null) {
+                               throw new IllegalStateException("Invalid placeholder index (" + index + ")");
+                       }
+                       _ptgs[index] = token;
+               }
+
+               public Ptg[] getResult() {
+                       return _ptgs;
+               }
+       }
+}
index 24cf2fae12a6255c1039d42c9b1ccb1243f89a33..0e59282f5e22789ee809382c36aaedd081aa15ef 100644 (file)
@@ -108,7 +108,10 @@ public class EmbeddedObjectRefSubRecord
             in.readByte(); // discard
         }
 
-        field_6_stream_id              = in.readInt();
+        // Fetch the stream ID
+        field_6_stream_id = in.readInt();
+        
+        // Store what's left
         remainingBytes = in.readRemainder();
     }
 
index 7cd704deba9783c136277265a6d2b901481bccf0..bda9950667cc2d84c0a341b4c3933e16ce65baa7 100644 (file)
@@ -44,6 +44,10 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
     protected byte field_1_num_args;
     protected short field_2_fnc_index;
 
+    public final boolean isBaseToken() {
+       return false;
+    }
+    
     public String toString() {
         StringBuffer sb = new StringBuffer(64);
         sb.append(getClass().getName()).append(" [");
index 69bbdce7010c525f15a8268a52130a7c3b8e3507..20f609c56b3992a4399322bbd191a9e32dbb2a9a 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    limitations under the License.
 ==================================================================== */
 
-/*
- * AddPtg.java
- *
- * Created on October 29, 2001, 7:48 PM
- */
 package org.apache.poi.hssf.record.formula;
 
 import org.apache.poi.ss.usermodel.Workbook;
@@ -32,10 +26,7 @@ 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 AddPtg
-    extends OperationPtg
-{
+public final class AddPtg extends ValueOperatorPtg {
     public final static int  SIZE = 1;
     public final static byte sid  = 0x03;
     
@@ -89,8 +80,6 @@ public class AddPtg
         buffer.append(operands[ 1 ]);
         return buffer.toString();
     }
-    
-    public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
            
     public Object clone() {
       return new AddPtg();
index 668a251516fd278f30968fed344486ae04c94844..e0411cd35ee692a89ecc744de7523bb21851c9b1 100644 (file)
@@ -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 Area3DPtg extends Ptg implements AreaI
-{
+public class Area3DPtg extends OperandPtg implements AreaI {
        public final static byte sid = 0x3b;
        private final static int SIZE = 11; // 10 + 1 for Ptg
        private short field_1_index_extern_sheet;
index 38cae39f442fc68b4e0d7cb9ae2a20d254ac83f5..f5f43e6cd9bd80098b2dc62081c7a2181cb95c17 100644 (file)
@@ -31,7 +31,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
  * @author  andy
  * @author Jason Height (jheight at chariot dot net dot au)
  */
-public class AreaPtg extends Ptg implements AreaI {
+public class AreaPtg extends OperandPtg implements AreaI {
     /**
      * TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
      * see similar comment in ReferencePtg
index e3ce03f9f59d107161234695a432e8129781005d..cacce0485857d4f81b17cf45e5fd7e64b7195e56 100644 (file)
@@ -59,6 +59,10 @@ public class ArrayPtg extends Ptg {
                }
        }
        
+       public boolean isBaseToken() {
+               return false;
+       }
+       
        /** 
         * Read in the actual token (array) values. This occurs 
         * AFTER the last Ptg in the expression.
@@ -95,6 +99,10 @@ public class ArrayPtg extends Ptg {
                return buffer.toString();
        }
 
+       /**
+        * Note - (2D) array elements are stored column by column 
+        * @return the index into the internal 1D array for the specified column and row
+        */
        /* package */ int getValueIndex(int colIx, int rowIx) {
                if(colIx < 0 || colIx >= 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) {
index 263c1728ba2beb8dca523502858b16674278b73c..8ebbdc33160df20024311966ad11c29596a47d54 100644 (file)
@@ -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;
index a738653951bd42c12b592b85c51f703dc4a32eaa..401bf4df0256a61b37a22438575f639b8986946f 100644 (file)
@@ -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;
index 366a2ad3c2a3d4ec17f115b7eaf18e4779394787..6473c7f989817d8504885d28ccabee238851b2c4 100644 (file)
@@ -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;
     
index 52c6836198ba372041683024386b76b7a8c52ae5..6c97bd1a37d6a845baeddc9d812dc1aeb7398efe 100644 (file)
    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");
+       }
 }
index f471c8a3ce0d793b05a5fdcba8c87052fe3eb031..960ff90320ae3f1bf7d9e1f0fdbc17d93653ace5 100644 (file)
@@ -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;
 
index b31a6fa14c6ef7c90badf1e3dd07edda0193bf25..9ae08426b22d3801cc9fb5a960463d5babdeca63 100644 (file)
@@ -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;
 
index 3343238b9960a0c02123d77511ae99a667fa1f82..9d81e3bc24f0a2f9e323d71b522c64ec42cca822 100644 (file)
@@ -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);
     }
index 05b0fbe8648dbdc2240aa6ff4593da9ceeab3138..91f4baf3b125d898e77ebc75cb64d96a71515565 100644 (file)
@@ -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;
index 364ddf5a02bb46abb9030559394ccba816e2e47e..cea44ed430d7bcd40cb8c154dca037f2460aff3b 100644 (file)
@@ -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;
index 431dc5717b820969f231bf436ee9a5aab4139b97..e3d2e77310bbb0c8f6b0456671f58d28a30cabc8 100644 (file)
@@ -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();
+        }
     }
 
     /**
index 88cfa84d4fd87db7ed349750bc412040f933d460..91b9713a84889c82bbbd1d9808609322f67ba40a 100755 (executable)
@@ -1,4 +1,3 @@
-        
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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;
 
index cfe45a404ae246434215a3045168b5ebd8dff079..9bc83e569005cd39f476a34512ae9fbba9a97d6c 100644 (file)
    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
index a1753f5ac14fc33401d89c9c98964f78c6b07d5e..673ce4fade0522189163a87e4dab992147f3b053 100644 (file)
@@ -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);
index 61d02edaa7ae14af0fa0125310737f0816435185..e7a0ea7ce59f8dcf2c6952d25812335bbff99023 100644 (file)
@@ -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()
     {
index e63fda02d3b9b4cfc96bbbb66cca7486ed806bef..f6e4919c45b20f22219751d0eada075f39136030 100755 (executable)
@@ -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;
 
index 6f3c0b0a29d36436840dba3c92978d5e3fd113cd..03241ef37e00cdee60909a8d031ed6f566ebbf3c 100644 (file)
    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
index add7b0461b8b5b308a1f39394e067a6328a11a41..a2e075c2285bc1013f39a97d4600ef4336a154af 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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;
index ac23e8aab7e14cd93fab98a778393436a07e7c48..a351cbc9ad3c1c52f0695a3989da6736962ac875 100644 (file)
@@ -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;
index 32ba801406539e5a9232fa25d35b532f12f30fba..890f9e895f2ae85b4c3140ef7406200638d68a67 100644 (file)
@@ -26,9 +26,7 @@ import org.apache.poi.hssf.record.RecordInputStream;
  * Avik Sengupta &lt;avik at apache.org&gt;
  * @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();
index 04589c1a864f197dd2672577f1824e226ee1e381..1c5c80eab1a985eb991a0c89ce859159c4f13cd3 100644 (file)
@@ -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;
 
index f501f2b3da4eb0876915a4df19bef1ed6cbfef10..22215e2ec6921561e9f22452dfd270b14572ccd3 100644 (file)
@@ -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 */
index 39e05262a7cc5989c9e74a5e06f51a8d1fc2737f..6d98ef2eb4def35c59e3d7d430e122365bdc36fb 100644 (file)
@@ -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
index 713e8fb04613f7567fdd0ea2dab79617175cfb4d..95b68e87daaa4ceca0999d2840f108046dcb6a8d 100755 (executable)
@@ -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;
 
index f74faf091721dfcde4cb3736f48954e3ea4a9f27..a6d9e9259770e7a65d03e4a081679bcebab925a9 100644 (file)
@@ -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 (file)
index 0000000..02a708f
--- /dev/null
@@ -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 <tt>Ptg</tt>s are classifed ('relative', 'value', 'array')  
+        */
+       public final boolean isBaseToken() {
+               return false;
+       }
+}
index 9d977f5eb807ff14a08268d640b4acb198943026..7781d061ce2d84a0957d4291114cc37632452c3d 100644 (file)
@@ -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();
index 3e1d650dcd2b5d5ab4530bbc65a521b864633f4d..a8d6450b4e09f1f1e5d75595587b0280b7709ba7 100644 (file)
@@ -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();
     }
index 327f9c566a71da8aec243d24ec96e5e2f07b0b28..dbd0f28581da16fdaaca8304cac60e8d63e3e285 100644 (file)
@@ -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;
 
index 8816a2d683c31d2f0a1b7b348a4f0e4b61b855c9..7819c35bc8f6e10fe3e21bd15cdf18266e392dc4 100644 (file)
@@ -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 <code>false</code> if this token is classified as 'reference', 'value', or 'array'
+     */
+    public abstract boolean isBaseToken();
 }
index 09bedaecf6fb2006eccf1ff6d48f5e1ac0f035cb..05cb6defe14aba43a42c140186dd10635c3279ab 100644 (file)
@@ -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()
     {
index 1ae56cf52e78503400962e137e59cb49bf72e5e2..fa1563c040b68a6494ab74a9489631b60a8f1cfd 100644 (file)
@@ -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;
index 031fa411203a330e36d20778ee6b9020b4fd7496..fed32ddade5fedcd892e434f1a2aacbf97d8b1fd 100755 (executable)
@@ -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;
index 4486ec087ca7f948bd5de72e67fc2d15f4623375..d06507e50eb98668a56c2b510d1becb22b87dcf0 100644 (file)
@@ -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 (file)
index 0000000..43b8c13
--- /dev/null
@@ -0,0 +1,31 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hssf.record.formula;\r
+\r
+/**\r
+ * @author Josh Micich\r
+ */\r
+abstract class ScalarConstantPtg extends Ptg {\r
+       public boolean isBaseToken() {\r
+               return true;\r
+       }\r
+    public final byte getDefaultOperandClass() {\r
+        return Ptg.CLASS_VALUE;\r
+    }\r
+       \r
+}\r
index c90590d1b81415b9963fb47cacc4954fa3491731..6cd65005e223a2d2b25268d93dbf155d57c885ad 100644 (file)
@@ -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;
index 6d1d1e860dc2a7bedc4620dbfa128074b3d8dcdf..fc99293fdcad8ccb1b7c1fd8ba0e85f7b626536c 100644 (file)
@@ -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;
 
index d85cc4913eefad2e8d6ee6c772fcb32a394326a8..296cf25c592343a9e66f98465df7bee927d12955 100644 (file)
@@ -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();
     }
index 6ae89cf2c1c32af5db9a2579a7d34753fd795154..eef161e4411d6fb7aa778ab57e79a1e5e0016008 100644 (file)
@@ -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();
     }
index 3b671e22fd499cfd123cfc94a007478471a6ad09..4abc33b86869366ad9eeba57e7917fc906176440 100644 (file)
@@ -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()
     {
index af5ebc84416a850d687140407b8c999a73ae8842..07749022edc0e4dc63d869a42c6c1502175332a3 100644 (file)
@@ -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 (file)
index 0000000..4ef6ab5
--- /dev/null
@@ -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 <tt>Ptg</tt>s are base tokens (i.e. are not RVA classifed)  
+        */
+       public final boolean isBaseToken() {
+               return true;
+       }
+       public final byte getDefaultOperandClass() {
+               return Ptg.CLASS_VALUE;
+       }
+}
index b1c5c66e08b03c1064006dac82221cf523ff56c4..697c33b9e2b9d5731bcff562b6123992f49b36de 100644 (file)
@@ -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
-    {
+    public DirectoryEntry getDirectory() throws IOException {
+       EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
+
+       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())
-        {
+        
+        while (subRecordIter.hasNext()) {
             Object subRecord = subRecordIter.next();
-            if (subRecord instanceof EmbeddedObjectRefSubRecord)
-            {
-                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");
-                }
+            if (subRecord instanceof EmbeddedObjectRefSubRecord) {
+               return (EmbeddedObjectRefSubRecord)subRecord;
             }
         }
-
+        
         throw new IllegalStateException("Object data does not contain a reference to an embedded object OLE2 directory");
     }
 }
index 0a9bdcfe8814fac7d660b7f0278d11ed27670e06..0215af012606e80bb9d07f07e4fa54775384872f 100644 (file)
@@ -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 - / , . :
index 36916e9c814c9ec600058e39569d2eddfeca936c..0ecde630493a6f7d3f8fc1f01a8598ae9e4a2d10 100644 (file)
@@ -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);
index 8a85f42841a8405a4e591145d67cc359a6750462..31694d5d76a9b8d482c48b827e1157fc65dfb5ba 100644 (file)
@@ -15,6 +15,7 @@
 \r
 # Created by (org.apache.poi.hssf.record.formula.function.ExcelFileFormatDocFunctionExtractor)\r
 # from source file 'excelfileformat.odt' (size=356107, md5=0x8f789cb6e75594caf068f8e193004ef4)\r
+#  ! + some manual edits !\r
 #\r
 #Columns: (index, name, minParams, maxParams, returnClass, paramClasses, isVolatile, hasFootnote )\r
 \r
@@ -78,8 +79,8 @@
 58     NPER    3       5       V       V V V V V               \r
 59     PMT     3       5       V       V V V V V               \r
 60     RATE    3       6       V       V V V V V V             \r
-61     MIRR    3       3       V       R V V           \r
-62     IRR     1       2       V       R V             \r
+61     MIRR    3       3       V       A V V           \r
+62     IRR     1       2       V       A V             \r
 63     RAND    0       0       V       -       x       \r
 64     MATCH   2       3       V       V R R           \r
 65     DATE    3       3       V       V V V           \r
@@ -93,8 +94,8 @@
 73     SECOND  1       1       V       V               \r
 74     NOW     0       0       V       -       x       \r
 75     AREAS   1       1       V       R               \r
-76     ROWS    1       1       V       R               \r
-77     COLUMNS 1       1       V       R               \r
+76     ROWS    1       1       V       A               \r
+77     COLUMNS 1       1       V       A               \r
 78     OFFSET  3       5       R       R V V V V       x       \r
 82     SEARCH  2       3       V       V V V           \r
 83     TRANSPOSE       1       1       A       A               \r
index 5b48501938bf0863e51cf6094cbb92d107f050c8..e3f5bb23e92a2a8d825ffc6f2c2fdf372e33a66f 100644 (file)
@@ -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
index 184d46d2fe2008e9e650c766745ffadd0e6fb335..d28b8a8777fc0a73287e2336ea5982650e4f0d2c 100644 (file)
@@ -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 (file)
index 0000000..3c49fc2
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 (file)
index 0000000..327edbb
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/testRVA.xls differ
index 19ef437063be75446f9a993d215f04faf20d0c1b..045e371a22afc6d167e37f96edc12a45b3197170 100755 (executable)
@@ -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;
index f2821140f3c325ae6cfdaac482463a9539635b12..929279a3ce9683ecbf4509e17620acd3f5f4c17a 100644 (file)
@@ -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 <code>null</code>
         */
-       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<A2,B1,B2)");
-        assertEquals(9, ptgs.length);
-        assertEquals("3rd Ptg is less than", LessThanPtg.class, ptgs[2].getClass());
-       }
-
-       public void testParenIf() {
-               Ptg[] ptgs = parseFormula("IF((A1+A2)<=3,\"yes\",\"no\")");
-               assertEquals(12, ptgs.length);
-               assertEquals("6th Ptg is less than equal",LessEqualPtg.class,ptgs[5].getClass());
-               assertEquals("11th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[10].getClass());
-       }
-
-       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 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 (file)
index 0000000..ba05c16
--- /dev/null
@@ -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 <tt>FormulaParser</tt> 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(A1<A2,B1,B2)");
+        assertEquals(9, ptgs.length);
+        assertEquals("3rd Ptg is less than", LessThanPtg.class, ptgs[2].getClass());
+       }
+
+       public void testParenIf() {
+               Ptg[] ptgs = parseFormula("IF((A1+A2)<=3,\"yes\",\"no\")");
+               assertEquals(12, ptgs.length);
+               assertEquals("6th Ptg is less than equal",LessEqualPtg.class,ptgs[5].getClass());
+               assertEquals("11th Ptg is not a goto (Attr) ptg",AttrPtg.class,ptgs[10].getClass());
+       }
+       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());
+       }
+       /**
+        * 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());
+       }
+}
diff --git a/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java b/src/testcases/org/apache/poi/hssf/model/TestOperandClassTransformer.java
new file mode 100644 (file)
index 0000000..90a5d8b
--- /dev/null
@@ -0,0 +1,110 @@
+/* ====================================================================
+   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.AbstractFunctionPtg;
+import org.apache.poi.hssf.record.formula.FuncVarPtg;
+import org.apache.poi.hssf.record.formula.Ptg;
+
+/**
+ * Tests specific formula examples in <tt>OperandClassTransformer</tt>.
+ * 
+ * @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: <code>INDEX(PI(),1)</code>, 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 (file)
index 0000000..cb51c17
--- /dev/null
@@ -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
+ * <tt>OperandClassTransformer</tt> 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 + ")");
+       }
+}
index 16f80bb791a0c7f242be57a80455a123e5687368..39464b5e03ab468e0b4ebff6871fc1a9be96b0b8 100644 (file)
@@ -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 (file)
index 0000000..d657647
--- /dev/null
@@ -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 <tt>Ptg</tt> 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;
+       }
+
+}
index 0e0a656341640409cae9f68963fd087a90fe5337..419bc33bb46322dc1b6f25b1558497af20726855 100644 (file)
 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) {}
+    }
 }
index 76c098da2df3e4a1342483b63e6e7983fc393437..4f526b61c0a645e5db70aefc2c91579460a98e5f 100644 (file)
@@ -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<formats.length; i++) {
-            assertTrue( HSSFDateUtil.isADateFormat(formatId, formats[i]) );
+            assertTrue(
+                       formats[i] + " is a date format", 
+                       HSSFDateUtil.isADateFormat(formatId, formats[i])
+            );
         }
         
         // Then time based ones too
@@ -270,7 +276,10 @@ public class TestHSSFDateUtil extends TestCase {
                 "mm/dd HH:MM PM", "mm/dd HH:MM pm" 
         };
         for(int i=0; i<formats.length; i++) {
-            assertTrue( HSSFDateUtil.isADateFormat(formatId, formats[i]) );
+            assertTrue(
+                       formats[i] + " is a datetime format", 
+                       HSSFDateUtil.isADateFormat(formatId, formats[i])
+            );
         }
         
         // Then invalid ones
@@ -278,10 +287,14 @@ public class TestHSSFDateUtil extends TestCase {
                 "yyyy*mm*dd", 
                 "0.0", "0.000",
                 "0%", "0.0%",
+                "[]Foo", "[BLACK]0.00%",
                 "", null
         };
         for(int i=0; i<formats.length; i++) {
-            assertFalse( HSSFDateUtil.isADateFormat(formatId, formats[i]) );
+            assertFalse( 
+                       formats[i] + " is not a date or datetime format",
+                       HSSFDateUtil.isADateFormat(formatId, formats[i])
+            );
         }
         
         // And these are ones we probably shouldn't allow,