]> source.dussan.org Git - poi.git/commitdiff
Big patch from Josh from bug #44504 - lots of formula parser improvements
authorNick Burch <nick@apache.org>
Tue, 4 Mar 2008 16:53:32 +0000 (16:53 +0000)
committerNick Burch <nick@apache.org>
Tue, 4 Mar 2008 16:53:32 +0000 (16:53 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@633547 13f79535-47bb-0310-9956-ffa450edef68

81 files changed:
src/java/org/apache/poi/hssf/model/FormulaParser.java
src/java/org/apache/poi/hssf/model/LinkTable.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/model/RecordStream.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/model/Workbook.java
src/java/org/apache/poi/hssf/record/CRNCountRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/CRNRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/ExternalNameRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/RecordFactory.java
src/java/org/apache/poi/hssf/record/SupBookRecord.java
src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/formula/AbstractFunctionPtg.java
src/java/org/apache/poi/hssf/record/formula/IntPtg.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/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
src/java/org/apache/poi/util/LittleEndian.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/AddEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ConcatEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/DivideEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/MultiplyEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/OperandResolver.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/PowerEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref2DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/RefEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/SubtractEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryMinusEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ValueEvalToNumericXlator.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Avedev.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Average.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FinanceFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/If.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Isblank.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Len.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Maxa.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mid.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Mina.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/NumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Stdev.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumsq.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2my2.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumx2py2.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumxmy2.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Trim.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/XYNumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/scratchpad/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/EvalFactory.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/NumericFunctionInvoker.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMid.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/HSSFTests.java
src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls
src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
src/testcases/org/apache/poi/hssf/record/AllRecordTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/record/TestSupBookRecord.java
src/testcases/org/apache/poi/hssf/record/TestcaseRecordInputStream.java
src/testcases/org/apache/poi/hssf/record/formula/AbstractPtgTestCase.java
src/testcases/org/apache/poi/hssf/record/formula/AllFormulaTests.java
src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java

index dc75a5c1701c76102e0619511f1f5f1a263c56a0..7b89b90d81f27193380e8f1f7d594bb228152be3 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
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
-
 
 package org.apache.poi.hssf.model;
 
 import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
+import java.util.Stack;
 import java.util.regex.Pattern;
 
 //import PTG's .. since we need everything, import *
@@ -33,7 +30,7 @@ import org.apache.poi.hssf.record.formula.*;
 
 /**
  * This class parses a formula string into a List of tokens in RPN order.
- * Inspired by 
+ * Inspired by
  *           Lets Build a Compiler, by Jack Crenshaw
  * BNF for the formula expression is :
  * <expression> ::= <term> [<addop> <term>]*
@@ -48,138 +45,124 @@ import org.apache.poi.hssf.record.formula.*;
  *  @author Peter M. Murray (pete at quantrix dot com)
  *  @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
  */
-public class FormulaParser {
+public final class FormulaParser {
     
+    /**
+     * Specific exception thrown when a supplied formula does not parse properly.<br/>
+     * Primarily used by test cases when testing for specific parsing exceptions.</p>
+     *    
+     */
+    static final class FormulaParseException extends RuntimeException {
+        // This class was given package scope until it would become clear that it is useful to
+        // general client code. 
+        public FormulaParseException(String msg) {
+            super(msg);
+        }
+    }
+
     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;
-    
-    private String formulaString;
-    private int pointer=0;
-    private int formulaLength;
-    
-    private List tokens = new java.util.Stack();
-    
-    /**
-     * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded.
-     */
-    private List functionTokens = new LinkedList();
+
+    private final String formulaString;
+    private final int formulaLength;
+    private int pointer;
+
+    private final List tokens = new Stack();
 
     /**
      * Used for spotting if we have a cell reference,
      *  or a named range
      */
     private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+");
-        
+
     private static char TAB = '\t';
-    private static char CR = '\n';
-    
-   private char look;              // Lookahead Character
-   
-   private Workbook book;
-    
-    
-    /** 
+
+    /**
+     * Lookahead Character.
+     * gets value '\0' when the input string is exhausted
+     */
+    private char look;
+
+    private Workbook book;
+
+
+    /**
      * Create the formula parser, with the string that is to be
      *  parsed against the supplied workbook.
      * A later call the parse() method to return ptg list in
      *  rpn order, then call the getRPNPtg() to retrive the
      *  parse results.
      * This class is recommended only for single threaded use.
-     * 
+     *
      * If you only have a usermodel.HSSFWorkbook, and not a
      *  model.Workbook, then use the convenience method on
-     *  usermodel.HSSFFormulaEvaluator 
+     *  usermodel.HSSFFormulaEvaluator
      */
     public FormulaParser(String formula, Workbook book){
         formulaString = formula;
         pointer=0;
         this.book = book;
-       formulaLength = formulaString.length();
+        formulaLength = formulaString.length();
+    }
+
+    public static Ptg[] parse(String formula, Workbook book) {
+        FormulaParser fp = new FormulaParser(formula, book);
+        fp.parse();
+        return fp.getRPNPtg();
     }
-    
 
     /** Read New Character From Input Stream */
     private void GetChar() {
         // Check to see if we've walked off the end of the string.
-       // Just return if so and reset Look to smoething to keep 
-       // SkipWhitespace from spinning
-        if (pointer == formulaLength) {
+        if (pointer > formulaLength) {
+            throw new RuntimeException("too far");
+        }
+        if (pointer < formulaLength) {
+            look=formulaString.charAt(pointer);
+        } else {
+            // Just return if so and reset 'look' to something to keep
+            // SkipWhitespace from spinning
             look = (char)0;
-           return;
-       }
-        look=formulaString.charAt(pointer++);
+        }    
+        pointer++;
         //System.out.println("Got char: "+ look);
     }
-    
-
-    /** Report an Error */
-    private void Error(String s) {
-        System.out.println("Error: "+s);
-    }
-    
-    
-    /** Report Error and Halt */
-    private void Abort(String s) {
-        Error(s);
-        //System.exit(1);  //throw exception??
-        throw new RuntimeException("Cannot Parse, sorry : " + s + " @ " + pointer + " [Formula String was: '" + formulaString + "']");
-    }
-    
-    
 
     /** Report What Was Expected */
-    private void Expected(String s) {
-        Abort(s + " Expected");
+    private RuntimeException expected(String s) {
+        return new FormulaParseException(s + " Expected");
     }
-    
-    
+
+
+
     /** Recognize an Alpha Character */
     private boolean IsAlpha(char c) {
         return Character.isLetter(c) || c == '$' || c=='_';
     }
-    
-    
+
+
+
     /** Recognize a Decimal Digit */
     private boolean IsDigit(char c) {
         //System.out.println("Checking digit for"+c);
         return Character.isDigit(c);
     }
-    
-    
+
+
 
     /** Recognize an Alphanumeric */
     private boolean  IsAlNum(char c) {
         return  (IsAlpha(c) || IsDigit(c));
     }
-    
-    
 
-    /** Recognize an Addop */
-    private boolean IsAddop( char c) {
-        return (c =='+' || c =='-');
-    }
-    
 
     /** Recognize White Space */
     private boolean IsWhite( char c) {
         return  (c ==' ' || c== TAB);
     }
-    
-    /**
-     * Determines special characters;primarily in use for definition of string literals
-     * @param c
-     * @return boolean
-     */
-    private boolean IsSpecialChar(char c) {
-       return (c == '>' || c== '<' || c== '=' || c=='&' || c=='[' || c==']');
-    }
-    
 
     /** Skip Over Leading White Space */
     private void SkipWhite() {
@@ -187,108 +170,84 @@ public class FormulaParser {
             GetChar();
         }
     }
-    
-    
 
-    /** Match a Specific Input Character */
+    /**
+     *  Consumes the next input character if it is equal to the one specified otherwise throws an
+     *  unchecked exception. This method does <b>not</b> consume whitespace (before or after the
+     *  matched character). 
+     */
     private void Match(char x) {
         if (look != x) {
-            Expected("" + x + "");
-        }else {
-            GetChar();
-            SkipWhite();
+            throw expected("'" + x + "'");
         }
+        GetChar();
     }
-    
+
     /** Get an Identifier */
     private String GetName() {
         StringBuffer Token = new StringBuffer();
         if (!IsAlpha(look) && look != '\'') {
-            Expected("Name");
+            throw expected("Name");
         }
         if(look == '\'')
         {
-               Match('\'');
-               boolean done = look == '\'';
-               while(!done)
-               {
-                       Token.append(Character.toUpperCase(look));
-                       GetChar();
-                       if(look == '\'')
-                       {
-                               Match('\'');
-                               done = look != '\'';
-                       }
-               }
+            Match('\'');
+            boolean done = look == '\'';
+            while(!done)
+            {
+                Token.append(look);
+                GetChar();
+                if(look == '\'')
+                {
+                    Match('\'');
+                    done = look != '\'';
+                }
+            }
         }
         else
         {
-               while (IsAlNum(look)) {
-                   Token.append(Character.toUpperCase(look));
-                   GetChar();
-               }
-               }
-        SkipWhite();
-        return Token.toString();
-    }
-    
-    /**Get an Identifier AS IS, without stripping white spaces or 
-       converting to uppercase; used for literals */
-    private String GetNameAsIs() {
-        StringBuffer Token = new StringBuffer();
-               
-               while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) {
-            Token = Token.append(look);
-            GetChar();
+            while (IsAlNum(look)) {
+                Token.append(look);
+                GetChar();
+            }
         }
         return Token.toString();
     }
-    
-    
+
+
     /** Get a Number */
     private String GetNum() {
         StringBuffer value = new StringBuffer();
-        
+
         while (IsDigit(this.look)){
             value.append(this.look);
             GetChar();
         }
-        
-        SkipWhite();
-        
         return value.length() == 0 ? null : value.toString();
     }
-        
-    
-    /** Output a String with Tab */
-    private void  Emit(String s){
-        System.out.print(TAB+s);
-    }
 
-    /** Output a String with Tab and CRLF */
-    private void EmitLn(String s) {
-        Emit(s);
-        System.out.println();;
-    }
-    
     /** Parse and Translate a String Identifier */
-    private void Ident() {
+    private Ptg parseIdent() {
         String name;
         name = GetName();
         if (look == '('){
             //This is a function
-            function(name);
-        } else if (look == ':' || look == '.') { // this is a AreaReference
+            return function(name);
+        }
+
+        if (look == ':' || look == '.') { // this is a AreaReference
             GetChar();
-            
+
             while (look == '.') { // formulas can have . or .. or ... instead of :
                 GetChar();
             }
-            
+
             String first = name;
             String second = GetName();
-            tokens.add(new AreaPtg(first+":"+second));
-        } else if (look == '!') {
+            return new AreaPtg(first+":"+second);
+        }
+
+        if (look == '!') {
             Match('!');
             String sheetName = name;
             String first = GetName();
@@ -297,79 +256,78 @@ public class FormulaParser {
                 Match(':');
                 String second=GetName();
                 if (look == '!') {
-                       //The sheet name was included in both of the areas. Only really
-                       //need it once
-                       Match('!');
-                       String third=GetName();
-                       
-                       if (!sheetName.equals(second))
-                               throw new RuntimeException("Unhandled double sheet reference.");
-                       
-                       tokens.add(new Area3DPtg(first+":"+third,externIdx));
-                } else {                                  
-                  tokens.add(new Area3DPtg(first+":"+second,externIdx));
+                    //The sheet name was included in both of the areas. Only really
+                    //need it once
+                    Match('!');
+                    String third=GetName();
+
+                    if (!sheetName.equals(second))
+                        throw new RuntimeException("Unhandled double sheet reference.");
+
+                    return new Area3DPtg(first+":"+third,externIdx);
                 }
-            } else {
-                tokens.add(new Ref3DPtg(first,externIdx));
+                return new Area3DPtg(first+":"+second,externIdx);
             }
-        } else {
-            // This can be either a cell ref or a named range
-               // Try to spot which it is
-               boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
-            boolean boolLit = (name.equals("TRUE") || name.equals("FALSE"));
-            
-            if (boolLit) {
-                tokens.add(new BoolPtg(name));
-            } else if (cellRef) {
-                tokens.add(new ReferencePtg(name));
-            } else {
-               boolean nameRecordExists = false;
-                for(int i = 0; i < book.getNumNames(); i++) {
-                       // Our formula will by now contain an upper-cased
-                       //  version of any named range names
-                    if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) {
-                        nameRecordExists = true;
-                    }
-                }
-                if(!nameRecordExists)
-                    Abort("Found reference to named range \"" + name + "\", but that named range wasn't defined!");
-                tokens.add(new NamePtg(name, book));
+            return new Ref3DPtg(first,externIdx);
+        }
+        if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
+            return new BoolPtg(name.toUpperCase());
+        }
+
+        // This can be either a cell ref or a named range
+        // Try to spot which it is
+        boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
+        if (cellRef) {
+            return new ReferencePtg(name);
+        }
+
+        for(int i = 0; i < book.getNumNames(); i++) {
+            // named range name matching is case insensitive
+            if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) {
+                return new NamePtg(name, book);
             }
         }
+        throw new FormulaParseException("Found reference to named range \"" 
+                    + 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() {
-               if (this.functionTokens.size() > 0) {
-                       //no bounds check because this method should not be called unless a token array is setup by function()
-                       List arguments = (List)this.functionTokens.get(0);
-                       arguments.add(tokens.get(tokens.size()-1));
-               }
+    private void addArgumentPointer(List argumentPointers) {
+        argumentPointers.add(tokens.get(tokens.size()-1));
     }
-    
-    private void function(String name) {
-       //average 2 args per function
-       this.functionTokens.add(0, new ArrayList(2));
-       
+
+    /**
+     * Note - Excel function names are 'case aware but not case sensitive'.  This method may end
+     * up creating a defined name record in the workbook if the specified name is not an internal
+     * Excel function, and has not been encountered before. 
+     * 
+     * @param name case preserved function name (as it was entered/appeared in the formula). 
+     */
+    private Ptg function(String name) {
+        int numArgs =0 ;
+        // 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);
+            
+            // 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('(');
-        int numArgs = Arguments();
+        numArgs += Arguments(argumentPointers);
         Match(')');
-                
-        AbstractFunctionPtg functionPtg = getFunction(name,(byte)numArgs);
-        
-               tokens.add(functionPtg);
-        if (functionPtg.getName().equals("externalflag")) {
-            tokens.add(new NamePtg(name, this.book));
-        }
 
-               //remove what we just put in
-               this.functionTokens.remove(0);
+        return getFunction(name, numArgs, argumentPointers);
     }
-    
+
     /**
      * Adds the size of all the ptgs after the provided index (inclusive).
      * <p>
@@ -378,17 +336,17 @@ public class FormulaParser {
      * @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;
+        int count = 0;
+
+        Iterator ptgIterator = tokens.listIterator(index);
+        while (ptgIterator.hasNext()) {
+            Ptg ptg = (Ptg)ptgIterator.next();
+            count+=ptg.getSize();
+        }
+
+        return count;
     }
-    
+
     private int getPtgSize(int start, int end) {
         int count = 0;
         int index = start;
@@ -398,390 +356,430 @@ public class FormulaParser {
             count+=ptg.getSize();
             index++;
         }
-        
+
         return count;
     }
     /**
      * Generates the variable function ptg for the formula.
      * <p>
-     * For IF Formulas, additional PTGs are added to the tokens 
+     * For IF Formulas, additional PTGs are added to the tokens
      * @param name
      * @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, byte numArgs) {
-        AbstractFunctionPtg retval = null;
-        
-        if (name.equals("IF")) {
-            retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs);
-            
-            //simulated pop, no bounds checking because this list better be populated by function()
-            List argumentPointers = (List)this.functionTokens.get(0);
-            
-            
-            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 > (int)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));
-            
-        } else {
-            
-            retval = new FuncVarPtg(name,numArgs);
+    private AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) {
+
+        AbstractFunctionPtg retval = new FuncVarPtg(name, (byte)numArgs);
+        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;
     }
+
+    private static boolean isArgumentDelimiter(char ch) {
+        return ch ==  ',' || ch == ')';
+    }
     
     /** get arguments to a function */
-    private int Arguments() {
-        int numArgs = 0;
-        if (look != ')')  {
-            numArgs++; 
-            Expression();
-                          addArgumentPointer();
+    private int Arguments(List argumentPointers) {
+        SkipWhite();
+        if(look == ')') {
+            return 0;
         }
-        while (look == ','  || look == ';') { //TODO handle EmptyArgs
-            if(look == ',') {
-              Match(',');
-            }
-            else {
-              Match(';');
+        
+        boolean missedPrevArg = true;
+        
+        int numArgs = 0;
+        while(true) {
+            SkipWhite();
+            if(isArgumentDelimiter(look)) {
+                if(missedPrevArg) {
+                    tokens.add(new MissingArgPtg());
+                    addArgumentPointer(argumentPointers);
+                    numArgs++;
+                }
+                if(look == ')') {
+                    break;
+                }
+                Match(',');
+                missedPrevArg = true;
+                continue;
             }
-            Expression();
-                          addArgumentPointer();
+            comparisonExpression();
+            addArgumentPointer(argumentPointers);
             numArgs++;
+            missedPrevArg = false;
         }
         return numArgs;
     }
 
    /** Parse and Translate a Math Factor  */
-    private void Factor() {
-       if (look == '-')
-       {
-               Match('-');
-               Factor();
-               tokens.add(new UnaryMinusPtg());
-       }
-        else if (look == '+') {
-            Match('+');
-            Factor();
-            tokens.add(new UnaryPlusPtg());
+    private void powerFactor() {
+        percentFactor();
+        while(true) {
+            SkipWhite();
+            if(look != '^') {
+                return;
+            }
+            Match('^');
+            percentFactor();
+            tokens.add(new PowerPtg());
         }
-        else if (look == '(' ) {
-            Match('(');
-            Expression();
-            Match(')');
-            tokens.add(new ParenthesisPtg());
-        } else if (IsAlpha(look) || look == '\''){
-            Ident();
-        } else if(look == '"') {
-           StringLiteral();
-        } else if (look == ')' || look == ',') {
-               tokens.add(new MissingArgPtg());
-        } else {
-            String number2 = null;
-            String exponent = null;
-            String number1 = GetNum();
-            
-            if (look == '.') {
-                GetChar();
-                number2 = GetNum();
+    }
+    
+    private void percentFactor() {
+        tokens.add(parseSimpleFactor());
+        while(true) {
+            SkipWhite();
+            if(look != '%') {
+                return;
             }
-            
-            if (look == 'E') {
+            Match('%');
+            tokens.add(new PercentPtg());
+        }
+    }
+    
+    
+    /**
+     * factors (without ^ or % )
+     */
+    private Ptg parseSimpleFactor() {
+        SkipWhite();
+        switch(look) {
+            case '#':
+                return parseErrorLiteral();
+            case '-':
+                Match('-');
+                powerFactor();
+                return new UnaryMinusPtg();
+            case '+':
+                Match('+');
+                powerFactor();
+                return new UnaryPlusPtg();
+            case '(':
+                Match('(');
+                comparisonExpression();
+                Match(')');
+                return new ParenthesisPtg();
+            case '"':
+                return parseStringLiteral();
+            case ',':
+            case ')':
+                return new MissingArgPtg(); // TODO - not quite the right place to recognise a missing arg
+        }
+        if (IsAlpha(look) || look == '\''){
+            return parseIdent();
+        }
+        // else - assume number
+        return parseNumber();
+    }
+
+
+    private Ptg parseNumber() {
+        String number2 = null;
+        String exponent = null;
+        String number1 = GetNum();
+
+        if (look == '.') {
+            GetChar();
+            number2 = GetNum();
+        }
+
+        if (look == 'E') {
+            GetChar();
+
+            String sign = "";
+            if (look == '+') {
                 GetChar();
-                
-                String sign = "";
-                if (look == '+') {
-                    GetChar();
-                } else if (look == '-') {
-                    GetChar();
-                    sign = "-";
-                }
-                
-                String number = GetNum();
-                if (number == null) {
-                    Expected("Integer");
-                }
-                exponent = sign + number;
+            } else if (look == '-') {
+                GetChar();
+                sign = "-";
             }
-            
-            if (number1 == null && number2 == null) {
-                Expected("Integer");
+
+            String number = GetNum();
+            if (number == null) {
+                throw expected("Integer");
             }
-            
-            tokens.add(getNumberPtgFromString(number1, number2, exponent));
+            exponent = sign + number;
         }
+
+        if (number1 == null && number2 == null) {
+            throw expected("Integer");
+        }
+
+        return getNumberPtgFromString(number1, number2, exponent);
     }
-    
-       /** 
-        * Get a PTG for an integer from its string representation. 
-        * return Int or Number Ptg based on size of input
-        */
-       private Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
+
+
+    private ErrPtg parseErrorLiteral() {
+        Match('#');
+        String part1 = GetName().toUpperCase();
+
+        switch(part1.charAt(0)) {
+            case 'V':
+                if(part1.equals("VALUE")) {
+                    Match('!');
+                    return ErrPtg.VALUE_INVALID;
+                }
+                throw expected("#VALUE!");
+            case 'R':
+                if(part1.equals("REF")) {
+                    Match('!');
+                    return ErrPtg.REF_INVALID;
+                }
+                throw expected("#REF!");
+            case 'D':
+                if(part1.equals("DIV")) {
+                    Match('/');
+                    Match('0');
+                    Match('!');
+                    return ErrPtg.DIV_ZERO;
+                }
+                throw expected("#DIV/0!");
+            case 'N':
+                if(part1.equals("NAME")) {
+                    Match('?');  // only one that ends in '?'
+                    return ErrPtg.NAME_INVALID;
+                }
+                if(part1.equals("NUM")) {
+                    Match('!');
+                    return ErrPtg.NUM_ERROR;
+                }
+                if(part1.equals("NULL")) {
+                    Match('!');
+                    return ErrPtg.NULL_INTERSECTION;
+                }
+                if(part1.equals("N")) {
+                    Match('/');
+                    if(look != 'A' && look != 'a') {
+                        throw expected("#N/A");
+                    }
+                    Match(look);
+                    // Note - no '!' or '?' suffix
+                    return ErrPtg.N_A;
+                }
+                throw expected("#NAME?, #NUM!, #NULL! or #N/A");
+
+        }
+        throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
+    }
+
+
+    /**
+     * Get a PTG for an integer from its string representation.
+     * return Int or Number Ptg based on size of input
+     */
+    private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
         StringBuffer number = new StringBuffer();
-        
-           if (number2 == null) {
-               number.append(number1);
-           
-           if (exponent != null) {
-               number.append('E');
-               number.append(exponent);
-           }
-           
+
+        if (number2 == null) {
+            number.append(number1);
+
+            if (exponent != null) {
+                number.append('E');
+                number.append(exponent);
+            }
+
             String numberStr = number.toString();
-            
+            int intVal;
             try {
-                return new IntPtg(numberStr);
+                intVal = Integer.parseInt(numberStr);
             } catch (NumberFormatException e) {
                 return new NumberPtg(numberStr);
             }
-           } else {
-            if (number1 != null) {
-                number.append(number1);
-            }
-            
-            number.append('.');
-            number.append(number2);
-            
-            if (exponent != null) {
-                number.append('E');
-                number.append(exponent);
+            if (IntPtg.isInRange(intVal)) {
+                return new IntPtg(intVal);
             }
-            
-            return new NumberPtg(number.toString());
-           }
-       }
-       
-       
-       private void StringLiteral() 
-       {
-               // Can't use match here 'cuz it consumes whitespace
-               // which we need to preserve inside the string.
-               // - pete
-               // Match('"');
-               if (look != '"')
-                       Expected("\"");
-               else
-               {
-                       GetChar();
-                       StringBuffer Token = new StringBuffer();
-                       for (;;)
-                       {
-                               if (look == '"')
-                               {
-                                       GetChar();
-                                       SkipWhite(); //potential white space here since it doesnt matter up to the operator
-                                       if (look == '"')
-                                               Token.append("\"");
-                                       else
-                                               break;
-                               }
-                               else if (look == 0)
-                               {
-                                       break;
-                               }
-                               else
-                               {
-                                       Token.append(look);
-                                       GetChar();
-                               }
-                       }
-                       tokens.add(new StringPtg(Token.toString()));
-               }
-       }
-    
-    /** Recognize and Translate a Multiply */
-    private void Multiply(){
-        Match('*');
-        Factor();
-        tokens.add(new MultiplyPtg());
-  
-    }
-    
-    
-    /** Recognize and Translate a Divide */
-    private void Divide() {
-        Match('/');
-        Factor();
-        tokens.add(new DividePtg());
+            return new NumberPtg(numberStr);
+        }
+
+        if (number1 != null) {
+            number.append(number1);
+        }
 
+        number.append('.');
+        number.append(number2);
+
+        if (exponent != null) {
+            number.append('E');
+            number.append(exponent);
+        }
+
+        return new NumberPtg(number.toString());
     }
-    
-    
-    /** Parse and Translate a Math Term */
-    private void  Term(){
-        Factor();
-                while (look == '*' || look == '/' || look == '^' || look == '&') {
+
+
+    private StringPtg parseStringLiteral()
+    {
+        Match('"');
         
-            ///TODO do we need to do anything here??
-            if (look == '*') Multiply();
-            else if (look == '/') Divide();
-            else if (look == '^') Power();
-            else if (look == '&') Concat();
+        StringBuffer token = new StringBuffer();
+        while (true) {
+            if (look == '"') {
+                GetChar();
+                if (look != '"') {
+                    break;
+                }
+             }
+            token.append(look);
+            GetChar();
         }
+        return new StringPtg(token.toString());
     }
-    
-    /** Recognize and Translate an Add */
-    private void Add() {
-        Match('+');
-        Term();
-        tokens.add(new AddPtg());
+
+    /** Parse and Translate a Math Term */
+    private void  Term() {
+        powerFactor();
+        while(true) {
+            SkipWhite();
+            switch(look) {
+                case '*':
+                    Match('*');
+                    powerFactor();
+                    tokens.add(new MultiplyPtg());
+                    continue;
+                case '/':
+                    Match('/');
+                    powerFactor();
+                    tokens.add(new DividePtg());
+                    continue;
+            }
+            return; // finished with Term
+        }
     }
     
-    /** Recognize and Translate a Concatination */
-    private void Concat() {
-        Match('&');
-        Term();
-        tokens.add(new ConcatPtg());
+    private void comparisonExpression() {
+        concatExpression();
+        while (true) {
+            SkipWhite();
+            switch(look) {
+                case '=':
+                case '>':
+                case '<':
+                    Ptg comparisonToken = getComparisonToken();
+                    concatExpression();
+                    tokens.add(comparisonToken);
+                    continue;
+            }
+            return; // finished with predicate expression
+        }
     }
-    
-    /** Recognize and Translate a test for Equality  */
-    private void Equal() {
-        Match('=');
-        Expression();
-        tokens.add(new EqualPtg());
+
+    private Ptg getComparisonToken() {
+        if(look == '=') {
+            Match(look);
+            return new EqualPtg();
+        }
+        boolean isGreater = look == '>';
+        Match(look);
+        if(isGreater) {
+            if(look == '=') {
+                Match('=');
+                return new GreaterEqualPtg();
+            }
+            return new GreaterThanPtg();
+        }
+        switch(look) {
+            case '=':
+                Match('=');
+                return new LessEqualPtg();
+            case '>':
+                Match('>');
+                return new NotEqualPtg();
+        }
+        return new LessThanPtg();
     }
     
-    /** Recognize and Translate a Subtract */
-    private void Subtract() {
-        Match('-');
-        Term();
-        tokens.add(new SubtractPtg());
-    }    
 
-    private void Power() {
-        Match('^');
-        Term();
-        tokens.add(new PowerPtg());
+    private void concatExpression() {
+        additiveExpression();
+        while (true) {
+            SkipWhite();
+            if(look != '&') {
+                break; // finished with concat expression
+            }
+            Match('&');
+            additiveExpression();
+            tokens.add(new ConcatPtg());
+        }
     }
     
-    
+
     /** Parse and Translate an Expression */
-    private void Expression() {
+    private void additiveExpression() {
         Term();
-        while (IsAddop(look)) {
-            if (look == '+' )  Add();
-            else if (look == '-') Subtract();
+        while (true) {
+            SkipWhite();
+            switch(look) {
+                case '+':
+                    Match('+');
+                    Term();
+                    tokens.add(new AddPtg());
+                    continue;
+                case '-':
+                    Match('-');
+                    Term();
+                    tokens.add(new SubtractPtg());
+                    continue;
+            }
+            return; // finished with additive expression
         }
-        
-               /*
-                * This isn't quite right since it would allow multiple comparison operators.
-                */
-               
-                 if(look == '=' || look == '>' || look == '<') {
-                               if (look == '=') Equal();
-                     else if (look == '>') GreaterThan();
-                     else if (look == '<') LessThan();
-                     return;
-                 }        
-        
-        
-    }
-    
-    /** Recognize and Translate a Greater Than  */
-    private void GreaterThan() {
-               Match('>');
-               if(look == '=')
-                   GreaterEqual();
-               else {
-                   Expression();
-                   tokens.add(new GreaterThanPtg());
-               }
     }
-    
-    /** Recognize and Translate a Less Than  */
-    private void LessThan() {
-               Match('<');
-               if(look == '=')
-                   LessEqual();
-               else if(look == '>')
-                   NotEqual();
-               else {
-                   Expression();
-                   tokens.add(new LessThanPtg());
-               }
-
-       }  
-   
-   /**
-    * Recognize and translate Greater than or Equal
-    *
-    */ 
-       private void GreaterEqual() {
-           Match('=');
-           Expression();
-           tokens.add(new GreaterEqualPtg());
-       }    
-
-       /**
-        * Recognize and translate Less than or Equal
-        *
-        */ 
-
-       private void LessEqual() {
-           Match('=');
-           Expression();
-           tokens.add(new LessEqualPtg());
-       }
-       
-       /**
-        * Recognize and not Equal
-        *
-        */ 
-
-       private void NotEqual() {
-           Match('>');
-           Expression();
-           tokens.add(new NotEqualPtg());
-       }    
-    
+
     //{--------------------------------------------------------------}
     //{ Parse and Translate an Assignment Statement }
     /**
@@ -794,48 +792,46 @@ begin
 
 end;
      **/
-    
-    /** Initialize */
-    
-    private void  init() {
-        GetChar();
-        SkipWhite();
-    }
-    
+
+
     /** API call to execute the parsing of the formula
      *
      */
     public void parse() {
-        synchronized (tokens) {
-            init();
-            Expression();
+        pointer=0;
+        GetChar();
+        comparisonExpression();
+
+        if(pointer <= formulaLength) {
+            String msg = "Unused input [" + formulaString.substring(pointer-1) 
+                + "] after attempting to parse the formula [" + formulaString + "]"; 
+            throw new FormulaParseException(msg);
         }
     }
-    
-    
+
+
     /*********************************
      * PARSER IMPLEMENTATION ENDS HERE
      * EXCEL SPECIFIC METHODS BELOW
      *******************************/
-    
-    /** API call to retrive the array of Ptgs created as 
+
+    /** API call to retrive the array of Ptgs created as
      * a result of the parsing
      */
     public Ptg[] getRPNPtg() {
      return getRPNPtg(FORMULA_TYPE_CELL);
     }
-    
+
     public Ptg[] getRPNPtg(int formulaType) {
         Node node = createTree();
         setRootLevelRVA(node, formulaType);
         setParameterRVA(node,formulaType);
         return (Ptg[]) tokens.toArray(new Ptg[0]);
     }
-    
+
     private void setRootLevelRVA(Node n, int formulaType) {
         //Pg 16, excelfileformat.pdf @ openoffice.org
-        Ptg p = (Ptg) n.getValue();
+        Ptg p = n.getValue();
             if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
                 if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
                     setClass(n,Ptg.CLASS_REF);
@@ -845,9 +841,9 @@ end;
             } else {
                 setClass(n,Ptg.CLASS_VALUE);
             }
-        
+
     }
-    
+
     private void setParameterRVA(Node n, int formulaType) {
         Ptg p = n.getValue();
         int numOperands = n.getNumChildren();
@@ -863,11 +859,11 @@ end;
             for (int i =0;i<numOperands;i++) {
                 setParameterRVA(n.getChild(i),formulaType);
             }
-        } 
+        }
     }
     private void setParameterRVA(Node n, int expectedClass,int formulaType) {
-        Ptg p = (Ptg) n.getValue();
-        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1 
+        Ptg p = n.getValue();
+        if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
             if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
                 setClass(n, Ptg.CLASS_REF);
             }
@@ -887,7 +883,7 @@ end;
             } else {
                 setClass(n,Ptg.CLASS_VALUE);
             }
-        } else { //Array class, pg 16. 
+        } else { //Array class, pg 16.
             if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
                  (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
                  setClass(n,Ptg.CLASS_VALUE);
@@ -896,9 +892,9 @@ end;
             }
         }
     }
-    
+
      private void setClass(Node n, byte theClass) {
-        Ptg p = (Ptg) n.getValue();
+        Ptg p = n.getValue();
         if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
             p.setClass(theClass);
         } else {
@@ -909,7 +905,7 @@ end;
      }
     /**
      * Convience method which takes in a list then passes it to the
-     *  other toFormulaString signature. 
+     *  other toFormulaString signature.
      * @param book   workbook for 3D and named references
      * @param lptgs  list of Ptg, can be null or empty
      * @return a human readable String
@@ -930,9 +926,9 @@ end;
      * @return a human readable String
      */
     public String toFormulaString(List lptgs) {
-       return toFormulaString(book, lptgs);
+        return toFormulaString(book, lptgs);
     }
-    
+
     /**
      * Static method to convert an array of Ptgs in RPN order
      * to a human readable string format in infix mode.
@@ -941,69 +937,74 @@ end;
      * @return a human readable String
      */
     public static String toFormulaString(Workbook book, Ptg[] ptgs) {
-        if (ptgs == null || ptgs.length == 0) return "#NAME";
-        java.util.Stack stack = new java.util.Stack();
-        AttrPtg ifptg = null;
+        if (ptgs == null || ptgs.length == 0) {
+            // TODO - what is the justification for returning "#NAME" (which is not "#NAME?", btw)
+            return "#NAME";
+        }
+        Stack stack = new Stack();
 
            // Excel allows to have AttrPtg at position 0 (such as Blanks) which
            // do not have any operands. Skip them.
         int i;
         if(ptgs[0] instanceof AttrPtg) {
-               // TODO -this requirement is unclear and is not addressed by any junits
-               stack.push(ptgs[0].toFormulaString(book));
-               i=1;
+            AttrPtg attrPtg0 = (AttrPtg) ptgs[0];
+            if(attrPtg0.isSemiVolatile()) {
+                // no visible formula for semi-volatile
+            } else {
+                // TODO -this requirement is unclear and is not addressed by any junits
+                stack.push(ptgs[0].toFormulaString(book));
+            }
+            i=1;
         } else {
-               i=0;
+            i=0;
         }
-                  
+
         for ( ; i < ptgs.length; i++) {
             Ptg ptg = ptgs[i];
+            // TODO - what about MemNoMemPtg?
+            if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) {
+                // marks the start of a list of area expressions which will be naturally combined
+                // by their trailing operators (e.g. UnionPtg)
+                // TODO - put comment and throw exception in toFormulaString() of these classes
+                continue;
+            }
             if (! (ptg instanceof OperationPtg)) {
                 stack.push(ptg.toFormulaString(book));
                 continue;
             }
-                      
+
             if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
-                ifptg = (AttrPtg) ptg;
                 continue;
             }
-                      
-            final OperationPtg o = (OperationPtg) ptg;
-            final String[] operands = new String[o.getNumberOfOperands()];
-
-            for (int j = operands.length; j > 0; j--) {
-                //TODO: catch stack underflow and throw parse exception.
-                operands[j - 1] = (String) stack.pop();
-                      }  
-
-            stack.push(o.toFormulaString(operands));
-            if (!(o instanceof AbstractFunctionPtg)) continue;
 
-            final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
-            final String fname = f.getName();
-            if (fname == null) continue;
+            final OperationPtg o = (OperationPtg) ptg;
+            int nOperands = o.getNumberOfOperands();
+            final String[] operands = new String[nOperands];
 
-            if ((ifptg != null) && (fname.equals("specialflag"))) {
-                             // this special case will be way different.
-                stack.push(ifptg.toFormulaString(new String[]{(String) stack.pop()}));
-                continue;
-                      }
-            if (fname.equals("externalflag")) {
-                final String top = (String) stack.pop();
-                final int paren = top.indexOf('(');
-                final int comma = top.indexOf(',');
-                if (comma == -1) {
-                    final int rparen = top.indexOf(')');
-                    stack.push(top.substring(paren + 1, rparen) + "()");
-                  }
-                else {
-                    stack.push(top.substring(paren + 1, comma) + '(' +
-                               top.substring(comma + 1));
+            for (int j = nOperands-1; j >= 0; j--) {
+                if(stack.isEmpty()) {
+                    //TODO: write junit to prove this works
+                   String msg = "Too few arguments suppled to operation token ("
+                        + o.getClass().getName() + "). Expected (" + nOperands
+                        + " but got " + (nOperands - j + 1);
+                    throw new FormulaParseException(msg);
+                }
+                operands[j] = (String) stack.pop();
             }
+            stack.push(o.toFormulaString(operands));
         }
-    }
-        // TODO: catch stack underflow and throw parse exception.
-        return (String) stack.pop();
+        if(stack.isEmpty()) {
+            // inspection of the code above reveals that every stack.pop() is followed by a 
+            // stack.push(). So this is either an internal error or impossible.
+            throw new IllegalStateException("Stack underflow");
+        }
+        String result = (String) stack.pop();
+        if(!stack.isEmpty()) {
+            // Might be caused by some tokens like AttrPtg and Mem*Ptg, which really shouldn't
+            // put anything on the stack
+            throw new IllegalStateException("too much stuff left on the stack");
+        }
+        return result;
     }
     /**
      * Static method to convert an array of Ptgs in RPN order
@@ -1013,7 +1014,7 @@ end;
      * @return a human readable String
      */
     public String toFormulaString(Ptg[] ptgs) {
-       return toFormulaString(book, ptgs);
+        return toFormulaString(book, ptgs);
     }
 
 
@@ -1021,19 +1022,19 @@ end;
      *used to run the class(RVA) change algo
      */
     private Node createTree() {
-        java.util.Stack stack = new java.util.Stack();
+        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(); 
+                    operands[numOperands-j-1] = (Node) stack.pop();
                 }
                 Node result = new Node(o);
                 result.setChildren(operands);
@@ -1044,7 +1045,7 @@ end;
         }
         return (Node) stack.pop();
     }
-   
+
     /** toString on the parser instance returns the RPN ordered list of tokens
      *   Useful for testing
      */
@@ -1053,21 +1054,21 @@ end;
            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*/
-    class Node {
+    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; 
+            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/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java
new file mode 100755 (executable)
index 0000000..88c94a6
--- /dev/null
@@ -0,0 +1,302 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.model;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hssf.record.CRNCountRecord;
+import org.apache.poi.hssf.record.CRNRecord;
+import org.apache.poi.hssf.record.CountryRecord;
+import org.apache.poi.hssf.record.ExternSheetRecord;
+import org.apache.poi.hssf.record.ExternSheetSubRecord;
+import org.apache.poi.hssf.record.ExternalNameRecord;
+import org.apache.poi.hssf.record.NameRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.SupBookRecord;
+
+/**
+ * Link Table (OOO pdf reference: 4.10.3 ) <p/>
+ *
+ * The main data of all types of references is stored in the Link Table inside the Workbook Globals
+ * Substream (4.2.5). The Link Table itself is optional and occurs only, if  there are any
+ * references in the document.
+ *  <p/>
+ *
+ *  In BIFF8 the Link Table consists of
+ *  <ul>
+ *  <li>one or more EXTERNALBOOK Blocks<p/>
+ *     each consisting of
+ *     <ul>
+ *     <li>exactly one EXTERNALBOOK (0x01AE) record</li>
+ *     <li>zero or more EXTERNALNAME (0x0023) records</li>
+ *     <li>zero or more CRN Blocks<p/>
+ *                     each consisting of
+ *             <ul>
+ *             <li>exactly one XCT (0x0059)record</li>
+ *             <li>zero or more CRN (0x005A) records (documentation says one or more)</li>
+ *             </ul>
+ *     </li>
+ *     </ul>
+ *  </li>
+ *  <li>exactly one EXTERNSHEET (0x0017) record</li>
+ *  <li>zero or more DEFINEDNAME (0x0018) records</li>
+ *  </ul>
+ *
+ *
+ * @author Josh Micich
+ */
+final class LinkTable {
+
+       private static final class CRNBlock {
+
+               private final CRNCountRecord _countRecord;
+               private final CRNRecord[] _crns;
+
+               public CRNBlock(RecordStream rs) {
+                       _countRecord = (CRNCountRecord) rs.getNext();
+                       int nCRNs = _countRecord.getNumberOfCRNs();
+                       CRNRecord[] crns = new CRNRecord[nCRNs];
+                       for (int i = 0; i < crns.length; i++) {
+                               crns[i] = (CRNRecord) rs.getNext();
+                       }
+                       _crns = crns;
+               }
+               public CRNRecord[] getCrns() {
+            return (CRNRecord[]) _crns.clone();
+        }
+       }
+
+       private static final class ExternalBookBlock {
+               private final SupBookRecord _externalBookRecord;
+               private final ExternalNameRecord[] _externalNameRecords;
+               private final CRNBlock[] _crnBlocks;
+
+               public ExternalBookBlock(RecordStream rs) {
+                       _externalBookRecord = (SupBookRecord) rs.getNext();
+                       List temp = new ArrayList();
+                       while(rs.peekNextClass() == ExternalNameRecord.class) {
+                          temp.add(rs.getNext());
+                       }
+                       _externalNameRecords = new ExternalNameRecord[temp.size()];
+                       temp.toArray(_externalNameRecords);
+
+                       temp.clear();
+
+                       while(rs.peekNextClass() == CRNCountRecord.class) {
+                               temp.add(new CRNBlock(rs));
+                       }
+                       _crnBlocks = new CRNBlock[temp.size()];
+                       temp.toArray(_crnBlocks);
+               }
+
+               public ExternalBookBlock(short numberOfSheets) {
+                       _externalBookRecord = SupBookRecord.createInternalReferences(numberOfSheets);
+                       _externalNameRecords = new ExternalNameRecord[0];
+                       _crnBlocks = new CRNBlock[0];
+               }
+
+               public SupBookRecord getExternalBookRecord() {
+                       return _externalBookRecord;
+               }
+
+               public String getNameText(int definedNameIndex) {
+                       return _externalNameRecords[definedNameIndex].getText();
+               }
+       }
+
+       private final ExternalBookBlock[] _externalBookBlocks;
+       private final ExternSheetRecord _externSheetRecord;
+       private final List _definedNames;
+       private final int _recordCount;
+       private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this
+
+       public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList) {
+
+               _workbookRecordList = workbookRecordList;
+               RecordStream rs = new RecordStream(inputList, startIndex);
+
+               List temp = new ArrayList();
+               while(rs.peekNextClass() == SupBookRecord.class) {
+                  temp.add(new ExternalBookBlock(rs));
+               }
+               if(temp.size() < 1) {
+                       throw new RuntimeException("Need at least one EXTERNALBOOK blocks");
+               }
+               _externalBookBlocks = new ExternalBookBlock[temp.size()];
+               temp.toArray(_externalBookBlocks);
+               temp.clear();
+
+               // If link table is present, there is always 1 of ExternSheetRecord
+               Record next = rs.getNext();
+               _externSheetRecord = (ExternSheetRecord)next;
+               _definedNames = new ArrayList();
+               // collect zero or more DEFINEDNAMEs id=0x18
+               while(rs.peekNextClass() == NameRecord.class) {
+                       NameRecord nr = (NameRecord)rs.getNext();
+                       _definedNames.add(nr);
+               }
+
+               _recordCount = rs.getCountRead();
+               _workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount));
+       }
+
+       public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) {
+               _workbookRecordList = workbookRecordList;
+               _definedNames = new ArrayList();
+               _externalBookBlocks = new ExternalBookBlock[] {
+                               new ExternalBookBlock(numberOfSheets),
+               };
+               _externSheetRecord = new ExternSheetRecord();
+               _recordCount = 2;
+
+               // tell _workbookRecordList about the 2 new records
+
+               SupBookRecord supbook = _externalBookBlocks[0].getExternalBookRecord();
+
+               int idx = findFirstRecordLocBySid(CountryRecord.sid);
+               if(idx < 0) {
+                       throw new RuntimeException("CountryRecord not found");
+               }
+               _workbookRecordList.add(idx+1, _externSheetRecord);
+               _workbookRecordList.add(idx+1, supbook);
+       }
+
+       /**
+        * TODO - would not be required if calling code used RecordStream or similar
+        */
+       public int getRecordCount() {
+               return _recordCount;
+       }
+
+
+       public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex) {
+
+               Iterator iterator = _definedNames.iterator();
+               while (iterator.hasNext()) {
+                       NameRecord record = ( NameRecord ) iterator.next();
+
+                       //print areas are one based
+                       if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
+                               return record;
+                       }
+               }
+
+               return null;
+       }
+
+       public void removeBuiltinRecord(byte name, int sheetIndex) {
+               //the name array is smaller so searching through it should be faster than
+               //using the findFirstXXXX methods
+               NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
+               if (record != null) {
+                       _definedNames.remove(record);
+               }
+               // TODO - do we need "Workbook.records.remove(...);" similar to that in Workbook.removeName(int namenum) {}?
+       }
+
+       public int getNumNames() {
+               return _definedNames.size();
+       }
+
+       public NameRecord getNameRecord(int index) {
+               return (NameRecord) _definedNames.get(index);
+       }
+
+       public void addName(NameRecord name) {
+               _definedNames.add(name);
+
+          // TODO - this is messy
+               // Not the most efficient way but the other way was causing too many bugs
+               int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
+               if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
+               if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
+               int countNames = _definedNames.size();
+               _workbookRecordList.add(idx+countNames, name);
+
+       }
+
+       public void removeName(int namenum) {
+               _definedNames.remove(namenum);
+       }
+
+       public short getIndexToSheet(short num) {
+               return _externSheetRecord.getREFRecordAt(num).getIndexToFirstSupBook();
+       }
+
+       public int getSheetIndexFromExternSheetIndex(int externSheetNumber) {
+               if (externSheetNumber >= _externSheetRecord.getNumOfREFStructures()) {
+            return -1;
+        }
+               return _externSheetRecord.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
+       }
+
+       public short addSheetIndexToExternSheet(short sheetNumber) {
+
+               ExternSheetSubRecord record = new ExternSheetSubRecord();
+               record.setIndexToFirstSupBook(sheetNumber);
+               record.setIndexToLastSupBook(sheetNumber);
+               _externSheetRecord.addREFRecord(record);
+               _externSheetRecord.setNumOfREFStructures((short)(_externSheetRecord.getNumOfREFStructures() + 1));
+               return (short)(_externSheetRecord.getNumOfREFStructures() - 1);
+       }
+
+       public short checkExternSheet(int sheetNumber) {
+
+               //Trying to find reference to this sheet
+               int nESRs = _externSheetRecord.getNumOfREFStructures();
+               for(short i=0; i< nESRs; i++) {
+                       ExternSheetSubRecord esr = _externSheetRecord.getREFRecordAt(i);
+
+                       if (esr.getIndexToFirstSupBook() ==  sheetNumber
+                               && esr.getIndexToLastSupBook() == sheetNumber){
+                               return i;
+                       }
+               }
+
+               //We Haven't found reference to this sheet
+               return addSheetIndexToExternSheet((short) sheetNumber);
+       }
+
+
+       /**
+        * copied from Workbook
+        */
+       private int findFirstRecordLocBySid(short sid) {
+               int index = 0;
+               for (Iterator iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) {
+                       Record record = ( Record ) iterator.next();
+
+                       if (record.getSid() == sid) {
+                               return index;
+                       }
+                       index ++;
+               }
+               return -1;
+       }
+
+       public int getNumberOfREFStructures() {
+               return _externSheetRecord.getNumOfREFStructures();
+       }
+
+       public String resolveNameXText(int refIndex, int definedNameIndex) {
+               short extBookIndex = _externSheetRecord.getREFRecordAt(refIndex).getIndexToSupBook();
+               return _externalBookBlocks[extBookIndex].getNameText(definedNameIndex);
+       }
+}
diff --git a/src/java/org/apache/poi/hssf/model/RecordStream.java b/src/java/org/apache/poi/hssf/model/RecordStream.java
new file mode 100755 (executable)
index 0000000..03177c7
--- /dev/null
@@ -0,0 +1,65 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.model;
+
+import java.util.List;
+
+import org.apache.poi.hssf.record.Record;
+/**
+ * Simplifies iteration over a sequence of <tt>Record</tt> objects.
+ *
+ * @author Josh Micich
+ */
+final class RecordStream {
+
+       private final List _list;
+       private int _nextIndex;
+       private int _countRead;
+
+       public RecordStream(List inputList, int startIndex) {
+               _list = inputList;
+               _nextIndex = startIndex;
+               _countRead = 0;
+       }
+
+       public boolean hasNext() {
+               return _nextIndex < _list.size();
+       }
+
+       public Record getNext() {
+               if(_nextIndex >= _list.size()) {
+                       throw new RuntimeException("Attempt to read past end of record stream");
+               }
+               _countRead ++;
+               return (Record) _list.get(_nextIndex++);
+       }
+
+       /**
+        * @return the {@link Class} of the next Record. <code>null</code> if this stream is exhausted.
+        */
+       public Class peekNextClass() {
+               if(_nextIndex >= _list.size()) {
+                       return null;
+               }
+               return _list.get(_nextIndex).getClass();
+       }
+
+       public int getCountRead() {
+               return _countRead;
+       }
+}
index 2ba50857ca6fbe39371e1c6aa3e40604e9b5b35b..9bf1c8f475d9cc45fc95d3947296b94ba54a9f34 100644 (file)
@@ -79,10 +79,8 @@ public class Workbook implements Model
      */
     protected SSTRecord        sst         = null;
 
-    /**
-     * Holds the Extern Sheet with references to bound sheets
-     */
-    protected ExternSheetRecord externSheet= null;
+
+    private LinkTable linkTable; // optionally occurs if there are  references in the document. (4.10.3)
 
     /**
      * holds the "boundsheet" records (aka bundlesheet) so that they can have their
@@ -92,8 +90,6 @@ public class Workbook implements Model
 
     protected ArrayList        formats = new ArrayList();
 
-    protected ArrayList        names = new ArrayList();
-    
     protected ArrayList        hyperlinks = new ArrayList();
 
     protected int              numxfs      = 0;   // hold the number of extended format records
@@ -134,6 +130,7 @@ public class Workbook implements Model
                     new Integer(recs.size()));
         Workbook  retval  = new Workbook();
         ArrayList records = new ArrayList(recs.size() / 3);
+        retval.records.setRecords(records);
 
         int k;
         for (k = 0; k < recs.size(); k++) {
@@ -192,21 +189,16 @@ public class Workbook implements Model
                     retval.records.setBackuppos( k );
                     break;
                 case ExternSheetRecord.sid :
-                    if (log.check( POILogger.DEBUG ))
-                        log.log(DEBUG, "found extern sheet record at " + k);
-                    retval.externSheet = ( ExternSheetRecord ) rec;
-                    break;
+                    throw new RuntimeException("Extern sheet is part of LinkTable");
                 case NameRecord.sid :
-                    if (log.check( POILogger.DEBUG ))
-                        log.log(DEBUG, "found name record at " + k);
-                    retval.names.add(rec);
-                    //                    retval.records.namepos = k;
-                    break;
+                    throw new RuntimeException("DEFINEDNAME is part of LinkTable");
                 case SupBookRecord.sid :
                     if (log.check( POILogger.DEBUG ))
                         log.log(DEBUG, "found SupBook record at " + k);
+                    retval.linkTable = new LinkTable(recs, k, retval.records);
                     //                    retval.records.supbookpos = k;
-                    break;
+                    k+=retval.linkTable.getRecordCount() - 1;
+                    continue;
                 case FormatRecord.sid :
                     if (log.check( POILogger.DEBUG ))
                         log.log(DEBUG, "found format record at " + k);
@@ -262,8 +254,6 @@ public class Workbook implements Model
                        break;
             }
         }
-
-        retval.records.setRecords(records);
         
         if (retval.windowOne == null) {
             retval.windowOne = (WindowOneRecord) retval.createWindowOne();
@@ -283,6 +273,7 @@ public class Workbook implements Model
             log.log( DEBUG, "creating new workbook from scratch" );
         Workbook retval = new Workbook();
         ArrayList records = new ArrayList( 30 );
+        retval.records.setRecords(records);
         ArrayList formats = new ArrayList( 8 );
 
         records.add( retval.createBOF() );
@@ -339,8 +330,9 @@ public class Workbook implements Model
             records.add( retval.createStyle( k ) );
         }
         records.add( retval.createUseSelFS() );
-        for ( int k = 0; k < 1; k++ )
-        {   // now just do 1
+
+        int nBoundSheets = 1; // now just do 1
+        for ( int k = 0; k < nBoundSheets; k++ ) {   
             BoundSheetRecord bsr =
                     (BoundSheetRecord) retval.createBoundSheet( k );
 
@@ -351,12 +343,14 @@ public class Workbook implements Model
 //        retval.records.supbookpos = retval.records.bspos + 1;
 //        retval.records.namepos = retval.records.supbookpos + 2;
         records.add( retval.createCountry() );
+        for ( int k = 0; k < nBoundSheets; k++ ) {   
+            retval.getOrCreateLinkTable().checkExternSheet(k);
+        }
         retval.sst = (SSTRecord) retval.createSST();
         records.add( retval.sst );
         records.add( retval.createExtendedSST() );
 
         records.add( retval.createEOF() );
-        retval.records.setRecords(records);
         if (log.check( POILogger.DEBUG ))
             log.log( DEBUG, "exit create new workbook from scratch" );
         return retval;
@@ -369,36 +363,20 @@ public class Workbook implements Model
         * @param sheetIndex Index to match
         * @return null if no builtin NameRecord matches
         */
-       public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
-       {
-           Iterator iterator = names.iterator();
-           while (iterator.hasNext()) {
-               NameRecord record = ( NameRecord ) iterator.next();
-       
-               //print areas are one based
-               if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
-                   return record;
-               }
-           }
-           
-           return null;
-           
-       }
+    public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
+    {
+        return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex);
+    }
 
        /**
         * Removes the specified Builtin NameRecord that matches the name and index
         * @param name byte representation of the builtin to match
         * @param sheetIndex zero-based sheet reference
         */
-       public void removeBuiltinRecord(byte name, int sheetIndex) {
-               //the name array is smaller so searching through it should be faster than
-               //using the findFirstXXXX methods
-               NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
-               if (record != null) {
-                       names.remove(record);
-               }
-               
-       }
+    public void removeBuiltinRecord(byte name, int sheetIndex) {
+        linkTable.removeBuiltinRecord(name, sheetIndex);
+        // TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}?
+    }
 
     public int getNumRecords() {
         return records.size();
@@ -614,6 +592,7 @@ public class Workbook implements Model
             records.add(records.getBspos()+1, bsr);
             records.setBspos( records.getBspos() + 1 );
             boundsheets.add(bsr);
+            getOrCreateLinkTable().checkExternSheet(sheetnum);
             fixTabIdRecord();
         }
     }
@@ -1824,14 +1803,26 @@ public class Workbook implements Model
     protected Record createEOF() {
         return new EOFRecord();
     }
+    
+    /**
+     * lazy initialization
+     * Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record
+     */
+    private LinkTable getOrCreateLinkTable() {
+        if(linkTable == null) {
+            linkTable = new LinkTable((short) getNumSheets(), records);
+        }
+        return linkTable;
+    }
 
     public SheetReferences getSheetReferences() {
         SheetReferences refs = new SheetReferences();
         
-        if (externSheet != null) {
-            for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) {
+        if (linkTable != null) {
+            int numRefStructures = linkTable.getNumberOfREFStructures();
+            for (short k = 0; k < numRefStructures; k++) {
                 
-                String sheetName = findSheetNameFromExternSheet((short)k);
+                String sheetName = findSheetNameFromExternSheet(k);
                 refs.addSheetReference(sheetName, k);
                 
             }
@@ -1846,7 +1837,8 @@ public class Workbook implements Model
     public String findSheetNameFromExternSheet(short num){
         String result="";
 
-        short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook();
+        short indexToSheet = linkTable.getIndexToSheet(num);
+        
         if (indexToSheet>-1) { //error check, bail out gracefully!
             result = getSheetName(indexToSheet);
         }
@@ -1861,10 +1853,7 @@ public class Workbook implements Model
      */
     public int getSheetIndexFromExternSheetIndex(int externSheetNumber)
     {
-        if (externSheetNumber >= externSheet.getNumOfREFStructures())
-            return -1;
-        else
-            return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
+        return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber);
     }
 
     /** returns the extern sheet number for specific sheet number ,
@@ -1873,58 +1862,17 @@ public class Workbook implements Model
      * @return index to extern sheet
      */
     public short checkExternSheet(int sheetNumber){
-
-        int i = 0;
-        boolean flag = false;
-        short result = 0;
-
-        if (externSheet == null) {
-            externSheet = createExternSheet();
-        }
-
-        //Trying to find reference to this sheet
-        while (i < externSheet.getNumOfREFStructures() && !flag){
-            ExternSheetSubRecord record = externSheet.getREFRecordAt(i);
-
-            if (record.getIndexToFirstSupBook() ==  sheetNumber &&
-            record.getIndexToLastSupBook() == sheetNumber){
-                flag = true;
-                result = (short) i;
-            }
-
-            ++i;
-        }
-
-        //We Havent found reference to this sheet
-        if (!flag) {
-            result = addSheetIndexToExternSheet((short) sheetNumber);
-        }
-
-        return result;
+        return getOrCreateLinkTable().checkExternSheet(sheetNumber);
     }
 
-    private short addSheetIndexToExternSheet(short sheetNumber){
-        short result;
-
-        ExternSheetSubRecord record = new ExternSheetSubRecord();
-        record.setIndexToFirstSupBook(sheetNumber);
-        record.setIndexToLastSupBook(sheetNumber);
-        externSheet.addREFRecord(record);
-        externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1));
-        result = (short)(externSheet.getNumOfREFStructures() - 1);
-
-        return result;
-    }
-
-
-
     /** gets the total number of names
      * @return number of names
      */
     public int getNumNames(){
-        int result = names.size();
-
-        return result;
+        if(linkTable == null) {
+            return 0;
+        }
+        return linkTable.getNumNames();
     }
 
     /** gets the name record
@@ -1932,28 +1880,14 @@ public class Workbook implements Model
      * @return name record
      */
     public NameRecord getNameRecord(int index){
-        NameRecord result = (NameRecord) names.get(index);
-
-        return result;
-
+        return linkTable.getNameRecord(index);
     }
 
     /** creates new name
      * @return new name record
      */
     public NameRecord createName(){
-
-        NameRecord name = new NameRecord();
-
-        // Not the most efficient way but the other way was causing too many bugs
-        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
-
-        records.add(idx+names.size()+1, name);
-        names.add(name);
-
-        return name;
+        return addName(new NameRecord());
     }
 
 
@@ -1962,67 +1896,41 @@ public class Workbook implements Model
      */
     public NameRecord addName(NameRecord name)
     {
-        // Not the most efficient way but the other way was causing too many bugs
-        int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
-        if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
-        records.add(idx+names.size()+1, name);
-        names.add(name);
+        
+        getOrCreateLinkTable().addName(name);
 
         return name;
     }
 
-       /**Generates a NameRecord to represent a built-in region
-        * @return a new NameRecord unless the index is invalid
-        */
-       public NameRecord createBuiltInName(byte builtInName, int index)
-       {
-               if (index == -1 || index+1 > (int)Short.MAX_VALUE) 
-                       throw new IllegalArgumentException("Index is not valid ["+index+"]");
-           
-               NameRecord name = new NameRecord(builtInName, (short)(index));
-                   
-               addName(name);
-           
-               return name;
-       }
+    /**Generates a NameRecord to represent a built-in region
+     * @return a new NameRecord unless the index is invalid
+     */
+    public NameRecord createBuiltInName(byte builtInName, int index)
+    {
+        if (index == -1 || index+1 > Short.MAX_VALUE) 
+            throw new IllegalArgumentException("Index is not valid ["+index+"]");
+        
+        NameRecord name = new NameRecord(builtInName, (short)(index));
+                
+        addName(name);
+        
+        return name;
+    }
 
 
     /** removes the name
      * @param namenum name index
      */
     public void removeName(int namenum){
-        if (names.size() > namenum) {
+        
+        if (linkTable.getNumNames() > namenum) {
             int idx = findFirstRecordLocBySid(NameRecord.sid);
             records.remove(idx + namenum);
-            names.remove(namenum);
+            linkTable.removeName(namenum);
         }
 
     }
 
-    /** creates a new extern sheet record
-     * @return the new extern sheet record
-     */
-    protected ExternSheetRecord createExternSheet(){
-        ExternSheetRecord externSheet = new ExternSheetRecord();
-
-        int idx = findFirstRecordLocBySid(CountryRecord.sid);
-
-        records.add(idx+1, externSheet);
-//        records.add(records.supbookpos + 1 , rec);
-
-        //We also adds the supBook for internal reference
-        SupBookRecord supbook = new SupBookRecord();
-
-        supbook.setNumberOfSheets((short)getNumSheets());
-        //supbook.setFlag();
-
-        records.add(idx+1, supbook);
-//        records.add(records.supbookpos + 1 , supbook);
-
-        return externSheet;
-    }
-
     /**
      * Returns a format index that matches the passed in format.  It does not tie into HSSFDataFormat.
      * @param format the format string
@@ -2419,5 +2327,14 @@ public class Workbook implements Model
         writeProtect = null;
     }
 
+    /**
+     * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table
+     * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record
+     * @return the string representation of the defined or external name
+     */
+    public String resolveNameXText(int refIndex, int definedNameIndex) {
+        return linkTable.resolveNameXText(refIndex, definedNameIndex);
+    }
 }
 
+
diff --git a/src/java/org/apache/poi/hssf/record/CRNCountRecord.java b/src/java/org/apache/poi/hssf/record/CRNCountRecord.java
new file mode 100755 (executable)
index 0000000..4c9e442
--- /dev/null
@@ -0,0 +1,94 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.util.LittleEndian;
+/**
+ * XCT \96 CRN Count <P>
+ *
+ * REFERENCE:  5.114<P>
+ *
+ * @author Josh Micich
+ */
+public final class CRNCountRecord extends Record {
+       public final static short sid = 0x59;
+
+       private static final short BASE_RECORD_SIZE = 4;
+
+
+       private int      field_1_number_crn_records;
+       private int      field_2_sheet_table_index;
+
+       public CRNCountRecord() {
+               throw new RuntimeException("incomplete code");
+       }
+
+       public CRNCountRecord(RecordInputStream in) {
+               super(in);
+       }
+
+       protected void validateSid(short id) {
+               if (id != sid) {
+                       throw new RecordFormatException("NOT An XCT RECORD");
+               }
+       }
+
+       public int getNumberOfCRNs() {
+               return field_1_number_crn_records;
+       }
+
+
+       protected void fillFields(RecordInputStream in) {
+               field_1_number_crn_records = in.readShort();
+               if(field_1_number_crn_records < 0) {
+                       // TODO - seems like the sign bit of this field might be used for some other purpose
+                       // see example file for test case "TestBugs.test19599()"
+                       field_1_number_crn_records = (short)-field_1_number_crn_records;
+               }
+               field_2_sheet_table_index = in.readShort();
+        }
+
+
+       public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(getClass().getName()).append(" [XCT");
+        sb.append(" nCRNs=").append(field_1_number_crn_records);
+        sb.append(" sheetIx=").append(field_2_sheet_table_index);
+        sb.append("]");
+        return sb.toString();
+       }
+
+       public int serialize(int offset, byte [] data) {
+               LittleEndian.putShort(data, 0 + offset, sid);
+               LittleEndian.putShort(data, 2 + offset, BASE_RECORD_SIZE);
+               LittleEndian.putShort(data, 4 + offset, (short)field_1_number_crn_records);
+               LittleEndian.putShort(data, 6 + offset, (short)field_2_sheet_table_index);
+               return getRecordSize();
+       }
+
+       public int getRecordSize() {
+               return BASE_RECORD_SIZE + 4;
+       }
+
+       /**
+        * return the non static version of the id for this record.
+        */
+       public short getSid() {
+               return sid;
+       }
+}
diff --git a/src/java/org/apache/poi/hssf/record/CRNRecord.java b/src/java/org/apache/poi/hssf/record/CRNRecord.java
new file mode 100755 (executable)
index 0000000..73b9e42
--- /dev/null
@@ -0,0 +1,99 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.hssf.record.constant.ConstantValueParser;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Title:       CRN  <P>
+ * Description: This record stores the contents of an external cell or cell range <P>
+ * REFERENCE:  5.23<P>
+ *
+ * @author josh micich
+ */
+public final class CRNRecord extends Record {
+       public final static short sid = 0x5A;
+
+       private int      field_1_last_column_index;
+       private int      field_2_first_column_index;
+       private int      field_3_row_index;
+       private Object[] field_4_constant_values;
+
+       public CRNRecord() {
+               throw new RuntimeException("incomplete code");
+       }
+
+       public CRNRecord(RecordInputStream in) {
+               super(in);
+       }
+
+       protected void validateSid(short id) {
+               if (id != sid) {
+                       throw new RecordFormatException("NOT An XCT RECORD");
+               }
+       }
+
+       public int getNumberOfCRNs() {
+               return field_1_last_column_index;
+       }
+
+
+       protected void fillFields(RecordInputStream in) {
+               field_1_last_column_index = in.readByte() & 0x00FF;
+               field_2_first_column_index = in.readByte() & 0x00FF;
+               field_3_row_index = in.readShort();
+               int nValues = field_1_last_column_index - field_2_first_column_index + 1;
+               field_4_constant_values = ConstantValueParser.parse(in, nValues);
+        }
+
+
+       public String toString() {
+               StringBuffer sb = new StringBuffer();
+               sb.append(getClass().getName()).append(" [CRN");
+               sb.append(" rowIx=").append(field_3_row_index);
+               sb.append(" firstColIx=").append(field_2_first_column_index);
+               sb.append(" lastColIx=").append(field_1_last_column_index);
+               sb.append("]");
+               return sb.toString();
+       }
+       private int getDataSize() {
+               return 4 + ConstantValueParser.getEncodedSize(field_4_constant_values);
+       }
+
+       public int serialize(int offset, byte [] data) {
+               int dataSize = getDataSize();
+               LittleEndian.putShort(data, 0 + offset, sid);
+               LittleEndian.putShort(data, 2 + offset, (short) dataSize);
+               LittleEndian.putByte(data, 4 + offset, field_1_last_column_index);
+               LittleEndian.putByte(data, 5 + offset, field_2_first_column_index);
+               LittleEndian.putShort(data, 6 + offset, (short) field_3_row_index);
+               return getRecordSize();
+       }
+
+       public int getRecordSize() {
+               return getDataSize() + 4;
+       }
+
+       /**
+        * return the non static version of the id for this record.
+        */
+       public short getSid() {
+               return sid;
+       }
+}
diff --git a/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java
new file mode 100755 (executable)
index 0000000..771603c
--- /dev/null
@@ -0,0 +1,179 @@
+/* ====================================================================
+   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;
+
+import java.util.List;
+import java.util.Stack;
+
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
+
+/**
+ * EXTERNALNAME<p/>
+ * 
+ * @author josh micich
+ */
+public final class ExternalNameRecord extends Record {
+
+    public final static short sid = 0x23; // as per BIFF8. (some old versions used 0x223)
+
+
+    private static final int OPT_BUILTIN_NAME           =   0x0001;
+    private static final int OPT_AUTOMATIC_LINK         =   0x0002;
+    private static final int OPT_PICTURE_LINK           =   0x0004;
+    private static final int OPT_STD_DOCUMENT_NAME      =   0x0008;
+    private static final int OPT_OLE_LINK               =   0x0010;
+//    private static final int OPT_CLIP_FORMAT_MASK       =   0x7FE0;
+    private static final int OPT_ICONIFIED_PICTURE_LINK =   0x8000;
+
+
+    private short             field_1_option_flag;
+    private short              field_2_index;
+    private short              field_3_not_used;
+    private String             field_4_name;
+    private Stack             field_5_name_definition;
+
+
+    public ExternalNameRecord(RecordInputStream in) {
+        super(in);
+    }
+
+       /**
+        * Convenience Function to determine if the name is a built-in name
+        */
+       public boolean isBuiltInName() {
+           return (field_1_option_flag & OPT_BUILTIN_NAME) != 0;
+       }
+    /**
+     * For OLE and DDE, links can be either 'automatic' or 'manual'
+     */
+    public boolean isAutomaticLink() {
+        return (field_1_option_flag & OPT_AUTOMATIC_LINK) != 0;
+    }
+    /**
+     * only for OLE and DDE
+     */
+    public boolean isPicureLink() {
+        return (field_1_option_flag & OPT_PICTURE_LINK) != 0;
+    }
+    /**
+     * DDE links only. If <code>true</code>, this denotes the 'StdDocumentName'
+     */
+    public boolean isStdDocumentNameIdentifier() {
+        return (field_1_option_flag & OPT_STD_DOCUMENT_NAME) != 0;
+    }
+    public boolean isOLELink() {
+        return (field_1_option_flag & OPT_OLE_LINK) != 0;
+    }
+    public boolean isIconifiedPictureLink() {
+        return (field_1_option_flag & OPT_ICONIFIED_PICTURE_LINK) != 0;
+    }
+    /**
+     * @return the standard String representation of this name
+     */
+    public String getText() {
+       return field_4_name;
+    }
+
+
+    /**
+     * called by constructor, should throw runtime exception in the event of a
+     * record passed with a differing ID.
+     *
+     * @param id alleged id for this record
+     */
+    protected void validateSid(short id) {
+        if (id != sid) {
+            throw new RecordFormatException("NOT A valid ExternalName RECORD");
+        }
+    }
+
+    private int getDataSize(){
+        return 2 + 2 + field_4_name.length() + 2 + getNameDefinitionSize();
+    }
+
+    /**
+     * called by the class that is responsible for writing this sucker.
+     * Subclasses should implement this so that their data is passed back in a
+     * byte array.
+     *
+     * @param offset to begin writing at
+     * @param data byte array containing instance data
+     * @return number of bytes written
+     */
+    public int serialize( int offset, byte[] data ) {
+       // TODO - junit tests
+        int dataSize = getDataSize();
+
+        LittleEndian.putShort( data, 0 + offset, sid );
+               LittleEndian.putShort( data, 2 + offset, (short) dataSize );
+        LittleEndian.putShort( data, 4 + offset, field_1_option_flag );
+        LittleEndian.putShort( data, 6 + offset, field_2_index );
+        LittleEndian.putShort( data, 8 + offset, field_3_not_used );
+        short nameLen = (short) field_4_name.length();
+               LittleEndian.putShort( data, 10 + offset, nameLen );
+        StringUtil.putCompressedUnicode( field_4_name, data, 10 + offset );
+        short defLen = (short) getNameDefinitionSize();
+        LittleEndian.putShort( data, 12 + nameLen + offset, defLen );
+               Ptg.serializePtgStack(field_5_name_definition, data, 12 + nameLen + offset );
+               return dataSize + 4;
+    }
+
+    private int getNameDefinitionSize() {
+       int result = 0;
+        List list   = field_5_name_definition;
+
+        for (int k = 0; k < list.size(); k++)
+        {
+               Ptg ptg = ( Ptg ) list.get(k);
+        
+               result += ptg.getSize();
+        }
+        return result;
+    }
+
+
+    public int getRecordSize(){
+        return 6 + 2 + field_4_name.length() + 2 + getNameDefinitionSize();
+    }
+
+
+    protected void fillFields(RecordInputStream in) {
+        field_1_option_flag             = in.readShort();
+        field_2_index                   = in.readShort();
+        field_3_not_used                = in.readShort();
+        short nameLength = in.readShort();
+        field_4_name                    = in.readCompressedUnicode(nameLength);
+        short formulaLen = in.readShort();
+               field_5_name_definition         = Ptg.createParsedExpressionTokens(formulaLen, in);
+    }
+
+    public short getSid() {
+        return sid;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(getClass().getName()).append(" [EXTERNALNAME ");
+        sb.append(" ").append(field_4_name);
+        sb.append(" ix=").append(field_2_index);
+        sb.append("]");
+        return sb.toString();
+    }
+}
index 0f164b44735ae4ac4c58df02af4dd2206bf37a97..f247a8d4b9b3d24435455183546ff9612cd50837 100644 (file)
@@ -77,7 +77,11 @@ public class RecordFactory
                 NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, 
                 FileSharingRecord.class, ChartTitleFormatRecord.class,
                 DVRecord.class, DVALRecord.class, UncalcedRecord.class,
-                HyperlinkRecord.class
+                HyperlinkRecord.class,
+                ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class
+                SupBookRecord.class,
+                CRNCountRecord.class,
+                CRNRecord.class,
             };
     }
     private static Map           recordsMap  = recordsToMap(records);
index 91a16f0793451561008b01d3efde216d58ea1ba4..6755aa6f8b5fc61e54cdad056536ad468bea056f 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
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
 
 package org.apache.poi.hssf.record;
 
+import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
 import org.apache.poi.util.LittleEndian;
 
 /**
- * Title:        Sup Book  <P>
- * Description:  A Extrenal Workbook Deciption (Sup Book)
+ * Title:        Sup Book (EXTERNALBOOK) <P>
+ * Description:  A External Workbook Description (Suplemental Book)
  *               Its only a dummy record for making new ExternSheet Record <P>
- * REFERENCE:  <P>
+ * REFERENCE:  5.38<P>
  * @author Libin Roman (Vista Portal LDT. Developer)
  * @author Andrew C. Oliver (acoliver@apache.org)
  *
  */
-public class SupBookRecord extends Record
-{
+public final class SupBookRecord extends Record {
+
     public final static short sid = 0x1AE;
-    private short             field_1_number_of_sheets;
-    private short             field_2_flag;
 
+    private static final short SMALL_RECORD_SIZE = 4;
+    private static final short TAG_INTERNAL_REFERENCES = 0x0401;
+    private static final short TAG_ADD_IN_FUNCTIONS = 0x3A01;
 
-    public SupBookRecord()
-    {
-        setFlag((short)0x401);
+    private short             field_1_number_of_sheets;
+    private UnicodeString     field_2_encoded_url;
+    private UnicodeString[]   field_3_sheet_names;
+    private boolean           _isAddInFunctions;
+
+    
+    public static SupBookRecord createInternalReferences(short numberOfSheets) {
+        return new SupBookRecord(false, numberOfSheets);
+    }
+    public static SupBookRecord createAddInFunctions() {
+        return new SupBookRecord(true, (short)0);
+    }
+    public static SupBookRecord createExternalReferences(UnicodeString url, UnicodeString[] sheetNames) {
+        return new SupBookRecord(url, sheetNames);
+    }
+    private SupBookRecord(boolean isAddInFuncs, short numberOfSheets) {
+        // else not 'External References'
+        field_1_number_of_sheets = numberOfSheets;
+        field_2_encoded_url = null;
+        field_3_sheet_names = null;
+        _isAddInFunctions = isAddInFuncs;
+    }
+    public SupBookRecord(UnicodeString url, UnicodeString[] sheetNames) {
+        field_1_number_of_sheets = (short) sheetNames.length;
+        field_2_encoded_url = url;
+        field_3_sheet_names = sheetNames;
+        _isAddInFunctions = false;
     }
 
     /**
      * Constructs a Extern Sheet record and sets its fields appropriately.
      *
-     * @param in the RecordInputstream to read the record from
+     * @param id     id must be 0x16 or an exception will be throw upon validation
+     * @param size  the size of the data area of the record
+     * @param data  data of the record (should not contain sid/len)
      */
-    public SupBookRecord(RecordInputStream in)
-    {
+    public SupBookRecord(RecordInputStream in) {
         super(in);
     }
 
-    protected void validateSid(short id)
-    {
-        if (id != sid)
-        {
-            throw new RecordFormatException("NOT An Supbook RECORD");
+    protected void validateSid(short id) {
+        if (id != sid) {
+            throw new RecordFormatException("NOT An ExternSheet RECORD");
         }
     }
 
+    public boolean isExternalReferences() {
+        return field_3_sheet_names != null;
+    }
+    public boolean isInternalReferences() {
+        return field_3_sheet_names == null && !_isAddInFunctions;
+    }
+    public boolean isAddInFunctions() {
+        return field_3_sheet_names == null && _isAddInFunctions;
+    }
     /**
-     * @param in the RecordInputstream to read the record from
+     * called by the constructor, should set class level fields.  Should throw
+     * runtime exception for bad/incomplete data.
+     *
+     * @param data raw data
+     * @param size size of data
+     * @param offset of the record's data (provided a big array of the file)
      */
-    protected void fillFields(RecordInputStream in)
-    {
-        //For now We use it only for one case
-        //When we need to add an named range when no named ranges was
-        //before it
+    protected void fillFields(RecordInputStream in) {
         field_1_number_of_sheets = in.readShort();
-        field_2_flag = in.readShort();
-    }
-
+        
+        if(in.getLength() > SMALL_RECORD_SIZE) {
+            // 5.38.1 External References
+            _isAddInFunctions = false;
+
+            field_2_encoded_url = in.readUnicodeString();
+            UnicodeString[] sheetNames = new UnicodeString[field_1_number_of_sheets];
+            for (int i = 0; i < sheetNames.length; i++) {
+                sheetNames[i] = in.readUnicodeString();
+            }
+            field_3_sheet_names = sheetNames;
+            return;
+        }
+        // else not 'External References'
+        field_2_encoded_url = null;
+        field_3_sheet_names = null;
+      
+        short nextShort = in.readShort();
+        if(nextShort == TAG_INTERNAL_REFERENCES) {
+            // 5.38.2 'Internal References'
+            _isAddInFunctions = false;
+        } else if(nextShort == TAG_ADD_IN_FUNCTIONS) {
+            // 5.38.3 'Add-In Functions'
+            _isAddInFunctions = true;
+            if(field_1_number_of_sheets != 1) {
+                throw new RuntimeException("Expected 0x0001 for number of sheets field in 'Add-In Functions' but got ("
+                     + field_1_number_of_sheets + ")");
+            }
+        } else {
+            throw new RuntimeException("invalid EXTERNALBOOK code (" 
+                     + Integer.toHexString(nextShort) + ")");
+        }
+     }
 
-    public String toString()
-    {
-        StringBuffer buffer = new StringBuffer();
-        buffer.append("[SUPBOOK]\n");
-        buffer.append("numberosheets = ").append(getNumberOfSheets()).append('\n');
-        buffer.append("flag          = ").append(getFlag()).append('\n');
-        buffer.append("[/SUPBOOK]\n");
-        return buffer.toString();
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(getClass().getName()).append(" [SUPBOOK ");
+        
+        if(isExternalReferences()) {
+            sb.append("External References");
+            sb.append(" nSheets=").append(field_1_number_of_sheets);
+            sb.append(" url=").append(field_2_encoded_url);
+        } else if(_isAddInFunctions) {
+            sb.append("Add-In Functions");
+        } else {
+            sb.append("Internal References ");
+            sb.append(" nSheets= ").append(field_1_number_of_sheets);
+        }
+        return sb.toString();
+    }
+    private int getDataSize() {
+        if(!isExternalReferences()) {
+            return SMALL_RECORD_SIZE;
+        }
+        int sum = 2; // u16 number of sheets
+        UnicodeRecordStats urs = new UnicodeRecordStats();
+        field_2_encoded_url.getRecordSize(urs);
+        sum += urs.recordSize;
+        
+        for(int i=0; i<field_3_sheet_names.length; i++) {
+            urs = new UnicodeRecordStats();
+            field_3_sheet_names[i].getRecordSize(urs);
+            sum += urs.recordSize;
+        }
+        return sum;
     }
 
     /**
@@ -92,14 +178,30 @@ public class SupBookRecord extends Record
      * @param data byte array containing instance data
      * @return number of bytes written
      */
-    public int serialize(int offset, byte [] data)
-    {
+    public int serialize(int offset, byte [] data) {
         LittleEndian.putShort(data, 0 + offset, sid);
-        LittleEndian.putShort(data, 2 + offset, (short) 4);
+        int dataSize = getDataSize();
+        LittleEndian.putShort(data, 2 + offset, (short) dataSize);
         LittleEndian.putShort(data, 4 + offset, field_1_number_of_sheets);
-        LittleEndian.putShort(data, 6 + offset, field_2_flag);
-
-        return getRecordSize();
+               
+        if(isExternalReferences()) {
+            
+            int currentOffset = 6 + offset;
+            UnicodeRecordStats urs = new UnicodeRecordStats();
+            field_2_encoded_url.serialize(urs, currentOffset, data);
+            currentOffset += urs.recordSize;
+            
+            for(int i=0; i<field_3_sheet_names.length; i++) {
+                urs = new UnicodeRecordStats();
+                field_3_sheet_names[i].serialize(urs, currentOffset, data);
+                currentOffset += urs.recordSize;
+            }
+        } else {
+            short field2val = _isAddInFunctions ? TAG_ADD_IN_FUNCTIONS : TAG_INTERNAL_REFERENCES;
+            
+            LittleEndian.putShort(data, 6 + offset, field2val);
+        }
+        return dataSize + 4;
     }
 
     public void setNumberOfSheets(short number){
@@ -110,21 +212,18 @@ public class SupBookRecord extends Record
         return field_1_number_of_sheets;
     }
 
-    public void setFlag(short flag){
-        field_2_flag = flag;
-    }
-
-    public short getFlag() {
-        return field_2_flag;
-    }
-
-    public int getRecordSize()
-    {
-        return 4 + 4;
+    public int getRecordSize() {
+        return getDataSize() + 4;
     }
 
     public short getSid()
     {
         return sid;
     }
+    public UnicodeString getURL() {
+        return field_2_encoded_url;
+    }
+    public UnicodeString[] getSheetNames() {
+        return (UnicodeString[]) field_3_sheet_names.clone();
+    }
 }
diff --git a/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java b/src/java/org/apache/poi/hssf/record/constant/ConstantValueParser.java
new file mode 100755 (executable)
index 0000000..6ec831e
--- /dev/null
@@ -0,0 +1,111 @@
+/* ====================================================================
+   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.constant;
+
+import org.apache.poi.hssf.record.RecordInputStream;
+import org.apache.poi.hssf.record.UnicodeString;
+import org.apache.poi.hssf.record.UnicodeString.UnicodeRecordStats;
+
+/**
+ * To support Constant Values (2.5.7) as required by the CRN record.
+ * This class should probably also be used for two dimensional arrays which are encoded by 
+ * EXTERNALNAME (5.39) records and Array tokens.<p/>
+ * TODO - code in ArrayPtg should be merged with this code.  It currently supports only 2 of the constant types
+ * 
+ * @author Josh Micich
+ */
+public final class ConstantValueParser {
+       // note - value 3 seems to be unused
+       private static final int TYPE_EMPTY = 0;
+       private static final int TYPE_NUMBER = 1;
+       private static final int TYPE_STRING = 2;
+       private static final int TYPE_BOOLEAN = 4; 
+       
+       private static final int TRUE_ENCODING = 1; 
+       private static final int FALSE_ENCODING = 0;
+       
+       // TODO - is this the best way to represent 'EMPTY'?
+       private static final Object EMPTY_REPRESENTATION = null;
+
+       private ConstantValueParser() {
+               // no instances of this class
+       }
+
+       public static Object[] parse(RecordInputStream in, int nValues) {
+        Object[] result = new Object[nValues];
+        for (int i = 0; i < result.length; i++) {
+                       result[i] = readAConstantValue(in);
+               }
+        return result;
+       }
+
+       private static Object readAConstantValue(RecordInputStream in) {
+               byte grbit = in.readByte();
+               switch(grbit) {
+                       case TYPE_EMPTY:
+                               in.readLong(); // 8 byte 'not used' field
+                               return EMPTY_REPRESENTATION; 
+                       case TYPE_NUMBER:
+                               return new Double(in.readDouble());
+                       case TYPE_STRING:
+                               return in.readUnicodeString();
+                       case TYPE_BOOLEAN:
+                               return readBoolean(in);
+               }
+               return null;
+       }
+
+       private static Object readBoolean(RecordInputStream in) {
+               byte val = in.readByte();
+               in.readLong(); // 8 byte 'not used' field
+               switch(val) {
+                       case FALSE_ENCODING:
+                               return Boolean.FALSE;
+                       case TRUE_ENCODING:
+                               return Boolean.TRUE;
+               }
+               // Don't tolerate unusual boolean encoded values (unless it becomes evident that they occur)
+               throw new RuntimeException("unexpected boolean encoding (" + val + ")");
+       }
+
+       public static int getEncodedSize(Object[] values) {
+               // start with one byte 'type' code for each value
+               int result = values.length * 1;
+               for (int i = 0; i < values.length; i++) {
+                       result += getEncodedSize(values[i]);
+               }
+               return 0;
+       }
+
+       /**
+        * @return encoded size without the 'type' code byte
+        */
+       private static int getEncodedSize(Object object) {
+               if(object == EMPTY_REPRESENTATION) {
+                       return 8;
+               }
+               Class cls = object.getClass();
+               if(cls == Boolean.class || cls == Double.class) {
+                       return 8;
+               }
+               UnicodeString strVal = (UnicodeString)object;
+               UnicodeRecordStats urs = new UnicodeRecordStats();
+               strVal.getRecordSize(urs);
+               return urs.recordSize;
+       }
+}
index 555c50294937425b21715aed8ff11c17e57f9248..c7c57280d32e5851d4509e8b1cd7a9e00374f4e1 100644 (file)
@@ -29,10 +29,13 @@ import org.apache.poi.hssf.model.Workbook;
  * @author Andrew C. Oliver (acoliver at apache dot org)
  */
 public abstract class AbstractFunctionPtg extends OperationPtg {
-       //constant used allow a ptgAttr to be mapped properly for its functionPtg
-       public static final String ATTR_NAME = "specialflag";
-           
-    public static final short INDEX_EXTERNAL = 255;
+
+    /**
+     * The name of the IF function (i.e. "IF").  Extracted as a constant for clarity.
+     */ 
+    public static final String FUNCTION_NAME_IF = "IF";
+    /** All external functions have function index 255 */
+    private static final short FUNCTION_INDEX_EXTERNAL = 255;
     
     private static BinaryTree map = produceHash(); 
     protected static Object[][] functionData = produceFunctionData();
@@ -66,6 +69,13 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
     public String getName() {
         return lookupName(field_2_fnc_index);
     }
+    /**
+     * external functions get some special processing
+     * @return <code>true</code> if this is an external function
+     */
+    public boolean isExternalFunction() {
+        return field_2_fnc_index == FUNCTION_INDEX_EXTERNAL;
+    }
     
     public String toFormulaString(Workbook book) {
         return getName();
@@ -73,39 +83,57 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
     
     public String toFormulaString(String[] operands) {
         StringBuffer buf = new StringBuffer();        
-          
-          if (field_2_fnc_index != 1) {
-              buf.append(getName());
-              buf.append('(');
-          }
-          if (operands.length >0) {
-              for (int i=0;i<operands.length;i++) {
-                  buf.append(operands[i]);
-                  buf.append(',');
-              }
-              buf.deleteCharAt(buf.length()-1);
-          }
-          if (field_2_fnc_index != 1) {
-            buf.append(")");
-          }
+        
+        if(isExternalFunction()) {
+            buf.append(operands[0]); // first operand is actually the function name
+            appendArgs(buf, 1, operands);
+        } else {
+            buf.append(getName());
+            appendArgs(buf, 0, operands);
+        }
         return buf.toString();
     }
+
+    private static void appendArgs(StringBuffer buf, int firstArgIx, String[] operands) {
+        buf.append('(');
+        for (int i=firstArgIx;i<operands.length;i++) {
+            if (i>firstArgIx) {
+                buf.append(',');
+            }
+            buf.append(operands[i]);
+        }
+        buf.append(")");
+    }
     
     public abstract void writeBytes(byte[] array, int offset);
     public abstract int getSize();
     
    
-    
-
+    /**
+     * Used to detect whether a function name found in a formula is one of the standard excel functions
+     * <p>
+     * The name matching is case insensitive.
+     * @return <code>true</code> if the name specifies a standard worksheet function, 
+     *  <code>false</code> if the name should be assumed to be an external function.
+     */
+    public static final boolean isInternalFunctionName(String name) {
+        return map.containsValue(name.toUpperCase());
+    }
     
     protected String lookupName(short index) {
         return ((String)map.get(new Integer(index))); 
     }
     
-    protected short lookupIndex(String name) {
-        Integer index = (Integer) map.getKeyForValue(name);
+    /**
+     * Resolves internal function names into function indexes.
+     * <p>
+     * The name matching is case insensitive.
+     * @return the standard worksheet function index if found, otherwise <tt>FUNCTION_INDEX_EXTERNAL</tt>
+     */
+    protected static short lookupIndex(String name) {
+        Integer index = (Integer) map.getKeyForValue(name.toUpperCase());
         if (index != null) return index.shortValue();
-        return INDEX_EXTERNAL;
+        return FUNCTION_INDEX_EXTERNAL;
     }
     
     /**
@@ -115,7 +143,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
         BinaryTree dmap = new BinaryTree();
 
         dmap.put(new Integer(0),"COUNT");
-        dmap.put(new Integer(1),"specialflag");
+        dmap.put(new Integer(1),FUNCTION_NAME_IF);
         dmap.put(new Integer(2),"ISNA");
         dmap.put(new Integer(3),"ISERROR");
         dmap.put(new Integer(4),"SUM");
@@ -354,7 +382,7 @@ public abstract class AbstractFunctionPtg extends OperationPtg {
         dmap.put(new Integer(252),"FREQUENCY");
         dmap.put(new Integer(253),"ADDTOOLBAR");
         dmap.put(new Integer(254),"DELETETOOLBAR");
-        dmap.put(new Integer(255),"externalflag");
+        dmap.put(new Integer(FUNCTION_INDEX_EXTERNAL),"externalflag");
         dmap.put(new Integer(256),"RESETTOOLBAR");
         dmap.put(new Integer(257),"EVALUATE");
         dmap.put(new Integer(258),"GETTOOLBAR");
index 257c089df8b297f48e1687772a9825578409ebf8..f4106b6aa8fc221e7ba949b4e5213157ab392d5d 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.
 ==================================================================== */
 
-
-/*
- * IntPtg.java
- *
- * Created on October 29, 2001, 7:37 PM
- */
 package org.apache.poi.hssf.record.formula;
 
 import org.apache.poi.util.LittleEndian;
@@ -29,64 +22,45 @@ import org.apache.poi.hssf.model.Workbook;
 import org.apache.poi.hssf.record.RecordInputStream;
 
 /**
- * Integer (unsigned short intger)
+ * Integer (unsigned short integer)
  * Stores an unsigned short value (java int) in a formula
  * @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 {
+    // 16 bit unsigned integer
+    private static final int MIN_VALUE = 0x0000;
+    private static final int MAX_VALUE = 0xFFFF;
+    
+    /**
+     * Excel represents integers 0..65535 with the tInt token. 
+     * @return <code>true</code> if the specified value is within the range of values 
+     * <tt>IntPtg</tt> can represent. 
+     */
+    public static boolean isInRange(int i) {
+        return i>=MIN_VALUE && i <=MAX_VALUE;
+    }
 
-public class IntPtg
-    extends Ptg
-{
     public final static int  SIZE = 3;
     public final static byte sid  = 0x1e;
     private int            field_1_value;
   
-    private IntPtg() {
-      //Required for clone methods
+    public IntPtg(RecordInputStream in) {
+        this(in.readUShort());
     }
 
-    public IntPtg(RecordInputStream in)
-    {
-        setValue(in.readUShort());
-    }
-    
-    
-    // IntPtg should be able to create itself, shouldnt have to call setValue
-    public IntPtg(String formulaToken) {
-        setValue(Integer.parseInt(formulaToken));
-    }
 
-    /**
-     * Sets the wrapped value.
-     * Normally you should call with a positive int.
-     */
-    public void setValue(int value)
-    {
-        if(value < 0 || value > (Short.MAX_VALUE + 1)*2 )
-            throw new IllegalArgumentException("Unsigned short is out of range: " + value);
+    public IntPtg(int value) {
+        if(!isInRange(value)) {
+            throw new IllegalArgumentException("value is out of range: " + value);
+        }
         field_1_value = value;
     }
 
-    /**
-     * Returns the value as a short, which may have
-     *  been wrapped into negative numbers
-     */
-    public int getValue()
-    {
+    public int getValue() {
         return field_1_value;
     }
 
-    /**
-     * Returns the value as an unsigned positive int.
-     */
-    public int getValueAsInt()
-    {
-       if(field_1_value < 0) {
-               return (Short.MAX_VALUE + 1)*2 + field_1_value;
-       }
-        return field_1_value;
-    }
 
     public void writeBytes(byte [] array, int offset)
     {
@@ -94,20 +68,25 @@ public class IntPtg
         LittleEndian.putUShort(array, offset + 1, getValue());
     }
 
-    public int getSize()
-    {
+    public int getSize() {
         return SIZE;
     }
 
-    public String toFormulaString(Workbook book)
-    {
-        return "" + getValue();
+    public String toFormulaString(Workbook book) {
+        return String.valueOf(getValue());
+    }
+    public byte getDefaultOperandClass() {
+        return Ptg.CLASS_VALUE;
     }
- public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}   
 
-   public Object clone() {
-     IntPtg ptg = new IntPtg();
-     ptg.field_1_value = field_1_value;
-     return ptg;
-   }
+    public Object clone() {
+     return new IntPtg(field_1_value);
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer(64);
+        sb.append(getClass().getName()).append(" [");
+        sb.append(field_1_value);
+        sb.append("]");
+        return sb.toString();
+    }
 }
index 01323c3a018c24b432e5fb554447be69507ec90a..5405481a09a315468c113d12f7769c0d112e3eb6 100644 (file)
@@ -33,6 +33,7 @@ public class NamePtg
 {
     public final static short sid  = 0x23;
     private final static int  SIZE = 5;
+    /** one-based index to defined name record */
     private short             field_1_label_index;
     private short             field_2_zero;   // reserved must be 0
     boolean xtra=false;
@@ -42,24 +43,32 @@ public class NamePtg
       //Required for clone methods
     }
 
-    /** Creates new NamePtg */
-
-    public NamePtg(String name, Workbook book)
-    {
-        final short n = (short) (book.getNumNames() + 1);
+    /**
+     * Creates new NamePtg and sets its name index to that of the corresponding defined name record
+     * in the workbook.  The search for the name record is case insensitive.  If it is not found, 
+     * it gets created.
+     */
+    public NamePtg(String name, Workbook book) {
+        field_1_label_index = (short)(1+getOrCreateNameRecord(book, name)); // convert to 1-based
+    }
+    /**
+     * @return zero based index of the found or newly created defined name record. 
+     */
+    private static final int getOrCreateNameRecord(Workbook book, String name) {
+        // perhaps this logic belongs in Workbook
+        int countNames = book.getNumNames();
         NameRecord rec;
-        for (short i = 1; i < n; i++) {
-            rec = book.getNameRecord(i - 1);
+        for (int i = 0; i < countNames; i++) {
+            rec = book.getNameRecord(i);
             if (name.equalsIgnoreCase(rec.getNameText())) {
-                field_1_label_index = i;
-                return;
+                return i; 
             }
         }
         rec = new NameRecord();
         rec.setNameText(name);
         rec.setNameTextLength((byte) name.length());
         book.addName(rec);
-        field_1_label_index = n;
+        return countNames;
     }
 
     /** Creates new NamePtg */
@@ -71,6 +80,13 @@ public class NamePtg
         field_2_zero        = in.readShort();
         //if (data[offset+6]==0) xtra=true;
     }
+    
+    /**
+     * @return zero based index to a defined name record in the LinkTable
+     */
+    public int getIndex() {
+        return field_1_label_index-1; // convert to zero based
+    }
 
     public void writeBytes(byte [] array, int offset)
     {
index b1f280a017fec032ca78a421a91b999597102aa3..ccf5ab6fcd342551ac34ae9352115b5e1d133c98 100644 (file)
@@ -25,13 +25,11 @@ import org.apache.poi.hssf.record.RecordInputStream;
  *
  * @author  aviks
  */
-
-public class NameXPtg extends Ptg
-{
+public final class NameXPtg extends Ptg {
     public final static short sid  = 0x39;
     private final static int  SIZE = 7;
-    private short             field_1_ixals;   // index to externsheet record
-    private short             field_2_ilbl;    //index to name or externname table(1 based)
+    private short             field_1_ixals;   // index to REF entry in externsheet record
+    private short             field_2_ilbl;    //index to defined name or externname table(1 based)
     private short            field_3_reserved;   // reserved must be 0
 
 
@@ -41,13 +39,6 @@ public class NameXPtg extends Ptg
 
     /** Creates new NamePtg */
 
-    public NameXPtg(String name)
-    {
-        //TODO
-    }
-
-    /** Creates new NamePtg */
-
     public NameXPtg(RecordInputStream in)
     {
         field_1_ixals        = in.readShort();
@@ -72,7 +63,8 @@ public class NameXPtg extends Ptg
 
     public String toFormulaString(Workbook book)
     {
-        return "NO IDEA - NAME";
+        // -1 to convert definedNameIndex from 1-based to zero-based
+        return book.resolveNameXText(field_1_ixals, field_2_ilbl-1); 
     }
     
     public byte getDefaultOperandClass() {return Ptg.CLASS_VALUE;}
index 05cee4e8d23a948ce8ad2cc01289395d48baaaed..17bae63ca7eaea76b6001bb4496710b6c3767490 100644 (file)
@@ -639,7 +639,7 @@ public class HSSFCell
             
             //only set to default if there is no extended format index already set
             if (rec.getXFIndex() == (short)0) rec.setXFIndex(( short ) 0x0f);
-            FormulaParser fp = new FormulaParser(formula+";",book);
+            FormulaParser fp = new FormulaParser(formulabook);
             fp.parse();
             Ptg[] ptg  = fp.getRPNPtg();
             int   size = 0;
index 9f7b17ad830c8d1b918db6dca1997542a470f240..8c7590d4eb5e1ee3a778ea7bd8aca63d4eb160d9 100644 (file)
@@ -400,7 +400,7 @@ public class HSSFSheet
 
        //formula fields ( size and data )
        String str_formula = obj_validation.getFirstFormula();
-       FormulaParser fp = new FormulaParser(str_formula+";",book);
+       FormulaParser fp = new FormulaParser(str_formulabook);
        fp.parse();
        Stack ptg_arr = new Stack();
        Ptg[] ptg  = fp.getRPNPtg();
@@ -424,7 +424,7 @@ public class HSSFSheet
        if ( obj_validation.getSecondFormula() != null )
        {
          str_formula = obj_validation.getSecondFormula();
-         fp = new FormulaParser(str_formula+";",book);
+         fp = new FormulaParser(str_formulabook);
          fp.parse();
          ptg_arr = new Stack();
          ptg  = fp.getRPNPtg();
index b883d71b186acc8a56bcfa435c398d507e0561fc..2278649cb89cf4e73fd4dcce2c773ed390043a49 100644 (file)
@@ -245,6 +245,16 @@ public class LittleEndian
         putNumber(data, offset, value, SHORT_SIZE);
     }
 
+    /**
+     * executes:<p/>
+     * <code>
+     * data[offset] = (byte)value;
+     * </code></p>
+     * Added for consistency with other put~() methods
+     */
+    public static void putByte(byte[] data, int offset, int value) {
+        putNumber(data, offset, value, LittleEndianConsts.BYTE_SIZE);
+    }
 
     /**
      *  put a array of shorts into a byte array
index 5bc0d3e9a96f2d7891037d08d0c5364247f9abc7..6562263d5474b8acda1fc5322e707fedc3c2ce10 100644 (file)
@@ -43,8 +43,6 @@ public class AddEval extends NumericOperationEval {
     private static final ValueEvalToNumericXlator NUM_XLATOR = 
         new ValueEvalToNumericXlator((short)
                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
                 | ValueEvalToNumericXlator.STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -59,33 +57,31 @@ public class AddEval extends NumericOperationEval {
     }
     
     
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
-        Eval retval = null;
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
+       
         double d = 0;
-        switch (operands.length) {
-        default: // will rarely happen. currently the parser itself fails.
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 2:
-            for (int i = 0, iSize = 2; retval==null && i < iSize; i++) {
-                ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol);
-                if (ve instanceof NumericValueEval) {
-                    d += ((NumericValueEval) ve).getNumberValue();
-                }
-                else if (ve instanceof BlankEval) {
-                    // do nothing
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            } // end for inside case
-        } // end switch
-        
-        if (retval == null) {
-            retval = Double.isNaN(d) ? (ValueEval) ErrorEval.VALUE_INVALID : new NumberEval(d);
+        for (int i = 0; i < 2; i++) {
+            ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol);
+            if(ve instanceof ErrorEval) {
+                               return ve;
+            }
+            if (ve instanceof NumericValueEval) {
+                d += ((NumericValueEval) ve).getNumberValue();
+            }
+            else if (ve instanceof BlankEval) {
+                // do nothing
+            }
+            else {
+                return ErrorEval.VALUE_INVALID;
+            }
         }
-
-        return retval;
+        if(Double.isNaN(d) || Double.isInfinite(d)) {
+               return ErrorEval.NUM_ERROR;
+        }
+        return new NumberEval(d);
     }
 
     public int getNumberOfOperands() {
index 2d8c58ef3ed5a5182778ec4cfb3a73c665bb9aa7..e54cd483f1972238d06d5fd74be2135be449c1f9 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.ConcatPtg;
@@ -27,7 +24,7 @@ import org.apache.poi.hssf.record.formula.Ptg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class ConcatEval extends StringOperationEval {
+public final class ConcatEval extends StringOperationEval {
 
     private ConcatPtg delegate;
 
@@ -35,36 +32,27 @@ public class ConcatEval extends StringOperationEval {
         this.delegate = (ConcatPtg) ptg;
     }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
-        Eval retval = null;
-        StringBuffer sb = null;
-        
-        switch (operands.length) {
-        default: // paranoid check :)
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 2:
-            sb = new StringBuffer();
-            for (int i = 0, iSize = 2; retval == null && i < iSize; i++) { 
-                
-                ValueEval ve = singleOperandEvaluate(operands[i], srcRow, srcCol);
-                if (ve instanceof StringValueEval) {
-                    StringValueEval sve = (StringValueEval) ve;
-                    sb.append(sve.getStringValue());
-                }
-                else if (ve instanceof BlankEval) {
-                    // do nothing
-                }
-                else { // must be an error eval
-                    retval = ve;
-                }
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < 2; i++) { 
+            
+            ValueEval ve = singleOperandEvaluate(args[i], srcRow, srcCol);
+            if (ve instanceof StringValueEval) {
+                StringValueEval sve = (StringValueEval) ve;
+                sb.append(sve.getStringValue());
+            }
+            else if (ve instanceof BlankEval) {
+                // do nothing
+            }
+            else { // must be an error eval
+                return ve;
             }
         }
         
-        if (retval == null) {
-            retval = new StringEval(sb.toString());
-        }
-        return retval;
+        return new StringEval(sb.toString());
     }
 
     public int getNumberOfOperands() {
index 6dd3db23de6af53ab1707900acf18e18298a1883..021168ad7907edbd771dfb5a367893cf36c8aa2b 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.DividePtg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class DivideEval extends NumericOperationEval {
+public final class DivideEval extends NumericOperationEval {
 
     private DividePtg delegate;
 
     private static final ValueEvalToNumericXlator NUM_XLATOR = 
         new ValueEvalToNumericXlator((short)
                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
                 | ValueEvalToNumericXlator.STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,18 +44,28 @@ public class DivideEval extends NumericOperationEval {
         return NUM_XLATOR;
     }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
         Eval retval = null;
         double d0 = 0;
         double d1 = 0;
-        switch (operands.length) {
-        default: // will rarely happen. currently the parser itself fails.
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 2:
-            ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+        ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d0 = ((NumericValueEval) ve).getNumberValue();
+        }
+        else if (ve instanceof BlankEval) {
+            // do nothing
+        }
+        else {
+            retval = ErrorEval.VALUE_INVALID;
+        }
+        
+        if (retval == null) { // no error yet
+            ve = singleOperandEvaluate(args[1], srcRow, srcCol);
             if (ve instanceof NumericValueEval) {
-                d0 = ((NumericValueEval) ve).getNumberValue();
+                d1 = ((NumericValueEval) ve).getNumberValue();
             }
             else if (ve instanceof BlankEval) {
                 // do nothing
@@ -68,20 +73,7 @@ public class DivideEval extends NumericOperationEval {
             else {
                 retval = ErrorEval.VALUE_INVALID;
             }
-            
-            if (retval == null) { // no error yet
-                ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
-                if (ve instanceof NumericValueEval) {
-                    d1 = ((NumericValueEval) ve).getNumberValue();
-                }
-                else if (ve instanceof BlankEval) {
-                    // do nothing
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-        } // end switch
+        }
 
         if (retval == null) {
             retval = (d1 == 0) 
index 56e4db1b22d9218094ae01e4b81464d3f8687a4b..e8e197d201588769ca68f6006ff4d1f7ec26d9ad 100644 (file)
@@ -48,12 +48,6 @@ public final class ErrorEval implements ValueEval {
     private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
     private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
 
-    /**
-     * @deprecated do not use this error code. For conditions that should never occur, throw an
-     *  unchecked exception. For all other situations use the error code that corresponds to the
-     *  error Excel would have raised under the same circumstances.
-     */
-    public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
     public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(FUNCTION_NOT_IMPLEMENTED_CODE);
     // Note - Excel does not seem to represent this condition with an error code
     public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE);
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java
new file mode 100755 (executable)
index 0000000..b1d81e6
--- /dev/null
@@ -0,0 +1,81 @@
+/* ====================================================================
+   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.eval;
+
+import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+/**
+ * 
+ * Common entry point for all external functions (where 
+ * <tt>AbstractFunctionPtg.field_2_fnc_index</tt> == 255)
+ * 
+ * @author Josh Micich
+ */
+final class ExternalFunction implements FreeRefFunction {
+
+       public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
+               
+               int nIncomingArgs = args.length;
+               if(nIncomingArgs < 1) {
+                       throw new RuntimeException("function name argument missing");
+               }
+               
+               if (!(args[0] instanceof NameEval)) {
+                       throw new RuntimeException("First argument should be a NameEval, but got ("
+                                       + args[0].getClass().getName() + ")");
+               }
+               NameEval functionNameEval = (NameEval) args[0];
+               
+               int nOutGoingArgs = nIncomingArgs -1;
+               Eval[] outGoingArgs = new Eval[nOutGoingArgs];
+               System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
+               
+               FreeRefFunction targetFunc;
+               try {
+                       targetFunc = findTargetFunction(workbook, functionNameEval);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+               
+               return targetFunc.evaluate(outGoingArgs, srcCellRow, srcCellCol, workbook, sheet);
+       }
+
+       private FreeRefFunction findTargetFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException {
+
+               int numberOfNames = workbook.getNumberOfNames();
+               
+               int nameIndex = functionNameEval.getIndex();
+               if(nameIndex < 0 || nameIndex >= numberOfNames) {
+                       throw new RuntimeException("Bad name index (" + nameIndex 
+                                       + "). Allowed range is (0.." + (numberOfNames-1) + ")");
+               }
+               
+               String functionName = workbook.getNameName(nameIndex);
+               if(false) {
+                       System.out.println("received call to external function index (" + functionName + ")");
+               }
+               // TODO - detect if the NameRecord corresponds to a named range, function, or something undefined
+               // throw the right errors in these cases
+               
+               // TODO find the implementation for the external function e.g. "YEARFRAC" or "ISEVEN"
+               
+               throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
+       }
+
+}
index d1c17b9f5442ad73c83c0cedd09605ede35122a6..533c604a0c77fec760dd5cb033825d2a8d9402af 100644 (file)
@@ -38,7 +38,8 @@ public abstract class FunctionEval implements OperationEval {
         public static final int OFFSET = 78;
         /** 148 */
         public static final int INDIRECT = 148;
-        
+        /** 255 */
+        public static final int EXTERNAL_FUNC = 255;
     }
     // convenient access to namespace
     private static final FunctionID ID = null;
@@ -51,6 +52,7 @@ public abstract class FunctionEval implements OperationEval {
         Map m = new HashMap();
         addMapping(m, ID.OFFSET, new Offset());
         addMapping(m, ID.INDIRECT, new Indirect());
+        addMapping(m, ID.EXTERNAL_FUNC, new ExternalFunction());
         freeRefFunctionsByIdMap = m;
     }
     private static void addMapping(Map m, int offset, FreeRefFunction frf) {
@@ -316,7 +318,7 @@ public abstract class FunctionEval implements OperationEval {
         retval[252] = new Frequency(); // FREQUENCY
         retval[253] = new NotImplementedFunction(); // ADDTOOLBAR
         retval[254] = new NotImplementedFunction(); // DELETETOOLBAR
-        retval[255] = new NotImplementedFunction(); // EXTERNALFLAG
+        retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction
         retval[256] = new NotImplementedFunction(); // RESETTOOLBAR
         retval[257] = new Evaluate(); // EVALUATE
         retval[258] = new NotImplementedFunction(); // GETTOOLBAR
index ecada85d562b8f4048cd1c28bc07727c0966d968..22d87b7e4d4f2dccec54eb8680c450127f9e9513 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.MultiplyPtg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class MultiplyEval extends NumericOperationEval {
+public final class MultiplyEval extends NumericOperationEval {
 
     private MultiplyPtg delegate;
 
     private static final ValueEvalToNumericXlator NUM_XLATOR = 
         new ValueEvalToNumericXlator((short)
                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
                 | ValueEvalToNumericXlator.STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,46 +44,39 @@ public class MultiplyEval extends NumericOperationEval {
         return NUM_XLATOR;
     }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
-        Eval retval = null;
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
+       
         double d0 = 0;
         double d1 = 0;
-        switch (operands.length) {
-        default: // will rarely happen. currently the parser itself fails.
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 2:
-            ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
-            if (ve instanceof NumericValueEval) {
-                d0 = ((NumericValueEval) ve).getNumberValue();
-            }
-            else if (ve instanceof BlankEval) {
-                // do nothing
-            }
-            else {
-                retval = ErrorEval.VALUE_INVALID;
-            }
-            
-            if (retval == null) { // no error yet
-                ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
-                if (ve instanceof NumericValueEval) {
-                    d1 = ((NumericValueEval) ve).getNumberValue();
-                }
-                else if (ve instanceof BlankEval) {
-                    // do nothing
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-        } // end switch
-
-        if (retval == null) {
-            retval = (Double.isNaN(d0) || Double.isNaN(d1)) 
-                    ? (ValueEval) ErrorEval.VALUE_INVALID 
-                    : new NumberEval(d0 * d1);
+        ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d0 = ((NumericValueEval) ve).getNumberValue();
+        }
+        else if (ve instanceof BlankEval) {
+            // do nothing
+        }
+        else {
+            return ErrorEval.VALUE_INVALID;
+        }
+        
+        ve = singleOperandEvaluate(args[1], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d1 = ((NumericValueEval) ve).getNumberValue();
+        }
+        else if (ve instanceof BlankEval) {
+        // do nothing
+        }
+        else {
+               return ErrorEval.VALUE_INVALID;
         }
-        return retval;
+        if (Double.isNaN(d0) || Double.isNaN(d1)) {
+                       return ErrorEval.NUM_ERROR;
+               }
+               return new NumberEval(d0 * d1);
     }
 
     public int getNumberOfOperands() {
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/NameEval.java
new file mode 100755 (executable)
index 0000000..682394b
--- /dev/null
@@ -0,0 +1,48 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record.formula.eval;
+
+/**
+ * @author Josh Micich
+ */
+public final class NameEval implements Eval {
+
+       private final int _index;
+
+       /**
+        * @param index zero based index to a defined name record
+        */
+       public NameEval(int index) {
+               _index = index;
+       }
+
+       /**
+        * @return zero based index to a defined name record
+        */
+       public int getIndex() {
+               return _index;
+       }
+
+       public String toString() {
+               StringBuffer sb = new StringBuffer(64);
+               sb.append(getClass().getName()).append(" [");
+               sb.append(_index);
+               sb.append("]");
+               return sb.toString();
+       }
+}
index 75a2bc46c3546b7805fb172a137fbd0c493f381a..be1cda5f8e3ab07d9f5e9d94b71a2447f5eb3c0f 100755 (executable)
@@ -17,7 +17,6 @@
 
 package org.apache.poi.hssf.record.formula.eval;
 
-
 /**
  * Provides functionality for evaluating arguments to functions and operators.
  * 
@@ -43,12 +42,10 @@ public final class OperandResolver {
         */
        public static ValueEval getSingleValue(Eval arg, int srcCellRow, short srcCellCol)
                        throws EvaluationException {
-               if (arg instanceof RefEval) {
-                       RefEval re = (RefEval) arg;
-                       return re.getInnerValueEval();
-               }
                Eval result;
-               if (arg instanceof AreaEval) {
+               if (arg instanceof RefEval) {
+                       result = ((RefEval) arg).getInnerValueEval();
+               } else if (arg instanceof AreaEval) {
                        result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
                } else {
                        result = arg;
@@ -223,13 +220,26 @@ public final class OperandResolver {
         *  Some examples:<br/> 
         *  " 123 " -&gt; 123.0<br/>
         *  ".123" -&gt; 0.123<br/>
+        *  These not supported yet:<br/>
         *  " $ 1,000.00 " -&gt; 1000.0<br/>
         *  "$1.25E4" -&gt; 12500.0<br/>
+        *  "5**2" -&gt; 500<br/>
+        *  "250%" -&gt; 2.5<br/>
         *  
         * @param text
         * @return <code>null</code> if the specified text cannot be parsed as a number
         */
-       public static Double parseDouble(String text) {
+       public static Double parseDouble(String pText) {
+               String text = pText.trim();
+               if(text.length() < 1) {
+                       return null;
+               }
+               boolean isPositive = true;
+               if(text.charAt(0) == '-') {
+                       isPositive = false;
+                       text= text.substring(1).trim();
+               }
+
                if(!Character.isDigit(text.charAt(0))) {
                        // avoid using NumberFormatException to tell when string is not a number
                        return null;
@@ -242,8 +252,26 @@ public final class OperandResolver {
                } catch (NumberFormatException e) {
                        return null;
                }
-               return new Double(val);
+               return new Double(isPositive ? +val : -val);
        }
        
-       
+       /**
+        * @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
+        * @return the converted string value. never <code>null</code>
+        */
+       public static String coerceValueToString(ValueEval ve) {
+               if (ve instanceof StringValueEval) {
+                       StringValueEval sve = (StringValueEval) ve;
+                       return sve.getStringValue();
+               }
+               if (ve instanceof NumberEval) {
+                       NumberEval neval = (NumberEval) ve;
+                       return neval.getStringValue();
+               } 
+
+               if (ve instanceof BlankEval) {
+                       return "";
+               }
+               throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
+       }
 }
index 437c24e40dac7954f13489f163ec7b9b5de0a206..651c5d2aa2c829b87984b7f25fc0ef227b6e960c 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.PowerPtg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class PowerEval extends NumericOperationEval {
+public final class PowerEval extends NumericOperationEval {
 
     private PowerPtg delegate;
 
     private static final ValueEvalToNumericXlator NUM_XLATOR = 
         new ValueEvalToNumericXlator((short)
                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
                 | ValueEvalToNumericXlator.STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,48 +44,40 @@ public class PowerEval extends NumericOperationEval {
         return NUM_XLATOR;
     }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
-        Eval retval = null;
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
         double d0 = 0;
         double d1 = 0;
         
-        switch (operands.length) {
-        default: // will rarely happen. currently the parser itself fails.
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 2:
-            ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
-            if (ve instanceof NumericValueEval) {
-                d0 = ((NumericValueEval) ve).getNumberValue();
-            }
-            else if (ve instanceof BlankEval) {
-                // do nothing
-            }
-            else {
-                retval = ErrorEval.VALUE_INVALID;
-            }
-            
-            if (retval == null) { // no error yet
-                ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
-                if (ve instanceof NumericValueEval) {
-                    d1 = ((NumericValueEval) ve).getNumberValue();
-                }
-                else if (ve instanceof BlankEval) {
-                    // do nothing
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-        } // end switch
-
-        if (retval == null) {
-            double p = Math.pow(d0, d1);
-            retval = (Double.isNaN(p)) 
-                    ? (ValueEval) ErrorEval.VALUE_INVALID 
-                    : new NumberEval(p);
+        ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d0 = ((NumericValueEval) ve).getNumberValue();
+        }
+        else if (ve instanceof BlankEval) {
+            // do nothing
+        }
+        else {
+            return ErrorEval.VALUE_INVALID;
         }
-        return retval;
+        
+        ve = singleOperandEvaluate(args[1], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d1 = ((NumericValueEval) ve).getNumberValue();
+        }
+        else if (ve instanceof BlankEval) {
+        // do nothing
+        }
+        else {
+            return ErrorEval.VALUE_INVALID;
+        }
+
+        double p = Math.pow(d0, d1);
+        if (Double.isNaN(p)) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               return new NumberEval(p);
     }
 
     public int getNumberOfOperands() {
index 7b24cb062b86e03241946eb2281e3c0e6bc57b29..4dd055c6c070f4c50ab209a1b6510f06f5522e90 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 9, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
-import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.ReferencePtg;
 
 /**
  * @author adeshmukh
  *  
  */
-public class Ref2DEval implements RefEval {
+public final class Ref2DEval implements RefEval {
 
-    private ValueEval value;
-
-    private ReferencePtg delegate;
+    private final ValueEval value;
+    private final ReferencePtg delegate;
     
-    private boolean evaluated;
-
-    public Ref2DEval(Ptg ptg, ValueEval value, boolean evaluated) {
-        this.value = value;
-        this.delegate = (ReferencePtg) ptg;
-        this.evaluated = evaluated;
+    public Ref2DEval(ReferencePtg ptg, ValueEval ve) {
+        value = ve;
+        delegate = ptg;
     }
-
     public ValueEval getInnerValueEval() {
         return value;
     }
-
     public short getRow() {
         return delegate.getRow();
     }
-
     public short getColumn() {
         return delegate.getColumn();
     }
-    
-    public boolean isEvaluated() {
-        return evaluated;
-    }
-
 }
index dcf8f0f712a815031dcb9a2bcb71a234c7e05167..7ecca26788e29d92458124976e5077d806495fb1 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 9, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
-import org.apache.poi.hssf.record.formula.Ptg;
 import org.apache.poi.hssf.record.formula.Ref3DPtg;
 
 /**
@@ -29,36 +25,23 @@ import org.apache.poi.hssf.record.formula.Ref3DPtg;
  */
 public final class Ref3DEval implements RefEval {
 
-    private ValueEval value;
+    private final ValueEval value;
+    private final Ref3DPtg delegate;
 
-    private Ref3DPtg delegate;
-
-    private boolean evaluated;
-
-    public Ref3DEval(Ptg ptg, ValueEval value, boolean evaluated) {
-        this.value = value;
-        this.delegate = (Ref3DPtg) ptg;
-        this.evaluated = evaluated;
+    public Ref3DEval(Ref3DPtg ptg, ValueEval ve) {
+        value = ve;
+        delegate = ptg;
     }
-
     public ValueEval getInnerValueEval() {
         return value;
     }
-
     public short getRow() {
         return delegate.getRow();
     }
-
     public short getColumn() {
         return delegate.getColumn();
     }
-    
-    public boolean isEvaluated() {
-        return evaluated;
-    }
-
     public int getExternSheetIndex() {
         return delegate.getExternSheetIndex();
     }
-
 }
index bb72adc4a05775fc9d687b414c2451a357db2cd6..48a94b024efc0dba5d4dd80927d9bbc7e33c5bc7 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 9, 2005
- *
- * 
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 /**
@@ -52,18 +48,4 @@ public interface RefEval extends ValueEval {
      * returns the row index.
      */
     public short getRow();
-    
-    /**
-     * returns true if this RefEval contains an
-     * evaluated value instead of a direct value.
-     * eg. say cell A1 has the value: ="test"
-     * Then the RefEval representing A1 will return
-     * isEvaluated() equal to false. On the other
-     * hand, say cell A1 has the value: =B1 and
-     * B1 has the value "test", then the RefEval
-     * representing A1 will return isEvaluated()
-     * equal to true.
-     */
-    public boolean isEvaluated();
-
 }
index 4bd77029f73fe2b27de6893cbee11a73a8ee0743..85a3845299a49686b8308b5832a601875d675cf7 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,15 +24,13 @@ import org.apache.poi.hssf.record.formula.SubtractPtg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class SubtractEval extends NumericOperationEval {
+public final class SubtractEval extends NumericOperationEval {
 
     private SubtractPtg delegate;
 
     private static final ValueEvalToNumericXlator NUM_XLATOR = 
         new ValueEvalToNumericXlator((short)
                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
                 | ValueEvalToNumericXlator.STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,18 +44,28 @@ public class SubtractEval extends NumericOperationEval {
         return NUM_XLATOR;
     }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
         Eval retval = null;
         double d0 = 0;
         double d1 = 0;
-        switch (operands.length) {
-        default: // will rarely happen. currently the parser itself fails.
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 2:
-            ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+        ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d0 = ((NumericValueEval) ve).getNumberValue();
+        }
+        else if (ve instanceof BlankEval) {
+            // do nothing
+        }
+        else {
+            retval = ErrorEval.VALUE_INVALID;
+        }
+        
+        if (retval == null) { // no error yet
+            ve = singleOperandEvaluate(args[1], srcRow, srcCol);
             if (ve instanceof NumericValueEval) {
-                d0 = ((NumericValueEval) ve).getNumberValue();
+                d1 = ((NumericValueEval) ve).getNumberValue();
             }
             else if (ve instanceof BlankEval) {
                 // do nothing
@@ -68,21 +73,8 @@ public class SubtractEval extends NumericOperationEval {
             else {
                 retval = ErrorEval.VALUE_INVALID;
             }
-            
-            if (retval == null) { // no error yet
-                ve = singleOperandEvaluate(operands[1], srcRow, srcCol);
-                if (ve instanceof NumericValueEval) {
-                    d1 = ((NumericValueEval) ve).getNumberValue();
-                }
-                else if (ve instanceof BlankEval) {
-                    // do nothing
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-        } // end switch
-
+        }
         if (retval == null) {
             retval = (Double.isNaN(d0) || Double.isNaN(d1)) 
                     ? (ValueEval) ErrorEval.VALUE_INVALID 
index b4975eefcf947e658ac923a14031b25974cd234a..ef6f533ea52d3afff1c69ad0549ffffea3662dc5 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,14 +24,12 @@ import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class UnaryMinusEval extends NumericOperationEval {
+public final class UnaryMinusEval extends NumericOperationEval {
 
     private UnaryMinusPtg delegate;
     private static final ValueEvalToNumericXlator NUM_XLATOR = 
         new ValueEvalToNumericXlator((short)
                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
                 | ValueEvalToNumericXlator.STRING_IS_PARSED
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED
@@ -49,32 +44,24 @@ public class UnaryMinusEval extends NumericOperationEval {
         return NUM_XLATOR;
     }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
-        ValueEval retval = null;
+    public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
+       if(args.length != 1) {
+               return ErrorEval.VALUE_INVALID;
+       }
         double d = 0;
         
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 1:
-            ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
-            if (ve instanceof NumericValueEval) {
-                d = ((NumericValueEval) ve).getNumberValue();
-            }
-            else if (ve instanceof BlankEval) {
-                // do nothing
-            }
-            else if (ve instanceof ErrorEval) {
-                retval = ve;
-            }
+        ValueEval ve = singleOperandEvaluate(args[0], srcRow, srcCol);
+        if (ve instanceof NumericValueEval) {
+            d = ((NumericValueEval) ve).getNumberValue();
         }
-        
-        if (retval == null) {
-            retval = new NumberEval(-d);
+        else if (ve instanceof BlankEval) {
+            // do nothing
         }
-
-        return retval;
+        else if (ve instanceof ErrorEval) {
+            return ve;
+        }
+        
+        return new NumberEval(-d);
     }
 
     public int getNumberOfOperands() {
index 7816b228f7814d6fb4109227056aca0d5a3f720b..edcc7bee790a84cd7dc88a73cdb7fc75a256ba4c 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 8, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.eval;
 
 import org.apache.poi.hssf.record.formula.Ptg;
@@ -27,114 +24,38 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class UnaryPlusEval implements OperationEval /*extends NumericOperationEval*/ {
+public final class UnaryPlusEval implements OperationEval {
 
     private UnaryPlusPtg delegate;
     
-    /* 
-     * COMMENT FOR COMMENTED CODE IN THIS FILE
-     * 
-     * In excel the programmer seems to not have cared to
-     * think about how strings were handled in other numeric
-     * operations when he/she was implementing this operation :P
-     * 
-     * Here's what I mean:
-     * 
-     * Q. If the formula -"hello" evaluates to #VALUE! in excel, what should
-     * the formula +"hello" evaluate to?
-     * 
-     * A. +"hello" evaluates to "hello" (what the...?)
-     * 
-     */
-
-    
-//    private static final ValueEvalToNumericXlator NUM_XLATOR = 
-//        new ValueEvalToNumericXlator((short)
-//                ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
-//                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
-//                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
-//                | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
-//                | ValueEvalToNumericXlator.STRING_IS_PARSED
-//                ));
-
-
     /**
      * called by reflection
      */
     public UnaryPlusEval(Ptg ptg) {
         this.delegate = (UnaryPlusPtg) ptg;
     }
-    
-//    protected ValueEvalToNumericXlator getXlator() {
-//        return NUM_XLATOR;
-//    }
 
-    public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
-        ValueEval retval = null;
-        
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.UNKNOWN_ERROR;
-            break;
-        case 1:
-
-//            ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
-//            if (ve instanceof NumericValueEval) {
-//                d = ((NumericValueEval) ve).getNumberValue();
-//            }
-//            else if (ve instanceof BlankEval) {
-//                // do nothing
-//            }
-//            else if (ve instanceof ErrorEval) {
-//                retval = ve;
-//            }
-            if (operands[0] instanceof RefEval) {
-                RefEval re = (RefEval) operands[0];
-                retval = re.getInnerValueEval();
-            }
-            else if (operands[0] instanceof AreaEval) {
-                AreaEval ae = (AreaEval) operands[0];
-                if (ae.contains(srcRow, srcCol)) { // circular ref!
-                    retval = ErrorEval.CIRCULAR_REF_ERROR;
-                }
-                else if (ae.isRow()) {
-                    if (ae.containsColumn(srcCol)) {
-                        ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
-                        if (ve instanceof RefEval) {
-                            ve = ((RefEval) ve).getInnerValueEval();
-                        }
-                        retval = ve;
-                    }
-                    else {
-                        retval = ErrorEval.VALUE_INVALID;
-                    }
-                }
-                else if (ae.isColumn()) {
-                    if (ae.containsRow(srcRow)) {
-                        ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn());
-                        if (ve instanceof RefEval) {
-                            ve = ((RefEval) ve).getInnerValueEval();
-                        }
-                        retval = ve;
-                    }
-                    else {
-                        retval = ErrorEval.VALUE_INVALID;
-                    }
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-            else {
-                retval = (ValueEval) operands[0];
-            }
-        }
-        
-        if (retval instanceof BlankEval) {
-            retval = new NumberEval(0);
-        }
-
-        return retval;
+    public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+       if(args.length != 1) {
+               return ErrorEval.VALUE_INVALID;
+       }
+       double d;
+               try {
+                       ValueEval ve = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+                       if(ve instanceof BlankEval) {
+                               return NumberEval.ZERO;
+                       }
+                       if(ve instanceof StringEval) {
+                               // Note - asymmetric with UnaryMinus
+                               // -"hello" evaluates to #VALUE!
+                               // but +"hello" evaluates to "hello"
+                               return ve;
+                       }
+                       d = OperandResolver.coerceValueToDouble(ve);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+       return new NumberEval(+d);      
     }
 
     public int getNumberOfOperands() {
@@ -144,5 +65,4 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
     public int getType() {
         return delegate.getType();
     }
-
 }
index 5ffa2faeef73515f3bbe56faa6f8a59bcc49937a..1abcf34d2b90105392de7543390705e4d2a9a836 100644 (file)
@@ -24,7 +24,7 @@ package org.apache.poi.hssf.record.formula.eval;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *
  */
-public class ValueEvalToNumericXlator {
+public final class ValueEvalToNumericXlator {
 
     public static final int STRING_IS_PARSED = 0x0001;
     public static final int BOOL_IS_PARSED = 0x0002;
@@ -34,26 +34,18 @@ public class ValueEvalToNumericXlator {
     public static final int REF_BOOL_IS_PARSED = 0x0010;
     public static final int REF_BLANK_IS_PARSED = 0x0020;
     
-    public static final int EVALUATED_REF_STRING_IS_PARSED = 0x0040;
-    public static final int EVALUATED_REF_BOOL_IS_PARSED = 0x0080;
-    public static final int EVALUATED_REF_BLANK_IS_PARSED = 0x0100;
-    
-    public static final int STRING_TO_BOOL_IS_PARSED = 0x0200;
-    public static final int REF_STRING_TO_BOOL_IS_PARSED = 0x0400;
-    
     public static final int STRING_IS_INVALID_VALUE = 0x0800;
-    public static final int REF_STRING_IS_INVALID_VALUE = 0x1000;
-    
-//    public static final int BOOL_IS_BLANK = 0x2000;
-//    public static final int REF_BOOL_IS_BLANK = 0x4000;
-//    public static final int STRING_IS_BLANK = 0x8000;
-//    public static final int REF_STRING_IS_BLANK = 0x10000;
     
     private final int flags;
     
     
     public ValueEvalToNumericXlator(int flags) {
-        this.flags = flags;
+       
+       if (false) { // uncomment to see who is using this class
+                       System.err.println(new Throwable().getStackTrace()[1].getClassName() + "\t0x"
+                                       + Integer.toHexString(flags).toUpperCase());
+               }
+               this.flags = flags;
     }
     
     /**
@@ -71,7 +63,7 @@ public class ValueEvalToNumericXlator {
         
         // most common case - least worries :)
         else if (eval instanceof NumberEval) {
-            retval = (NumberEval) eval; 
+            retval = eval; 
         }
         
         // booleval
@@ -125,50 +117,33 @@ public class ValueEvalToNumericXlator {
      * @param eval
      */
     private ValueEval xlateRefEval(RefEval reval) {
-        ValueEval retval = null;
-        ValueEval eval = (ValueEval) reval.getInnerValueEval();
+        ValueEval eval = reval.getInnerValueEval();
         
         // most common case - least worries :)
         if (eval instanceof NumberEval) {
-            retval = (NumberEval) eval;
+            return eval;
         }
         
-        // booleval
-        else if (eval instanceof BoolEval) {
-            retval = ((flags & REF_BOOL_IS_PARSED) > 0)
+        if (eval instanceof BoolEval) {
+            return ((flags & REF_BOOL_IS_PARSED) > 0)
                     ? (ValueEval) eval
                     : BlankEval.INSTANCE;
         } 
         
-        // stringeval 
-        else if (eval instanceof StringEval) {
-            retval = xlateRefStringEval((StringEval) eval);
-        }
-        
-        // erroreval
-        else if (eval instanceof ErrorEval) {
-            retval = eval;
-        }
-        
-        // refeval
-        else if (eval instanceof RefEval) {
-            RefEval re = (RefEval) eval;
-            retval = xlateRefEval(re);
+        if (eval instanceof StringEval) {
+            return xlateRefStringEval((StringEval) eval);
         }
         
-        else if (eval instanceof BlankEval) {
-            retval = xlateBlankEval(reval.isEvaluated() ? EVALUATED_REF_BLANK_IS_PARSED : REF_BLANK_IS_PARSED);
+        if (eval instanceof ErrorEval) {
+            return eval;
         }
         
-        // probably AreaEval ? then not acceptable.
-        else { 
-            throw new RuntimeException("Invalid ValueEval type passed for conversion: " + eval.getClass());
+        if (eval instanceof BlankEval) {
+            return xlateBlankEval(REF_BLANK_IS_PARSED);
         }
-
         
-        
-
-        return retval;
+        throw new RuntimeException("Invalid ValueEval type passed for conversion: ("
+                       + eval.getClass().getName() + ")");
     }
     
     /**
@@ -176,93 +151,38 @@ public class ValueEvalToNumericXlator {
      * @param eval
      */
     private ValueEval xlateStringEval(StringEval eval) {
-        ValueEval retval = null;
+
         if ((flags & STRING_IS_PARSED) > 0) {
             String s = eval.getStringValue();
-            try { 
-                double d = Double.parseDouble(s);
-                retval = new NumberEval(d);
-            } 
-            catch (Exception e) {
-                if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) {
-                    try { 
-                        boolean b = Boolean.getBoolean(s);
-                        retval = b ? BoolEval.TRUE : BoolEval.FALSE;
-                    } 
-                    catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; }
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
+            Double d = OperandResolver.parseDouble(s);
+            if(d == null) {
+                return ErrorEval.VALUE_INVALID;
             }
+            return new NumberEval(d.doubleValue());
         }
-        else if ((flags & STRING_TO_BOOL_IS_PARSED) > 0) {
-            String s = eval.getStringValue();
-            try { 
-                boolean b = Boolean.getBoolean(s);
-                retval = b ? BoolEval.TRUE : BoolEval.FALSE;
-            } 
-            catch (Exception e) { retval = ErrorEval.VALUE_INVALID; }
-        }
-        
         // strings are errors?
-        else if ((flags & STRING_IS_INVALID_VALUE) > 0) {
-            retval = ErrorEval.VALUE_INVALID;
+        if ((flags & STRING_IS_INVALID_VALUE) > 0) {
+            return ErrorEval.VALUE_INVALID;
         }
         
         // ignore strings
-        else {
-            retval = xlateBlankEval(BLANK_IS_PARSED);
-        }
-        return retval;
+        return xlateBlankEval(BLANK_IS_PARSED);
     }
     
     /**
      * uses the relevant flags to decode the StringEval
      * @param eval
      */
-    private ValueEval xlateRefStringEval(StringEval eval) {
-        ValueEval retval = null;
+    private ValueEval xlateRefStringEval(StringEval sve) {
         if ((flags & REF_STRING_IS_PARSED) > 0) {
-            StringEval sve = (StringEval) eval;
             String s = sve.getStringValue();
-            try { 
-                double d = Double.parseDouble(s);
-                retval = new NumberEval(d);
-            } 
-            catch (Exception e) { 
-                if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) {
-                    try { 
-                        boolean b = Boolean.getBoolean(s);
-                        retval = b ? BoolEval.TRUE : BoolEval.FALSE;
-                    } 
-                    catch (Exception e2) { retval = ErrorEval.VALUE_INVALID; }
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
+            Double d = OperandResolver.parseDouble(s);
+            if(d == null) {
+                return ErrorEval.VALUE_INVALID;
             }
+            return new NumberEval(d.doubleValue());
         }
-        else if ((flags & REF_STRING_TO_BOOL_IS_PARSED) > 0) {
-            StringEval sve = (StringEval) eval;
-            String s = sve.getStringValue();
-            try { 
-                boolean b = Boolean.getBoolean(s);
-                retval = b ? BoolEval.TRUE : BoolEval.FALSE;;
-            } 
-            catch (Exception e) { retval = ErrorEval.VALUE_INVALID; }
-        }
-        
-        // strings are errors?
-        else if ((flags & REF_STRING_IS_INVALID_VALUE) > 0) {
-            retval = ErrorEval.VALUE_INVALID;
-        }
-        
         // strings are blanks
-        else {
-            retval = BlankEval.INSTANCE;
-        }
-        return retval;
+        return BlankEval.INSTANCE;
     }
-    
 }
index 592402b80d20c60e846615f3162a8c5a3c907019..bf888b97df41d76bca89213d2e5eb66652ec737f 100644 (file)
@@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
 public class Avedev extends MultiOperandNumericFunction {
 
     private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
-        new ValueEvalToNumericXlator((short) (0
-              //    ValueEvalToNumericXlator.BOOL_IS_PARSED  
+        new ValueEvalToNumericXlator((short) (
+                  ValueEvalToNumericXlator.BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
@@ -44,7 +44,6 @@ public class Avedev extends MultiOperandNumericFunction {
               //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
               //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
-                | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
                 ));
     
     /**
index 3491109177e8c50e11f9d4a14da8953e7aa8ce36..4043040713164d77e93d1127e1cfe3b5254ab0ce 100644 (file)
@@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
 public class Average extends MultiOperandNumericFunction {
 
     private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
-        new ValueEvalToNumericXlator((short) (0
-              //    ValueEvalToNumericXlator.BOOL_IS_PARSED  
+        new ValueEvalToNumericXlator((short) (
+                    ValueEvalToNumericXlator.BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
@@ -44,7 +44,6 @@ public class Average extends MultiOperandNumericFunction {
               //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
               //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
-                | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
                 ));
     
     /**
index 8eb7e841d6709625af2728caed8eefe5477944b5..c054c6dac4dcc5b6839eab4fc980b89015dc6809 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on Jun 20, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.BoolEval;
@@ -38,13 +35,10 @@ public abstract class FinanceFunction extends NumericFunction {
         new ValueEvalToNumericXlator((short) (0
                 | ValueEvalToNumericXlator.BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
                 | ValueEvalToNumericXlator.BLANK_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_BLANK_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
               //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
@@ -56,11 +50,11 @@ public abstract class FinanceFunction extends NumericFunction {
      * if they desire to return a different ValueEvalToNumericXlator instance
      * than the default.
      */
-    protected ValueEvalToNumericXlator getXlator() {
+    protected final ValueEvalToNumericXlator getXlator() {
         return DEFAULT_NUM_XLATOR;
     }
     
-    protected ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) {
+    protected final ValueEval singleOperandNumericAsBoolean(Eval eval, int srcRow, short srcCol) {
         ValueEval retval = null;
         retval = singleOperandEvaluate(eval, srcRow, srcCol);
         if (retval instanceof NumericValueEval) {
@@ -74,5 +68,4 @@ public abstract class FinanceFunction extends NumericFunction {
         }
         return retval;
     }
-
 }
index 90dbd591b75d33b58900fd1a6ce1a840d4775540..7aba5db72af837f8b7b355927669c6cf13553e19 100644 (file)
@@ -28,28 +28,22 @@ import org.apache.poi.hssf.record.formula.eval.Eval;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  * 
  */
-public class If implements Function {
+public final class If implements Function {
+
+    public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
 
-    public Eval evaluate(Eval[] evals, int srcCellRow, short srcCellCol) {
-        Eval retval = null;
         Eval evalWhenFalse = BoolEval.FALSE;
-        switch (evals.length) {
+        switch (args.length) {
         case 3:
-            evalWhenFalse = evals[2];
+            evalWhenFalse = args[2];
         case 2:
-            BoolEval beval = (BoolEval) evals[0];
+            BoolEval beval = (BoolEval) args[0]; // TODO - class cast exception
             if (beval.getBooleanValue()) {
-                retval = evals[1];
-            }
-            else {
-                retval = evalWhenFalse;
+                return args[1];
             }
-            break;
+            return evalWhenFalse;
         default:
-            retval = ErrorEval.UNKNOWN_ERROR;
+            return ErrorEval.VALUE_INVALID;
         }
-        return retval;
     }
-
-
 }
index 6e8f84b34260c59c5dab611484dfaafdee35836d..c0e482e5a8e70de1688531003318449a88129597 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
-import org.apache.poi.hssf.record.formula.eval.AreaEval;
 import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.BoolEval;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
 /**
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *
  */
-public class Isblank implements Function {
+public final class Isblank implements Function {
 
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-        ValueEval retval = null;
-        boolean b = false;
-        
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.VALUE_INVALID;
-            break;
-        case 1:
-            if (operands[0] instanceof BlankEval) {
-                b = true;
-            }
-            else if (operands[0] instanceof AreaEval) {
-                AreaEval ae = (AreaEval) operands[0];
-                if (ae.contains(srcCellRow, srcCellCol)) { // circular ref!
-                    retval = ErrorEval.CIRCULAR_REF_ERROR;
-                }
-                else if (ae.isRow()) {
-                    if (ae.containsColumn(srcCellCol)) {
-                        ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCellCol);
-                        b = (ve instanceof BlankEval);
-                    }
-                    else {
-                        b = false;
-                    }
-                }
-                else if (ae.isColumn()) {
-                    if (ae.containsRow(srcCellRow)) {
-                        ValueEval ve = ae.getValueAt(srcCellRow, ae.getFirstColumn());
-                        b = (ve instanceof BlankEval);
-                    }
-                    else {
-                        b = false;
-                    }
-                }
-                else {
-                    b = false;
-                }
-            }
-            else if (operands[0] instanceof RefEval) {
-                RefEval re = (RefEval) operands[0];
-                b = (!re.isEvaluated()) && re.getInnerValueEval() instanceof BlankEval; 
-            }
-            else {
-                b = false;
-            }
-        }
-        
-        if (retval == null) {
-            retval = b
-                    ? BoolEval.TRUE
-                    : BoolEval.FALSE;
-        }
-        return retval;
-    }
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               if(args.length != 1) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               Eval arg = args[0];
+               
+               ValueEval singleCellValue;
+               try {
+                       singleCellValue = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
+               } catch (EvaluationException e) {
+                       return BoolEval.FALSE;
+               }
+               return BoolEval.valueOf(singleCellValue instanceof BlankEval);
+       }
 }
index c0cb39b268369a6b9a9bc7fdbf09121cf4fc3bca..0bc49b40707436fe04dd82cd904e5588c0eeef55 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
-import org.apache.poi.hssf.record.formula.eval.AreaEval;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.RefEval;
-import org.apache.poi.hssf.record.formula.eval.StringValueEval;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
 /**
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *
  */
-public class Len extends TextFunction {
-
+public final class Len extends TextFunction {
 
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-        ValueEval retval = null;
-        String s = null;
-        
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.VALUE_INVALID;
-            break;
-        case 1:
-            ValueEval ve = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
-            if (ve instanceof StringValueEval) {
-                StringValueEval sve = (StringValueEval) ve;
-                s = sve.getStringValue();
-            }
-            else if (ve instanceof RefEval) {
-                RefEval re = (RefEval) ve;
-                ValueEval ive = re.getInnerValueEval();
-                if (ive instanceof BlankEval) {
-                    s = re.isEvaluated() ? "0" : null;
-                }
-                else if (ive instanceof StringValueEval) {
-                    s = ((StringValueEval) ive).getStringValue();
-                }
-                else if (ive instanceof BlankEval) {}
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-            else if (ve instanceof BlankEval) {}
-            else {
-                retval = ErrorEval.VALUE_INVALID;
-                break;
-            }
-        }
-        
-        if (retval == null) {
-            s = (s == null) ? EMPTY_STRING : s;
-            retval = new NumberEval(s.length());
-        }
-        
-        return retval;
-    }
-    
-    
-    protected ValueEval singleOperandEvaluate(Eval eval, int srcRow, short srcCol) {
-        ValueEval retval;
-        if (eval instanceof AreaEval) {
-            AreaEval ae = (AreaEval) eval;
-            if (ae.contains(srcRow, srcCol)) { // circular ref!
-                retval = ErrorEval.CIRCULAR_REF_ERROR;
-            }
-            else if (ae.isRow()) {
-                if (ae.containsColumn(srcCol)) {
-                    ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
-                    retval = attemptXlateToText(ve);
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-            else if (ae.isColumn()) {
-                if (ae.containsRow(srcRow)) {
-                    ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn());
-                    retval = attemptXlateToText(ve);
-                }
-                else {
-                    retval = ErrorEval.VALUE_INVALID;
-                }
-            }
-            else {
-                retval = ErrorEval.VALUE_INVALID;
-            }
-        }
-        else {
-            retval = attemptXlateToText((ValueEval) eval);
-        }
-        return retval;
-    }
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               
+               if(args.length != 1) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               
+               try {
+                       ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
 
-    
-    /**
-     * converts from Different ValueEval types to StringEval.
-     * Note: AreaEvals are not handled, if arg is an AreaEval,
-     * the returned value is ErrorEval.VALUE_INVALID
-     * @param ve
-     */
-    protected ValueEval attemptXlateToText(ValueEval ve) {
-        ValueEval retval;
-        if (ve instanceof StringValueEval || ve instanceof RefEval) {
-            retval = ve;
-        }
-        else if (ve instanceof BlankEval) {
-            retval = ve;
-        }
-        else {
-            retval = ErrorEval.VALUE_INVALID;
-        }
-        return retval;
-    }
+                       String str = OperandResolver.coerceValueToString(veval);
+                       
+                       return new NumberEval(str.length());
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+       }
 }
index 21e30de0c27fd757571c15eb991371a90cc17701..e25db7b746724b9f4015f9a86eaa57a932153556 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
@@ -30,12 +27,11 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
  *
  */
-public class Maxa extends MultiOperandNumericFunction {
+public final class Maxa extends MultiOperandNumericFunction {
     private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
         new ValueEvalToNumericXlator((short) (
                   ValueEvalToNumericXlator.BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
index 517e89feedbc15ac4831250cceb351008bfe64b8..7f30aa4cec62a327e0ff3de83e5e978b2428db5a 100644 (file)
@@ -23,7 +23,6 @@ import org.apache.poi.hssf.record.formula.eval.Eval;
 import org.apache.poi.hssf.record.formula.eval.EvaluationException;
 import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.StringValueEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
 /**
@@ -35,7 +34,7 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
  * 
  * @author Manda Wilson &lt; wilson at c bio dot msk cc dot org &gt;
  */
-public class Mid extends TextFunction {
+public class Mid implements Function {
        /**
         * Returns a specific number of characters from a text string, starting at
         * the position you specify, based on the number of characters you specify.
@@ -52,7 +51,8 @@ public class Mid extends TextFunction {
                int numChars;
 
                try {
-                       text = evaluateTextArg(args[0], srcCellRow, srcCellCol);
+                       ValueEval evText = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+                       text = OperandResolver.coerceValueToString(evText);
                        int startCharNum = evaluateNumberArg(args[1], srcCellRow, srcCellCol);
                        numChars = evaluateNumberArg(args[2], srcCellRow, srcCellCol);
                        startIx = startCharNum - 1; // convert to zero-based
@@ -79,7 +79,7 @@ public class Mid extends TextFunction {
 
        }
 
-       public static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
+       private static int evaluateNumberArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
                ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
                if (ev instanceof BlankEval) {
                        // Note - for start_num arg, blank causes error(#VALUE!),
@@ -89,12 +89,4 @@ public class Mid extends TextFunction {
 
                return OperandResolver.coerceValueToInt(ev);
        }
-
-       private static String evaluateTextArg(Eval arg, int srcCellRow, short srcCellCol) throws EvaluationException {
-               ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
-               if (ev instanceof StringValueEval) {
-                       return ((StringValueEval) ev).getStringValue();
-               }
-               throw EvaluationException.invalidValue();
-       }
 }
\ No newline at end of file
index a998a870f676ecab3843ef668d531e283cb04bc9..21ba47b569c552378cc7b3297a19db3678f64d3f 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
@@ -35,7 +32,6 @@ public class Mina extends MultiOperandNumericFunction {
         new ValueEvalToNumericXlator((short) (
                   ValueEvalToNumericXlator.BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
index f7dc60fea751e1e338707199729c4a835e6cb2fc..0e7cce217e2ebc04e07c99e891ade018e83afb6e 100644 (file)
@@ -76,32 +76,9 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
         }
     }
     
-
-    private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
-        new ValueEvalToNumericXlator((short) (
-                  ValueEvalToNumericXlator.BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_IS_PARSED  
-                | ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
-              //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE  
-                ));
-    
     private static final int DEFAULT_MAX_NUM_OPERANDS = 30;
 
-    /**
-     * this is the default impl for the factory method getXlator
-     * of the super class NumericFunction. Subclasses can override this method
-     * if they desire to return a different ValueEvalToNumericXlator instance
-     * than the default.
-     */
-    protected ValueEvalToNumericXlator getXlator() {
-        return DEFAULT_NUM_XLATOR;
-    }
+    protected abstract ValueEvalToNumericXlator getXlator();
     
     /**
      * Maximum number of operands accepted by this function.
@@ -160,9 +137,7 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
                  * HSSFFormulaEvaluator where we store an array
                  * of RefEvals as the "values" array. 
                  */
-                RefEval re = (values[j] instanceof RefEval)
-                        ? new Ref2DEval(null, ((RefEval) values[j]).getInnerValueEval(), true)
-                        : new Ref2DEval(null, values[j], false);
+                RefEval re = new Ref2DEval(null, values[j]);
                 ValueEval ve = singleOperandEvaluate(re, srcRow, srcCol);
                 
                 if (ve instanceof NumericValueEval) {
index 7b43317a3b7296c6f9c1452b3282cd78d1f1c8ab..fd96f1495a9c47978102c43caf1ad250eca49e77 100644 (file)
@@ -39,10 +39,8 @@ public abstract class NumericFunction implements Function {
         new ValueEvalToNumericXlator((short) (
                   ValueEvalToNumericXlator.BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
                 | ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
index fef7e03464c6ef04dc022f6b150bfc33960d770f..7995e66c34db1a99c439eb0b3bf14fd312b5f9bb 100644 (file)
@@ -33,8 +33,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
 public class Stdev extends MultiOperandNumericFunction {
 
     private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
-        new ValueEvalToNumericXlator((short) (0
-              //    ValueEvalToNumericXlator.BOOL_IS_PARSED  
+        new ValueEvalToNumericXlator((short) (
+                  ValueEvalToNumericXlator.BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
                 | ValueEvalToNumericXlator.STRING_IS_PARSED  
@@ -44,7 +44,6 @@ public class Stdev extends MultiOperandNumericFunction {
               //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
               //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE
-                | ValueEvalToNumericXlator.EVALUATED_REF_BLANK_IS_PARSED
                 ));
     
     /**
index f4e1959be611737ae4be63240f84da03ec360f47..b74b4161ac2a32a1ac113d8c68fdc14d9e792285 100644 (file)
@@ -33,18 +33,18 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
 public class Sumsq extends MultiOperandNumericFunction {
     private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
         new ValueEvalToNumericXlator((short) (
-              //  ValueEvalToNumericXlator.BOOL_IS_PARSED  
+                ValueEvalToNumericXlator.BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_IS_PARSED  
+                | ValueEvalToNumericXlator.STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
               //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
               //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE  
-                  ValueEvalToNumericXlator.REF_BLANK_IS_PARSED
-                | ValueEvalToNumericXlator.BLANK_IS_PARSED
+              //| ValueEvalToNumericXlator.REF_BLANK_IS_PARSED
+              //| ValueEvalToNumericXlator.BLANK_IS_PARSED
                 ));
     
     protected ValueEvalToNumericXlator getXlator() {
index 8e31224078cabd8d6258cc12a54965632e3c201d..30ad5ec2303ee70347d5da2cd219d97a2bc17884 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.functions;
 
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
+package org.apache.poi.hssf.record.formula.functions;
 
 /**
+ * Implementation of Excel function SUMX2MY2()<p/>
+ * 
+ * Calculates the sum of differences of squares in two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMX2MY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ * 
+ * result = &Sigma;<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>-y<sub>i</sub><sup>2</sup>)
+ * 
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
  */
-public class Sumx2my2 extends XYNumericFunction {
+public final class Sumx2my2 extends XYNumericFunction {
 
-    
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-        ValueEval retval = null;
-        double[][] values = null;
-        
-        int checkLen = 0; // check to see that all array lengths are equal
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.VALUE_INVALID;
-            break;
-        case 2:
-            values = getValues(operands, srcCellRow, srcCellCol);
-            if (values==null 
-                    || values[X] == null || values[Y] == null
-                    || values[X].length == 0 || values[Y].length == 0
-                    || values[X].length != values[Y].length) {
-                retval = ErrorEval.VALUE_INVALID;
-            }
-        }
-        
-        if (retval == null) {
-            double d = MathX.sumx2my2(values[X], values[Y]);
-            retval = (Double.isNaN(d) || Double.isInfinite(d))
-                    ? (ValueEval) ErrorEval.NUM_ERROR
-                    : new NumberEval(d);
-        }
-        
-        return retval;
+    protected double evaluate(double[] xArray, double[] yArray) {
+       return MathX.sumx2my2(xArray, yArray);
     }
 }
index deb7675a4ef4467b5e9c14178729f3d579e7be6f..dfd730d12cd347b378de777bef046097f7888d94 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.functions;
 
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
+package org.apache.poi.hssf.record.formula.functions;
 
 /**
+ * Implementation of Excel function SUMX2PY2()<p/>
+ * 
+ * Calculates the sum of squares in two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMX2PY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ * 
+ * result = &Sigma;<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>+y<sub>i</sub><sup>2</sup>)
+ * 
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
  */
-public class Sumx2py2 extends XYNumericFunction {
+public final class Sumx2py2 extends XYNumericFunction {
 
-    
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-        ValueEval retval = null;
-        double[][] values = null;
-        
-        int checkLen = 0; // check to see that all array lengths are equal
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.VALUE_INVALID;
-            break;
-        case 2:
-            values = getValues(operands, srcCellRow, srcCellCol);
-            if (values==null 
-                    || values[X] == null || values[Y] == null
-                    || values[X].length == 0 || values[Y].length == 0
-                    || values[X].length != values[Y].length) {
-                retval = ErrorEval.VALUE_INVALID;
-            }
-        }
-        
-        if (retval == null) {
-            double d = MathX.sumx2py2(values[X], values[Y]);
-            retval = (Double.isNaN(d) || Double.isInfinite(d))
-                    ? (ValueEval) ErrorEval.NUM_ERROR
-                    : new NumberEval(d);
-        }
-        
-        return retval;
+    protected double evaluate(double[] xArray, double[] yArray) {
+       return MathX.sumx2py2(xArray, yArray);
     }
 }
index c62a0b76221480acc2c5926ddc81d9983d4d7097..a1b2fec9b2854ebd5910db2e3d1bad7ca9b09f6a 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 15, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.functions;
 
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
+package org.apache.poi.hssf.record.formula.functions;
 
 /**
+ * Implementation of Excel function SUMXMY2()<p/>
+ * 
+ * Calculates the sum of squares of differences between two arrays of the same size.<br/>
+ * <b>Syntax</b>:<br/>
+ * <b>SUMXMY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/>
+ * 
+ * result = &Sigma;<sub>i: 0..n</sub>(x<sub>i</sub>-y<sub>i</sub>)<sup>2</sup>
+ * 
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
  */
-public class Sumxmy2 extends XYNumericFunction {
+public final class Sumxmy2 extends XYNumericFunction {
 
-    
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-        ValueEval retval = null;
-        double[][] values = null;
-        
-        int checkLen = 0; // check to see that all array lengths are equal
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.VALUE_INVALID;
-            break;
-        case 2:
-            values = getValues(operands, srcCellRow, srcCellCol);
-            if (values==null 
-                    || values[X] == null || values[Y] == null
-                    || values[X].length == 0 || values[Y].length == 0
-                    || values[X].length != values[Y].length) {
-                retval = ErrorEval.VALUE_INVALID;
-            }
-        }
-        
-        if (retval == null) {
-            double d = MathX.sumxmy2(values[X], values[Y]);
-            retval = (Double.isNaN(d) || Double.isInfinite(d))
-                    ? (ValueEval) ErrorEval.NUM_ERROR
-                    : new NumberEval(d);
-        }
-        
-        return retval;
+    protected double evaluate(double[] xArray, double[] yArray) {
+       return MathX.sumxmy2(xArray, yArray);
     }
 }
index 5e9d91c7cc7682eb40c6a5f0715d5697dd4c83d6..87e29ee34d7ba50ab63ab995dc2df4dd4610fd63 100644 (file)
 */
 package org.apache.poi.hssf.record.formula.functions;
 
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.Eval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.StringValueEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
 /**
@@ -30,46 +29,25 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
  *  value is string.
  * @author Manda Wilson &lt; wilson at c bio dot msk cc dot org &gt;
  */
-public class Trim extends TextFunction {
+public final class Trim extends TextFunction {
 
-       /**
-        * Removes leading and trailing spaces from value if evaluated 
-        *  operand value is string.
-        * Returns StringEval only if evaluated operand is of type string 
-        *  (and is not blank or null) or number. If evaluated operand is 
-        *  of type string and is blank or null, or if evaluated operand is 
-        *  of type blank, returns BlankEval.  Otherwise returns ErrorEval.
-        * 
-        * @see org.apache.poi.hssf.record.formula.eval.Eval
-        */
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-       Eval retval = ErrorEval.VALUE_INVALID;
-        String str = null;
-        
-        switch (operands.length) {
-               default:
-                   break;
-               case 1:
-                   ValueEval veval = singleOperandEvaluate(operands[0], srcCellRow, srcCellCol);
-                   if (veval instanceof StringValueEval) {
-                       StringValueEval sve = (StringValueEval) veval;
-                       str = sve.getStringValue();
-                       if (str == null || str.trim().equals("")) {
-                               return BlankEval.INSTANCE;
-                       }
-                   }
-                   else if (veval instanceof NumberEval) {
-                       NumberEval neval = (NumberEval) veval;
-                       str = neval.getStringValue();
-                   } 
-                   else if (veval instanceof BlankEval) {
-                       return BlankEval.INSTANCE;
-                   }
-           }
-               
-        if (str != null) {
-            retval = new StringEval(str.trim());
-        } 
-        return retval;
-    }
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               
+               if(args.length != 1) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               
+               try {
+                       ValueEval veval = OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol);
+
+                       String str = OperandResolver.coerceValueToString(veval);
+                       str = str.trim();
+                       if(str.length() < 1) {
+                               return StringEval.EMPTY_INSTANCE;
+                       }
+                       return new StringEval(str);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+       }
 }
index 1e6955ad9452fc996b2956ccda3bcfee49998003..b989c33a26790fdbdab30d815aaf7bcd614009f3 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 29, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.AreaEval;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
 import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.RefEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
 
 /**
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *
  */
-public abstract class XYNumericFunction extends NumericFunction {
+public abstract class XYNumericFunction implements Function {
     protected static final int X = 0;
     protected static final int Y = 1;
+    
+    protected static final class DoubleArrayPair {
 
-    private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
-        new ValueEvalToNumericXlator((short) (
-                  ValueEvalToNumericXlator.BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_IS_PARSED  
-                | ValueEvalToNumericXlator.REF_STRING_IS_PARSED  
-                | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_TO_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.REF_STRING_TO_BOOL_IS_PARSED  
-              //| ValueEvalToNumericXlator.STRING_IS_INVALID_VALUE  
-              //| ValueEvalToNumericXlator.REF_STRING_IS_INVALID_VALUE  
-                ));
+               private final double[] _xArray;
+               private final double[] _yArray;
 
-    /**
-     * this is the default impl for the factory method getXlator
-     * of the super class NumericFunction. Subclasses can override this method
-     * if they desire to return a different ValueEvalToNumericXlator instance
-     * than the default.
-     */
-    protected ValueEvalToNumericXlator getXlator() {
-        return DEFAULT_NUM_XLATOR;
-    }
-    
-    protected int getMaxNumOperands() {
-        return 30;
+               public DoubleArrayPair(double[] xArray, double[] yArray) {
+                       _xArray = xArray;
+                       _yArray = yArray;
+               }
+               public double[] getXArray() {
+                       return _xArray;
+               }
+               public double[] getYArray() {
+                       return _yArray;
+               }
     }
 
+
+    public final Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+       if(args.length != 2) {
+               return ErrorEval.VALUE_INVALID;
+       }
+       
+        double[][] values;
+               try {
+                       values = getValues(args[0], args[1]);
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+        if (values==null 
+                || values[X] == null || values[Y] == null
+                || values[X].length == 0 || values[Y].length == 0
+                || values[X].length != values[Y].length) {
+            return ErrorEval.VALUE_INVALID;
+        }
+        
+        double d = evaluate(values[X], values[Y]);
+        if (Double.isNaN(d) || Double.isInfinite(d)) {
+                       return ErrorEval.NUM_ERROR;
+               }
+               return new NumberEval(d);
+    }    
+    protected abstract double evaluate(double[] xArray, double[] yArray);
+
     /**
      * Returns a double array that contains values for the numeric cells
      * from among the list of operands. Blanks and Blank equivalent cells
      * are ignored. Error operands or cells containing operands of type
      * that are considered invalid and would result in #VALUE! error in 
      * excel cause this function to return null.
-     * 
-     * @param xops
-     * @param yops
-     * @param srcRow
-     * @param srcCol
      */
-    protected double[][] getNumberArray(Eval[] xops, Eval[] yops, int srcRow, short srcCol) {
-        double[][] retval = new double[2][30];
+    private static double[][] getNumberArray(Eval[] xops, Eval[] yops) throws EvaluationException {
+       
+       // check for errors first: size mismatch, value errors in x, value errors in y
+       
+       int nArrayItems = xops.length;
+               if(nArrayItems != yops.length) {
+               throw new EvaluationException(ErrorEval.NA);
+       }
+               for (int i = 0; i < xops.length; i++) {
+                       Eval eval = xops[i];
+                       if (eval instanceof ErrorEval) {
+                               throw new EvaluationException((ErrorEval) eval);
+                       }
+               }
+               for (int i = 0; i < yops.length; i++) {
+                       Eval eval = yops[i];
+                       if (eval instanceof ErrorEval) {
+                               throw new EvaluationException((ErrorEval) eval);
+                       }
+               }
+               
+        double[] xResult = new double[nArrayItems];
+        double[] yResult = new double[nArrayItems];
+       
         int count = 0;
         
-        if (xops.length > getMaxNumOperands() 
-                || yops.length > getMaxNumOperands()
-                || xops.length != yops.length) {
-            retval = null;
-        }
-        else {
-            
-            for (int i=0, iSize=xops.length; i<iSize; i++) {
-                Eval xEval = xops[i];
-                Eval yEval = yops[i];
-                
-                if (isNumberEval(xEval) && isNumberEval(yEval)) {
-                    retval[X] = ensureCapacity(retval[X], count);
-                    retval[Y] = ensureCapacity(retval[Y], count);
-                    retval[X][count] = getDoubleValue(xEval);
-                    retval[Y][count] = getDoubleValue(yEval);
-                    if (Double.isNaN(retval[X][count]) || Double.isNaN(retval[Y][count])) {
-                        retval = null;
-                        break;
-                    }
-                    count++;
-                }
-            }
-        }
-        
-        if (retval != null) {
-            double[][] temp = retval;
-            retval[X] = trimToSize(retval[X], count);
-            retval[Y] = trimToSize(retval[Y], count);
-        }
-        
-        return retval;
-    }
-    
-    protected double[][] getValues(Eval[] operands, int srcCellRow, short srcCellCol) {
-        double[][] retval = null;
-        
-        outer: do {
-            if (operands.length == 2) {
-                Eval[] xEvals = new Eval[1];
-                Eval[] yEvals = new Eval[1];
-                if (operands[X] instanceof AreaEval) {
-                    AreaEval ae = (AreaEval) operands[0];
-                    xEvals = ae.getValues();
-                }
-                else if (operands[X] instanceof ErrorEval) {
-                    break outer;
-                }
-                else {
-                    xEvals[0] = operands[X];
-                }
-                
-                if (operands[Y] instanceof AreaEval) {
-                    AreaEval ae = (AreaEval) operands[Y];
-                    yEvals = ae.getValues();
-                }
-                else if (operands[Y] instanceof ErrorEval) {
-                    break outer;
-                }
-                else {
-                    yEvals[0] = operands[Y];
-                }
-                
-                retval = getNumberArray(xEvals, yEvals, srcCellRow, srcCellCol);
-            }
-        } while (false);
+               for (int i=0, iSize=nArrayItems; i<iSize; i++) {
+                   Eval xEval = xops[i];
+                   Eval yEval = yops[i];
+                   
+                   if (isNumberEval(xEval) && isNumberEval(yEval)) {
+                       xResult[count] = getDoubleValue(xEval);
+                       yResult[count] = getDoubleValue(yEval);
+                       if (Double.isNaN(xResult[count]) || Double.isNaN(xResult[count])) {
+                           throw new EvaluationException(ErrorEval.NUM_ERROR);
+                       }
+                       count++;
+                   }
+               }
         
-        return retval;
+               return new double[][] {
+               trimToSize(xResult, count),
+            trimToSize(yResult, count),
+               };
     }
     
-
-    protected static double[] ensureCapacity(double[] arr, int pos) {
-        double[] temp = arr;
-        while (pos >= arr.length) {
-            arr = new double[arr.length << 2];
-        }
-        if (temp.length != arr.length)
-            System.arraycopy(temp, 0, arr, 0, temp.length);
-        return arr;
+    private static double[][] getValues(Eval argX, Eval argY) throws EvaluationException {
+       
+       if (argX instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) argX);
+               }
+       if (argY instanceof ErrorEval) {
+                       throw new EvaluationException((ErrorEval) argY);
+               }
+       
+        Eval[] xEvals;
+               Eval[] yEvals;
+               if (argX instanceof AreaEval) {
+                   AreaEval ae = (AreaEval) argX;
+                   xEvals = ae.getValues();
+               } else {
+                   xEvals = new Eval[] { argX, };
+               }
+               
+               if (argY instanceof AreaEval) {
+                   AreaEval ae = (AreaEval) argY;
+                   yEvals = ae.getValues();
+               } else {
+                   yEvals = new Eval[] { argY, };
+               }
+               
+               return getNumberArray(xEvals, yEvals);
     }
     
-    protected static double[] trimToSize(double[] arr, int len) {
+    private static double[] trimToSize(double[] arr, int len) {
         double[] tarr = arr;
         if (arr.length > len) {
             tarr = new double[len];
@@ -170,7 +167,7 @@ public abstract class XYNumericFunction extends NumericFunction {
         return tarr;
     }
     
-    protected static boolean isNumberEval(Eval eval) {
+    private static boolean isNumberEval(Eval eval) {
         boolean retval = false;
         
         if (eval instanceof NumberEval) {
@@ -185,7 +182,7 @@ public abstract class XYNumericFunction extends NumericFunction {
         return retval;
     }
     
-    protected static double getDoubleValue(Eval eval) {
+    private static double getDoubleValue(Eval eval) {
         double retval = 0;
         if (eval instanceof NumberEval) {
             NumberEval ne = (NumberEval) eval;
index acbca05e6257aa4316ae7679ed668ae568616456..3cbec2e6d24b6bcf3c94c116e0156cd3cd0f92ce 100644 (file)
@@ -80,6 +80,7 @@ import org.apache.poi.hssf.record.formula.eval.GreaterThanEval;
 import org.apache.poi.hssf.record.formula.eval.LessEqualEval;
 import org.apache.poi.hssf.record.formula.eval.LessThanEval;
 import org.apache.poi.hssf.record.formula.eval.MultiplyEval;
+import org.apache.poi.hssf.record.formula.eval.NameEval;
 import org.apache.poi.hssf.record.formula.eval.NotEqualEval;
 import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.OperationEval;
@@ -360,16 +361,16 @@ public class HSSFFormulaEvaluator {
         EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker();
         
         if(!tracker.startEvaluate(workbook, sheet, srcRowNum, srcColNum)) {
-               return ErrorEval.CIRCULAR_REF_ERROR;
+            return ErrorEval.CIRCULAR_REF_ERROR;
         }
         try {
-               return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula());
+            return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula());
         } finally {
-               tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum);
+            tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum);
         }
     }
     private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet, 
-               int srcRowNum, short srcColNum, String cellFormulaText) {
+            int srcRowNum, short srcColNum, String cellFormulaText) {
         FormulaParser parser = new FormulaParser(cellFormulaText, workbook.getWorkbook());
         parser.parse();
         Ptg[] ptgs = parser.getRPNPtg();
@@ -380,15 +381,24 @@ public class HSSFFormulaEvaluator {
         for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
 
             // since we don't know how to handle these yet :(
-            if (ptgs[i] instanceof ControlPtg) { continue; }
-            if (ptgs[i] instanceof MemErrPtg) { continue; }
-            if (ptgs[i] instanceof MissingArgPtg) { continue; }
-            if (ptgs[i] instanceof NamePtg) { continue; }
-            if (ptgs[i] instanceof NameXPtg) { continue; }
-            if (ptgs[i] instanceof UnknownPtg) { continue; }
+            Ptg ptg = ptgs[i];
+            if (ptg instanceof ControlPtg) { continue; }
+            if (ptg instanceof MemErrPtg) { continue; }
+            if (ptg instanceof MissingArgPtg) { continue; }
+            if (ptg instanceof NamePtg) { 
+               // named ranges, macro functions
+                NamePtg namePtg = (NamePtg) ptg;
+                stack.push(new NameEval(namePtg.getIndex()));
+                continue; 
+            }
+            if (ptg instanceof NameXPtg) {
+               // TODO - external functions
+                continue;
+            }
+            if (ptg instanceof UnknownPtg) { continue; }
 
-            if (ptgs[i] instanceof OperationPtg) {
-                OperationPtg optg = (OperationPtg) ptgs[i];
+            if (ptg instanceof OperationPtg) {
+                OperationPtg optg = (OperationPtg) ptg;
 
                 // parens can be ignored since we have RPN tokens
                 if (optg instanceof ParenthesisPtg) { continue; }
@@ -408,47 +418,67 @@ public class HSSFFormulaEvaluator {
                 Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet);
                 stack.push(opresult);
             }
-            else if (ptgs[i] instanceof ReferencePtg) {
-                ReferencePtg ptg = (ReferencePtg) ptgs[i];
-                short colnum = ptg.getColumn();
-                short rownum = ptg.getRow();
+            else if (ptg instanceof ReferencePtg) {
+                ReferencePtg refPtg = (ReferencePtg) ptg;
+                short colnum = refPtg.getColumn();
+                short rownum = refPtg.getRow();
                 HSSFRow row = sheet.getRow(rownum);
                 HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
-                stack.push(createRef2DEval(ptg, cell, row, sheet, workbook));
+                stack.push(createRef2DEval(refPtg, cell, row, sheet, workbook));
             }
-            else if (ptgs[i] instanceof Ref3DPtg) {
-                Ref3DPtg ptg = (Ref3DPtg) ptgs[i];
-                short colnum = ptg.getColumn();
-                short rownum = ptg.getRow();
+            else if (ptg instanceof Ref3DPtg) {
+                Ref3DPtg refPtg = (Ref3DPtg) ptg;
+                short colnum = refPtg.getColumn();
+                short rownum = refPtg.getRow();
                 Workbook wb = workbook.getWorkbook();
-                HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex()));
+                HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(refPtg.getExternSheetIndex()));
                 HSSFRow row = xsheet.getRow(rownum);
                 HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
-                stack.push(createRef3DEval(ptg, cell, row, xsheet, workbook));
+                stack.push(createRef3DEval(refPtg, cell, row, xsheet, workbook));
             }
-            else if (ptgs[i] instanceof AreaPtg) {
-                AreaPtg ap = (AreaPtg) ptgs[i];
+            else if (ptg instanceof AreaPtg) {
+                AreaPtg ap = (AreaPtg) ptg;
                 AreaEval ae = evaluateAreaPtg(sheet, workbook, ap);
                 stack.push(ae);
             }
-            else if (ptgs[i] instanceof Area3DPtg) {
-                Area3DPtg a3dp = (Area3DPtg) ptgs[i];
+            else if (ptg instanceof Area3DPtg) {
+                Area3DPtg a3dp = (Area3DPtg) ptg;
                 AreaEval ae = evaluateArea3dPtg(workbook, a3dp);
                 stack.push(ae);
             }
             else {
-                Eval ptgEval = getEvalForPtg(ptgs[i]);
+                Eval ptgEval = getEvalForPtg(ptg);
                 stack.push(ptgEval);
             }
         }
 
         ValueEval value = ((ValueEval) stack.pop());
-        if (value instanceof RefEval) {
-            RefEval rv = (RefEval) value;
+        if (!stack.isEmpty()) {
+            throw new IllegalStateException("evaluation stack not empty");
+        }
+        value = dereferenceValue(value, srcRowNum, srcColNum);
+        if (value instanceof BlankEval) {
+               // Note Excel behaviour here. A blank final final value is converted to zero.  
+            return NumberEval.ZERO;
+            // Formulas _never_ evaluate to blank.  If a formula appears to have evaluated to 
+            // blank, the actual value is empty string. This can be verified with ISBLANK().
+        }
+        return value;
+    }
+
+    /**
+     * Dereferences a single value from any AreaEval or RefEval evaluation result.
+     * If the supplied evaluationResult is just a plain value, it is returned as-is.
+        * @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
+        *  <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
+     */
+    private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
+        if (evaluationResult instanceof RefEval) {
+            RefEval rv = (RefEval) evaluationResult;
             return rv.getInnerValueEval();
         }
-        if (value instanceof AreaEval) {
-            AreaEval ae = (AreaEval) value;
+        if (evaluationResult instanceof AreaEval) {
+            AreaEval ae = (AreaEval) evaluationResult;
             if (ae.isRow()) {
                 if(ae.isColumn()) {
                     return ae.getValues()[0];
@@ -460,7 +490,7 @@ public class HSSFFormulaEvaluator {
             }
             return ErrorEval.VALUE_INVALID;
         }
-        return value;
+        return evaluationResult;
     }
 
     private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum,
@@ -486,7 +516,7 @@ public class HSSFFormulaEvaluator {
         // (eg C:C)
         // TODO: Handle whole column ranges properly
         if(row1 == -1 && row0 >= 0) {
-               row1 = (short)sheet.getLastRowNum();
+            row1 = (short)sheet.getLastRowNum();
         }
         
         ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
@@ -514,7 +544,7 @@ public class HSSFFormulaEvaluator {
         // (eg C:C)
         // TODO: Handle whole column ranges properly
         if(row1 == -1 && row0 >= 0) {
-               row1 = (short)xsheet.getLastRowNum();
+            row1 = (short)xsheet.getLastRowNum();
         }
         
         ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
@@ -631,22 +661,22 @@ public class HSSFFormulaEvaluator {
     private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell, 
             HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
         if (cell == null) {
-            return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
+            return new Ref2DEval(ptg, BlankEval.INSTANCE);
         }
         
         switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
+                return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()));
             case HSSFCell.CELL_TYPE_STRING:
-                return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
+                return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()));
             case HSSFCell.CELL_TYPE_FORMULA:
-                return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
+                return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook));
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
+                return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()));
             case HSSFCell.CELL_TYPE_BLANK:
-                return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
+                return new Ref2DEval(ptg, BlankEval.INSTANCE);
             case HSSFCell.CELL_TYPE_ERROR:
-                return new  Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
+                return new  Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()));
         }
         throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
     }
@@ -657,21 +687,21 @@ public class HSSFFormulaEvaluator {
     private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell, 
             HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
         if (cell == null) {
-            return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
+            return new Ref3DEval(ptg, BlankEval.INSTANCE);
         }
         switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
+                return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()));
             case HSSFCell.CELL_TYPE_STRING:
-                return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
+                return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()));
             case HSSFCell.CELL_TYPE_FORMULA:
-                return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
+                return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook));
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
+                return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()));
             case HSSFCell.CELL_TYPE_BLANK:
-                return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
+                return new Ref3DEval(ptg, BlankEval.INSTANCE);
             case HSSFCell.CELL_TYPE_ERROR:
-                return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
+                return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()));
         }
         throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
     }
index 9c2cb2b87fdfad108f156b483c4596831821fd62..a038a964eec673a2db3e8e230ca5d7e27d0f6bb8 100644 (file)
 ==================================================================== */
 
 package org.apache.poi.hssf.eventusermodel;
-import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
-import org.apache.poi.hssf.eventusermodel.HSSFListener;
-
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
 
-import org.apache.poi.hssf.eventusermodel.HSSFRequest;
 import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord;
 import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord;
 import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord;
@@ -31,31 +31,33 @@ import org.apache.poi.hssf.record.LabelSSTRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RowRecord;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-
-import junit.framework.TestCase;
-
-public class TestMissingRecordAwareHSSFListener extends TestCase {
-       private String dirname;
-       
-       public TestMissingRecordAwareHSSFListener() {
-               dirname = System.getProperty("HSSF.testdata.path");
-       }
+/**
+ * Tests for MissingRecordAwareHSSFListener
+ */
+public final class TestMissingRecordAwareHSSFListener extends TestCase {
        
-       public void testMissingRowRecords() throws Exception {
+       private Record[] r;
+
+       public void setUp() {
+               String dirname = System.getProperty("HSSF.testdata.path");
                File f = new File(dirname + "/MissingBits.xls");
-               
+
                HSSFRequest req = new HSSFRequest();
                MockHSSFListener mockListen = new MockHSSFListener();
                MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen);
                req.addListenerForAllRecords(listener);
                
-               POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
                HSSFEventFactory factory = new HSSFEventFactory();
-               factory.processWorkbookEvents(req, fs);
-               
-               // Check we got the dummy records
-               Record[] r = (Record[])
-                       mockListen.records.toArray(new Record[mockListen.records.size()]);
+               try {
+                       POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
+                       factory.processWorkbookEvents(req, fs);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+               r = mockListen.getRecords();
+       } 
+       
+       public void testMissingRowRecords() throws Exception {
                
                // We have rows 0, 1, 2, 20 and 21
                int row0 = -1;
@@ -105,20 +107,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase {
        }
        
        public void testEndOfRowRecords() throws Exception {
-               File f = new File(dirname + "/MissingBits.xls");
-               
-               HSSFRequest req = new HSSFRequest();
-               MockHSSFListener mockListen = new MockHSSFListener();
-               MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen);
-               req.addListenerForAllRecords(listener);
-               
-               POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
-               HSSFEventFactory factory = new HSSFEventFactory();
-               factory.processWorkbookEvents(req, fs);
-               
-               // Check we got the dummy records
-               Record[] r = (Record[])
-                       mockListen.records.toArray(new Record[mockListen.records.size()]);
                
                // Find the cell at 0,0
                int cell00 = -1;
@@ -240,20 +228,6 @@ public class TestMissingRecordAwareHSSFListener extends TestCase {
        
        
        public void testMissingCellRecords() throws Exception {
-               File f = new File(dirname + "/MissingBits.xls");
-               
-               HSSFRequest req = new HSSFRequest();
-               MockHSSFListener mockListen = new MockHSSFListener();
-               MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen);
-               req.addListenerForAllRecords(listener);
-               
-               POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(f));
-               HSSFEventFactory factory = new HSSFEventFactory();
-               factory.processWorkbookEvents(req, fs);
-               
-               // Check we got the dummy records
-               Record[] r = (Record[])
-                       mockListen.records.toArray(new Record[mockListen.records.size()]);
                
                // Find the cell at 0,0
                int cell00 = -1;
@@ -352,25 +326,35 @@ public class TestMissingRecordAwareHSSFListener extends TestCase {
                assertEquals(10, mc.getColumn());
        }
 
-       private static class MockHSSFListener implements HSSFListener {
-               private MockHSSFListener() {}
-               private ArrayList records = new ArrayList();
+       private static final class MockHSSFListener implements HSSFListener {
+               public MockHSSFListener() {}
+               private final List _records = new ArrayList();
 
                public void processRecord(Record record) {
-                       records.add(record);
+                       _records.add(record);
                        
                        if(record instanceof MissingRowDummyRecord) {
                                MissingRowDummyRecord mr = (MissingRowDummyRecord)record;
-                               System.out.println("Got dummy row " + mr.getRowNumber());
+                               log("Got dummy row " + mr.getRowNumber());
                        }
                        if(record instanceof MissingCellDummyRecord) {
                                MissingCellDummyRecord mc = (MissingCellDummyRecord)record;
-                               System.out.println("Got dummy cell " + mc.getRow() + " " + mc.getColumn());
+                               log("Got dummy cell " + mc.getRow() + " " + mc.getColumn());
                        }
                        if(record instanceof LastCellOfRowDummyRecord) {
                                LastCellOfRowDummyRecord lc = (LastCellOfRowDummyRecord)record;
-                               System.out.println("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber());
+                               log("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber());
+                       }
+               }
+               private static void log(String msg) {
+                       if(false) { // successful tests should be quiet
+                               System.out.println(msg);
                        }
                }
+               public Record[] getRecords() {
+                       Record[] result = new Record[_records.size()];
+                       _records.toArray(result);
+                       return result;
+               }
        }
 }
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/AllFormulaEvalTests.java
new file mode 100755 (executable)
index 0000000..3260c37
--- /dev/null
@@ -0,0 +1,38 @@
+/* ====================================================================
+   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.eval;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests the package <tt>org.apache.poi.hssf.record.formula.eval</tt>.
+ * 
+ * @author Josh Micich
+ */
+public class AllFormulaEvalTests {
+       
+       public static Test suite() {
+               TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.eval");
+               result.addTestSuite(TestCircularReferences.class);
+               result.addTestSuite(TestExternalFunction.class);
+               result.addTestSuite(TestFormulasFromSpreadsheet.class);
+               result.addTestSuite(TestUnaryPlusEval.class);
+               return result;
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java
new file mode 100755 (executable)
index 0000000..27e3338
--- /dev/null
@@ -0,0 +1,61 @@
+/* ====================================================================
+   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.eval;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFName;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
+/**
+ * 
+ * @author Josh Micich
+ */
+public final class TestExternalFunction extends TestCase {
+
+       /**
+        * Checks that an external function can get invoked from the formula evaluator. 
+        */
+       public void testInvoke() {
+        HSSFWorkbook wb = new HSSFWorkbook();
+        HSSFSheet sheet = wb.createSheet();
+        wb.setSheetName(0, "Sheet1");
+        HSSFRow row = sheet.createRow(0);
+        HSSFCell cell = row.createCell((short)0);
+
+        HSSFName hssfName = wb.createName();
+        hssfName.setNameName("myFunc");
+        
+        cell.setCellFormula("myFunc()");
+        String actualFormula=cell.getCellFormula();
+        assertEquals("myFunc()", actualFormula);
+               
+               HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
+               fe.setCurrentRow(row);
+               CellValue evalResult = fe.evaluate(cell);
+               
+               // Check the return value from ExternalFunction.evaluate()
+               // TODO - make this test assert something more interesting as soon as ExternalFunction works a bit better
+               assertEquals(HSSFCell.CELL_TYPE_ERROR, evalResult.getCellType());
+               assertEquals(ErrorEval.FUNCTION_NOT_IMPLEMENTED.getErrorCode(), evalResult.getErrorValue());
+       }
+}
index 8d21956edb5b60227b7fbaeda5b54646ca344679..d3e9c4c41220d85e8c25749ef1a353c253260ace 100755 (executable)
@@ -31,10 +31,13 @@ public final class AllIndividualFunctionEvaluationTests {
        // TODO - have this suite incorporated into a higher level one
        public static Test suite() {
                TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record.formula.functions");
+               result.addTestSuite(TestAverage.class);
                result.addTestSuite(TestCountFuncs.class);
                result.addTestSuite(TestDate.class);
                result.addTestSuite(TestFinanceLib.class);
                result.addTestSuite(TestIndex.class);
+               result.addTestSuite(TestIsBlank.class);
+               result.addTestSuite(TestLen.class);
                result.addTestSuite(TestMid.class);
                result.addTestSuite(TestMathX.class);
                result.addTestSuite(TestMatch.class);
@@ -43,6 +46,8 @@ public final class AllIndividualFunctionEvaluationTests {
                result.addTestSuite(TestSumproduct.class);
                result.addTestSuite(TestStatsLib.class);
                result.addTestSuite(TestTFunc.class);
+               result.addTestSuite(TestTrim.class);
+               result.addTestSuite(TestXYNumericFunction.class);
                return result;
        }
 
index 958c48664958bb69cabdb28b4c437a3060ce0587..a6e262b8682797d9d111613ebbc08dc496ff05a2 100755 (executable)
@@ -58,6 +58,6 @@ final class EvalFactory {
         * Creates a single RefEval (with value zero)
         */
        public static RefEval createRefEval(String refStr) {
-               return new Ref2DEval(new ReferencePtg(refStr), ZERO, true);
+               return new Ref2DEval(new ReferencePtg(refStr), ZERO);
        }
 }
index 2093d35b1ed8b08b9d44d54a126b4f85a58cf7e1..d477231349644c4c31f39a6cd311f0a618721ec8 100755 (executable)
@@ -111,9 +111,6 @@ public final class NumericFunctionInvoker {
                if(errorCodesAreEqual(ee, ErrorEval.FUNCTION_NOT_IMPLEMENTED)) {
                        return "Function not implemented";
                }
-               if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
-                       return "Unknown error";
-               }
                if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) {
                        return "Error code: #VALUE! (invalid value)";
                }
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestAverage.java
new file mode 100755 (executable)
index 0000000..4f0e5ff
--- /dev/null
@@ -0,0 +1,103 @@
+/* ====================================================================
+   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.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+/**
+ * Tests for Excel function AVERAGE()
+ * 
+ * @author Josh Micich
+ */
+public final class TestAverage extends TestCase {
+
+       
+       private static Eval invokeAverage(Eval[] args) {
+               return new Average().evaluate(args, -1, (short)-1);
+       }
+
+       private void confirmAverage(Eval[] args, double expected) {
+               Eval result = invokeAverage(args);
+               assertEquals(NumberEval.class, result.getClass());
+               assertEquals(expected, ((NumberEval)result).getNumberValue(), 0);
+       }
+
+       private void confirmAverage(Eval[] args, ErrorEval expectedError) {
+               Eval result = invokeAverage(args);
+               assertEquals(ErrorEval.class, result.getClass());
+               assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+       }
+       
+       public void testBasic() {
+               
+               ValueEval[] values = {
+                               new NumberEval(1),      
+                               new NumberEval(2),      
+                               new NumberEval(3),      
+                               new NumberEval(4),      
+               };
+               
+               confirmAverage(values, 2.5);
+               
+               values = new ValueEval[] {
+                               new NumberEval(1),      
+                               new NumberEval(2),
+                               BlankEval.INSTANCE,
+                               new NumberEval(3),      
+                               BlankEval.INSTANCE,
+                               new NumberEval(4),      
+                               BlankEval.INSTANCE,
+               };
+               
+               confirmAverage(values, 2.5);
+       }
+       
+       /**
+        * Valid cases where values are not pure numbers
+        */
+       public void testUnusualArgs() {
+               ValueEval[] values = {
+                               new NumberEval(1),      
+                               new NumberEval(2),      
+                               BoolEval.TRUE,  
+                               BoolEval.FALSE, 
+               };
+               
+               confirmAverage(values, 1.0);
+               
+       }
+
+       // currently disabled because MultiOperandNumericFunction.getNumberArray(Eval[], int, short)
+       // does not handle error values properly yet
+       public void XtestErrors() {
+               ValueEval[] values = {
+                               new NumberEval(1),      
+                               ErrorEval.NAME_INVALID, 
+                               new NumberEval(3),      
+                               ErrorEval.DIV_ZERO,     
+               };
+               confirmAverage(values, ErrorEval.NAME_INVALID);
+                       
+       }
+}
index fbaace921023cfed6401d5d7d3727563ee85f6ae..ae93a2d41b694b444fd3fd723dbde0ddca714de2 100755 (executable)
@@ -125,7 +125,7 @@ public final class TestCountFuncs extends TestCase {
                };
                Area2DEval arg0 = new Area2DEval(new AreaPtg("C1:C6"), values);
                
-               Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25), true);
+               Ref2DEval criteriaArg = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(25));
                Eval[] args=  { arg0, criteriaArg, };
                
                double actual = NumericFunctionInvoker.invoke(new Countif(), args);
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestIsBlank.java
new file mode 100755 (executable)
index 0000000..7ce2bd2
--- /dev/null
@@ -0,0 +1,62 @@
+/* ====================================================================
+   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.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
+/**
+ * Tests for Excel function ISBLANK()
+ * 
+ * @author Josh Micich
+ */
+public final class TestIsBlank extends TestCase {
+
+       
+
+       public void test3DArea() {
+        HSSFWorkbook wb = new HSSFWorkbook();
+        HSSFSheet sheet1 = wb.createSheet();
+        wb.setSheetName(0, "Sheet1");
+        wb.createSheet();
+        wb.setSheetName(1, "Sheet2");
+        HSSFRow row = sheet1.createRow(0);
+        HSSFCell cell = row.createCell((short)0);
+
+         
+        cell.setCellFormula("isblank(Sheet2!A1:A1)");
+        
+        HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet1, wb);
+        fe.setCurrentRow(row);
+        CellValue result = fe.evaluate(cell);
+        assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType());
+        assertEquals(true, result.getBooleanValue());
+        
+        cell.setCellFormula("isblank(D7:D7)");
+        
+        result = fe.evaluate(cell);
+        assertEquals(HSSFCell.CELL_TYPE_BOOLEAN, result.getCellType());
+        assertEquals(true, result.getBooleanValue());
+        
+   }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestLen.java
new file mode 100755 (executable)
index 0000000..a96fb4e
--- /dev/null
@@ -0,0 +1,73 @@
+/* ====================================================================
+   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.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+/**
+ * Tests for Excel function LEN()
+ * 
+ * @author Josh Micich
+ */
+public final class TestLen extends TestCase {
+
+       
+       private static Eval invokeLen(Eval text) {
+               Eval[] args = new Eval[] { text, };
+               return new Len().evaluate(args, -1, (short)-1);
+       }
+
+       private void confirmLen(Eval text, int expected) {
+               Eval result = invokeLen(text);
+               assertEquals(NumberEval.class, result.getClass());
+               assertEquals(expected, ((NumberEval)result).getNumberValue(), 0);
+       }
+
+       private void confirmLen(Eval text, ErrorEval expectedError) {
+               Eval result = invokeLen(text);
+               assertEquals(ErrorEval.class, result.getClass());
+               assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+       }
+       
+       public void testBasic() {
+               
+               confirmLen(new StringEval("galactic"), 8);
+       }
+       
+       /**
+        * Valid cases where text arg is not exactly a string
+        */
+       public void testUnusualArgs() {
+               
+               // text (first) arg type is number, other args are strings with fractional digits 
+               confirmLen(new NumberEval(123456), 6);
+               confirmLen(BoolEval.FALSE, 5);
+               confirmLen(BoolEval.TRUE, 4);
+               confirmLen(BlankEval.INSTANCE, 0);
+       }
+
+       public void testErrors() {
+               confirmLen(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+       }
+}
index ab9f609d1d88fca8f9fd016a384ea298228da207..dc3d595aedd77e993c0caefa10d44c0440d78ff4 100755 (executable)
@@ -77,13 +77,14 @@ public final class TestMid extends TestCase {
                
                // startPos is 1x1 area ref, numChars is cell ref
                AreaEval aeStart = new Area2DEval(new AreaPtg("A1:A1"), new ValueEval[] { new NumberEval(2), } );
-               RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new NumberEval(3),false);
+               RefEval reNumChars = new Ref2DEval(new ReferencePtg("B1"), new NumberEval(3));
                confirmMid(new StringEval("galactic"), aeStart, reNumChars, "ala");
 
                confirmMid(new StringEval("galactic"), new NumberEval(3.1), BlankEval.INSTANCE, "");
 
                confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.FALSE, "");
                confirmMid(new StringEval("galactic"), new NumberEval(3), BoolEval.TRUE, "l");
+               confirmMid(BlankEval.INSTANCE, new NumberEval(3), BoolEval.TRUE, "");
        
        }
 
index 9fbc9e42fed4d4b74040b409d05b358733bde3ff..73043911f450aa10b93b705dd058ae3541f9e63a 100755 (executable)
@@ -50,7 +50,7 @@ public final class TestSumproduct extends TestCase {
 
        public void testScalarSimple() {
                
-               RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3), true);
+               RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3));
                Eval[] args = {
                        refEval, 
                        new NumberEval(2),
index 6ecc3619c4cc3d957da58251c31ae8c7a9a10ff5..4d63cad1c58d82e7b327b35c04773cb84d13e5dc 100755 (executable)
@@ -50,7 +50,7 @@ public final class TestTFunc extends TestCase {
         * where cell A1 has the specified innerValue
         */
        private Eval invokeTWithReference(ValueEval innerValue) {
-               Eval arg = new Ref2DEval(new ReferencePtg((short)1, (short)1, false, false), innerValue, true);
+               Eval arg = new Ref2DEval(new ReferencePtg((short)1, (short)1, false, false), innerValue);
                return invokeT(arg);
        }
        
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTrim.java
new file mode 100755 (executable)
index 0000000..076ac1f
--- /dev/null
@@ -0,0 +1,78 @@
+/* ====================================================================
+   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.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.BoolEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+/**
+ * Tests for Excel function TRIM()
+ * 
+ * @author Josh Micich
+ */
+public final class TestTrim extends TestCase {
+
+       
+       private static Eval invokeTrim(Eval text) {
+               Eval[] args = new Eval[] { text, };
+               return new Trim().evaluate(args, -1, (short)-1);
+       }
+
+       private void confirmTrim(Eval text, String expected) {
+               Eval result = invokeTrim(text);
+               assertEquals(StringEval.class, result.getClass());
+               assertEquals(expected, ((StringEval)result).getStringValue());
+       }
+
+       private void confirmTrim(Eval text, ErrorEval expectedError) {
+               Eval result = invokeTrim(text);
+               assertEquals(ErrorEval.class, result.getClass());
+               assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+       }
+       
+       public void testBasic() {
+               
+               confirmTrim(new StringEval(" hi "), "hi");
+               confirmTrim(new StringEval("hi "), "hi");
+               confirmTrim(new StringEval("  hi"), "hi");
+               confirmTrim(new StringEval(" hi there  "), "hi there");
+               confirmTrim(new StringEval(""), "");
+               confirmTrim(new StringEval("   "), "");
+       }
+       
+       /**
+        * Valid cases where text arg is not exactly a string
+        */
+       public void testUnusualArgs() {
+               
+               // text (first) arg type is number, other args are strings with fractional digits 
+               confirmTrim(new NumberEval(123456), "123456");
+               confirmTrim(BoolEval.FALSE, "FALSE");
+               confirmTrim(BoolEval.TRUE, "TRUE");
+               confirmTrim(BlankEval.INSTANCE, "");
+       }
+
+       public void testErrors() {
+               confirmTrim(ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestXYNumericFunction.java
new file mode 100755 (executable)
index 0000000..c9f043b
--- /dev/null
@@ -0,0 +1,139 @@
+/* ====================================================================
+   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.functions;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.eval.Area2DEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+import org.apache.poi.hssf.record.formula.eval.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+/**
+ * Tests for Excel functions SUMX2MY2(), SUMX2PY2(), SUMXMY2()
+ * 
+ * @author Josh Micich
+ */
+public final class TestXYNumericFunction extends TestCase {
+       private static final Function SUM_SQUARES = new Sumx2py2();
+       private static final Function DIFF_SQUARES = new Sumx2my2();
+       private static final Function SUM_SQUARES_OF_DIFFS = new Sumxmy2();
+       
+       private static Eval invoke(Function function, Eval xArray, Eval yArray) {
+               Eval[] args = new Eval[] { xArray, yArray, };
+               return function.evaluate(args, -1, (short)-1);
+       }
+
+       private void confirm(Function function, Eval xArray, Eval yArray, double expected) {
+               Eval result = invoke(function, xArray, yArray);
+               assertEquals(NumberEval.class, result.getClass());
+               assertEquals(expected, ((NumberEval)result).getNumberValue(), 0);
+       }
+       private void confirmError(Function function, Eval xArray, Eval yArray, ErrorEval expectedError) {
+               Eval result = invoke(function, xArray, yArray);
+               assertEquals(ErrorEval.class, result.getClass());
+               assertEquals(expectedError.getErrorCode(), ((ErrorEval)result).getErrorCode());
+       }
+
+       private void confirmError(Eval xArray, Eval yArray, ErrorEval expectedError) {
+               confirmError(SUM_SQUARES, xArray, yArray, expectedError);
+               confirmError(DIFF_SQUARES, xArray, yArray, expectedError);
+               confirmError(SUM_SQUARES_OF_DIFFS, xArray, yArray, expectedError);
+       }
+       
+       public void testBasic() {
+               ValueEval[] xValues = {
+                       new NumberEval(1),      
+                       new NumberEval(2),      
+               };
+               ValueEval areaEvalX = createAreaEval(xValues);
+               confirm(SUM_SQUARES, areaEvalX, areaEvalX, 10.0);
+               confirm(DIFF_SQUARES, areaEvalX, areaEvalX, 0.0);
+               confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalX, 0.0);
+
+               ValueEval[] yValues = {
+                       new NumberEval(3),      
+                       new NumberEval(4),      
+               };
+               ValueEval areaEvalY = createAreaEval(yValues);
+               confirm(SUM_SQUARES, areaEvalX, areaEvalY, 30.0);
+               confirm(DIFF_SQUARES, areaEvalX, areaEvalY, -20.0);
+               confirm(SUM_SQUARES_OF_DIFFS, areaEvalX, areaEvalY, 8.0);
+       }
+       
+       /**
+        * number of items in array is not limited to 30
+        */
+       public void testLargeArrays() {
+               ValueEval[] xValues = createMockNumberArray(100, 3);
+               ValueEval[] yValues = createMockNumberArray(100, 2);
+               
+               confirm(SUM_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 1300.0);
+               confirm(DIFF_SQUARES, createAreaEval(xValues), createAreaEval(yValues), 500.0);
+               confirm(SUM_SQUARES_OF_DIFFS, createAreaEval(xValues), createAreaEval(yValues), 100.0);
+       }
+       
+
+       private ValueEval[] createMockNumberArray(int size, double value) {
+               ValueEval[] result = new ValueEval[size];
+               for (int i = 0; i < result.length; i++) {
+                       result[i] = new NumberEval(value);
+               }
+               return result;
+       }
+
+       private static ValueEval createAreaEval(ValueEval[] values) {
+               String refStr = "A1:A" + values.length;
+               return new Area2DEval(new AreaPtg(refStr), values);
+       }
+
+       public void testErrors() {
+               ValueEval[] xValues = {
+                               ErrorEval.REF_INVALID,  
+                               new NumberEval(2),      
+               };
+               ValueEval areaEvalX = createAreaEval(xValues);
+               ValueEval[] yValues = {
+                               new NumberEval(2),      
+                               ErrorEval.NULL_INTERSECTION,    
+               };
+               ValueEval areaEvalY = createAreaEval(yValues);
+               ValueEval[] zValues = { // wrong size
+                               new NumberEval(2),      
+               };
+               ValueEval areaEvalZ = createAreaEval(zValues);
+               
+               // if either arg is an error, that error propagates
+               confirmError(ErrorEval.REF_INVALID, ErrorEval.NAME_INVALID, ErrorEval.REF_INVALID);
+               confirmError(areaEvalX, ErrorEval.NAME_INVALID, ErrorEval.NAME_INVALID);
+               confirmError(ErrorEval.NAME_INVALID, areaEvalX, ErrorEval.NAME_INVALID);
+
+               // array sizes must match
+               confirmError(areaEvalX, areaEvalZ, ErrorEval.NA);
+               confirmError(areaEvalZ, areaEvalY, ErrorEval.NA);
+
+               // any error in an array item propagates up
+               confirmError(areaEvalX, areaEvalX, ErrorEval.REF_INVALID);
+               
+               // search for errors array by array, not pair by pair
+               confirmError(areaEvalX, areaEvalY, ErrorEval.REF_INVALID);
+               confirmError(areaEvalY, areaEvalX, ErrorEval.NULL_INTERSECTION);
+               
+       }
+}
index 7a1d51903a2fa4f81208c8e70952942589a7fb61..5b597a67c873f37f49346dc113dfbc99c97f9830 100644 (file)
@@ -14,7 +14,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
+
 package org.apache.poi.hssf;
 
 import junit.framework.Test;
@@ -25,80 +25,8 @@ import org.apache.poi.hssf.eventmodel.TestModelFactory;
 import org.apache.poi.hssf.model.TestDrawingManager;
 import org.apache.poi.hssf.model.TestFormulaParser;
 import org.apache.poi.hssf.model.TestSheet;
-import org.apache.poi.hssf.record.TestAreaFormatRecord;
-import org.apache.poi.hssf.record.TestAreaRecord;
-import org.apache.poi.hssf.record.TestAxisLineFormatRecord;
-import org.apache.poi.hssf.record.TestAxisOptionsRecord;
-import org.apache.poi.hssf.record.TestAxisParentRecord;
-import org.apache.poi.hssf.record.TestAxisRecord;
-import org.apache.poi.hssf.record.TestAxisUsedRecord;
-import org.apache.poi.hssf.record.TestBarRecord;
-import org.apache.poi.hssf.record.TestBoundSheetRecord;
-import org.apache.poi.hssf.record.TestCategorySeriesAxisRecord;
-import org.apache.poi.hssf.record.TestChartRecord;
-import org.apache.poi.hssf.record.TestDatRecord;
-import org.apache.poi.hssf.record.TestDataFormatRecord;
-import org.apache.poi.hssf.record.TestDefaultDataLabelTextPropertiesRecord;
-import org.apache.poi.hssf.record.TestFontBasisRecord;
-import org.apache.poi.hssf.record.TestFontIndexRecord;
-import org.apache.poi.hssf.record.TestFormulaRecord;
-import org.apache.poi.hssf.record.TestFrameRecord;
-import org.apache.poi.hssf.record.TestLegendRecord;
-import org.apache.poi.hssf.record.TestLineFormatRecord;
-import org.apache.poi.hssf.record.TestLinkedDataRecord;
-import org.apache.poi.hssf.record.TestNameRecord;
-import org.apache.poi.hssf.record.TestNumberFormatIndexRecord;
-import org.apache.poi.hssf.record.TestObjectLinkRecord;
-import org.apache.poi.hssf.record.TestPaletteRecord;
-import org.apache.poi.hssf.record.TestPlotAreaRecord;
-import org.apache.poi.hssf.record.TestPlotGrowthRecord;
-import org.apache.poi.hssf.record.TestRecordFactory;
-import org.apache.poi.hssf.record.TestSCLRecord;
-import org.apache.poi.hssf.record.TestSSTDeserializer;
-import org.apache.poi.hssf.record.TestSSTRecord;
-import org.apache.poi.hssf.record.TestSSTRecordSizeCalculator;
-import org.apache.poi.hssf.record.TestSeriesChartGroupIndexRecord;
-import org.apache.poi.hssf.record.TestSeriesIndexRecord;
-import org.apache.poi.hssf.record.TestSeriesLabelsRecord;
-import org.apache.poi.hssf.record.TestSeriesListRecord;
-import org.apache.poi.hssf.record.TestSeriesRecord;
-import org.apache.poi.hssf.record.TestSeriesTextRecord;
-import org.apache.poi.hssf.record.TestSeriesToChartGroupRecord;
-import org.apache.poi.hssf.record.TestSheetPropertiesRecord;
-import org.apache.poi.hssf.record.TestStringRecord;
-import org.apache.poi.hssf.record.TestSupBookRecord;
-import org.apache.poi.hssf.record.TestTextRecord;
-import org.apache.poi.hssf.record.TestTickRecord;
-import org.apache.poi.hssf.record.TestUnicodeString;
-import org.apache.poi.hssf.record.TestUnitsRecord;
-import org.apache.poi.hssf.record.TestValueRangeRecord;
-import org.apache.poi.hssf.record.aggregates.TestRowRecordsAggregate;
-import org.apache.poi.hssf.record.aggregates.TestValueRecordsAggregate;
-import org.apache.poi.hssf.record.formula.AllFormulaTests;
-import org.apache.poi.hssf.usermodel.TestBugs;
-import org.apache.poi.hssf.usermodel.TestCellStyle;
-import org.apache.poi.hssf.usermodel.TestCloneSheet;
-import org.apache.poi.hssf.usermodel.TestEscherGraphics;
-import org.apache.poi.hssf.usermodel.TestEscherGraphics2d;
-import org.apache.poi.hssf.usermodel.TestFontDetails;
-import org.apache.poi.hssf.usermodel.TestFormulas;
-import org.apache.poi.hssf.usermodel.TestHSSFCell;
-import org.apache.poi.hssf.usermodel.TestHSSFClientAnchor;
-import org.apache.poi.hssf.usermodel.TestHSSFComment;
-import org.apache.poi.hssf.usermodel.TestHSSFDateUtil;
-import org.apache.poi.hssf.usermodel.TestHSSFHeaderFooter;
-import org.apache.poi.hssf.usermodel.TestHSSFPalette;
-import org.apache.poi.hssf.usermodel.TestHSSFRichTextString;
-import org.apache.poi.hssf.usermodel.TestHSSFRow;
-import org.apache.poi.hssf.usermodel.TestHSSFSheet;
-import org.apache.poi.hssf.usermodel.TestHSSFSheetOrder;
-import org.apache.poi.hssf.usermodel.TestHSSFSheetSetOrder;
-import org.apache.poi.hssf.usermodel.TestHSSFWorkbook;
-import org.apache.poi.hssf.usermodel.TestNamedRange;
-import org.apache.poi.hssf.usermodel.TestReadWriteChart;
-import org.apache.poi.hssf.usermodel.TestSanityChecker;
-import org.apache.poi.hssf.usermodel.TestSheetShiftRows;
-import org.apache.poi.hssf.usermodel.TestWorkbook;
+import org.apache.poi.hssf.record.AllRecordTests;
+import org.apache.poi.hssf.usermodel.AllUserModelTests;
 import org.apache.poi.hssf.util.TestAreaReference;
 import org.apache.poi.hssf.util.TestCellReference;
 import org.apache.poi.hssf.util.TestRKUtil;
@@ -112,113 +40,30 @@ import org.apache.poi.hssf.util.TestSheetReferences;
  * 
  * @author Andrew C. Oliver acoliver@apache.org
  */
-public final class HSSFTests
-{
+public final class HSSFTests {
 
-    public static void main(String[] args)
-    {
+    public static void main(String[] args) {
         junit.textui.TestRunner.run(suite());
     }
 
-    public static Test suite()
-    {
-        TestSuite suite =
-            new TestSuite("Tests for org.apache.poi.hssf");
-        //$JUnit-BEGIN$
-
-    suite.addTest(new TestSuite(TestBugs.class));
-    suite.addTest(new TestSuite(TestCloneSheet.class));
-    suite.addTest(new TestSuite(TestEscherGraphics.class));
-    suite.addTest(new TestSuite(TestEscherGraphics2d.class));
-    suite.addTest(new TestSuite(TestFontDetails.class));
-    suite.addTest(new TestSuite(TestHSSFClientAnchor.class));
-    suite.addTest(new TestSuite(TestHSSFHeaderFooter.class));
-    suite.addTest(new TestSuite(TestHSSFRichTextString.class));
-    suite.addTest(new TestSuite(TestHSSFSheetOrder.class));
-    suite.addTest(new TestSuite(TestHSSFSheetSetOrder.class));
-    suite.addTest(new TestSuite(TestHSSFWorkbook.class));
-    suite.addTest(new TestSuite(TestSanityChecker.class));
-    suite.addTest(new TestSuite(TestSheetShiftRows.class));
-
-        suite.addTest(new TestSuite(TestCellStyle.class));
-        suite.addTest(new TestSuite(TestFormulas.class));
-        suite.addTest(new TestSuite(TestHSSFCell.class));
-        suite.addTest(new TestSuite(TestHSSFDateUtil.class));
-        suite.addTest(new TestSuite(TestHSSFPalette.class));
-        suite.addTest(new TestSuite(TestHSSFRow.class));
-        suite.addTest(new TestSuite(TestHSSFSheet.class));
-        suite.addTest(new TestSuite(TestNamedRange.class));
-        suite.addTest(new TestSuite(TestReadWriteChart.class));
-        suite.addTest(new TestSuite(TestWorkbook.class));
-
+    public static Test suite() {
+        TestSuite suite = new TestSuite("Tests for org.apache.poi.hssf");
+        // $JUnit-BEGIN$
 
+        suite.addTest(AllUserModelTests.suite());
+        suite.addTest(AllRecordTests.suite());
 
         suite.addTest(new TestSuite(TestFormulaParser.class));
-        suite.addTest(new TestSuite(TestAreaFormatRecord.class));
-        suite.addTest(new TestSuite(TestAreaRecord.class));
-        suite.addTest(new TestSuite(TestAxisLineFormatRecord.class));
-        suite.addTest(new TestSuite(TestAxisOptionsRecord.class));
-        suite.addTest(new TestSuite(TestAxisParentRecord.class));
-        suite.addTest(new TestSuite(TestAxisRecord.class));
-        suite.addTest(new TestSuite(TestAxisUsedRecord.class));
-        suite.addTest(new TestSuite(TestBarRecord.class));
-        suite.addTest(new TestSuite(TestBoundSheetRecord.class));
-        suite.addTest(new TestSuite(TestCategorySeriesAxisRecord.class));
-        suite.addTest(new TestSuite(TestChartRecord.class));
-        suite.addTest(new TestSuite(TestDatRecord.class));
-        suite.addTest(new TestSuite(TestDataFormatRecord.class));
-        suite.addTest(
-            new TestSuite(TestDefaultDataLabelTextPropertiesRecord.class));
-        suite.addTest(new TestSuite(TestFontBasisRecord.class));
-        suite.addTest(new TestSuite(TestFontIndexRecord.class));
-        suite.addTest(new TestSuite(TestFormulaRecord.class));
-        suite.addTest(new TestSuite(TestFrameRecord.class));
-        suite.addTest(new TestSuite(TestLegendRecord.class));
-        suite.addTest(new TestSuite(TestLineFormatRecord.class));
-        suite.addTest(new TestSuite(TestLinkedDataRecord.class));
-        suite.addTest(new TestSuite(TestNumberFormatIndexRecord.class));
-        suite.addTest(new TestSuite(TestObjectLinkRecord.class));
-        suite.addTest(new TestSuite(TestPaletteRecord.class));
-        suite.addTest(new TestSuite(TestPlotAreaRecord.class));
-        suite.addTest(new TestSuite(TestPlotGrowthRecord.class));
-        suite.addTest(new TestSuite(TestRecordFactory.class));
-        suite.addTest(new TestSuite(TestSCLRecord.class));
-        suite.addTest(new TestSuite(TestSSTDeserializer.class));
-        suite.addTest(new TestSuite(TestSSTRecord.class));
-        suite.addTest(new TestSuite(TestSSTRecordSizeCalculator.class));
-        suite.addTest(new TestSuite(TestSeriesChartGroupIndexRecord.class));
-        suite.addTest(new TestSuite(TestSeriesIndexRecord.class));
-        suite.addTest(new TestSuite(TestSeriesLabelsRecord.class));
-        suite.addTest(new TestSuite(TestSeriesListRecord.class));
-        suite.addTest(new TestSuite(TestSeriesRecord.class));
-        suite.addTest(new TestSuite(TestSeriesTextRecord.class));
-        suite.addTest(new TestSuite(TestSeriesToChartGroupRecord.class));
-        suite.addTest(new TestSuite(TestSheetPropertiesRecord.class));
-        suite.addTest(new TestSuite(TestStringRecord.class));
-        suite.addTest(new TestSuite(TestSupBookRecord.class));
-        suite.addTest(new TestSuite(TestTextRecord.class));
-        suite.addTest(new TestSuite(TestTickRecord.class));
-        suite.addTest(new TestSuite(TestUnicodeString.class));
-        suite.addTest(new TestSuite(TestUnitsRecord.class));
-        suite.addTest(new TestSuite(TestValueRangeRecord.class));
-        suite.addTest(new TestSuite(TestRowRecordsAggregate.class));
         suite.addTest(new TestSuite(TestAreaReference.class));
         suite.addTest(new TestSuite(TestCellReference.class));
-                 suite.addTest(new TestSuite(TestRangeAddress.class));         
+        suite.addTest(new TestSuite(TestRangeAddress.class));
         suite.addTest(new TestSuite(TestRKUtil.class));
         suite.addTest(new TestSuite(TestSheetReferences.class));
-        
-        
-        suite.addTest(AllFormulaTests.suite());
-                 suite.addTest(new TestSuite(TestValueRecordsAggregate.class));
-                 suite.addTest(new TestSuite(TestNameRecord.class));
-                  suite.addTest(new TestSuite(TestEventRecordFactory.class));
-                  suite.addTest(new TestSuite(TestModelFactory.class));
-                  suite.addTest(new TestSuite(TestDrawingManager.class));
-                  suite.addTest(new TestSuite(TestSheet.class));
-
-        suite.addTest(new TestSuite(TestHSSFComment.class));
-        //$JUnit-END$
+        suite.addTest(new TestSuite(TestEventRecordFactory.class));
+        suite.addTest(new TestSuite(TestModelFactory.class));
+        suite.addTest(new TestSuite(TestDrawingManager.class));
+        suite.addTest(new TestSuite(TestSheet.class));
+        // $JUnit-END$
         return suite;
     }
 }
index 97766d6aaa13fb4633854f7b88bdb43c850a7427..6260d878bc89ada3ebe92c7fa615baa1c387cde3 100644 (file)
Binary files a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls
new file mode 100755 (executable)
index 0000000..07eafb4
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/externalFunctionExample.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png
new file mode 100755 (executable)
index 0000000..90a915a
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/logoKarmokar4.png differ
index e7400bc3512fdf1c4fdfda0670a71a202d4e39ae..7b42bf333f690f7dc67a1560e8470c63a3583c53 100644 (file)
         
 package org.apache.poi.hssf.model;
 
-import java.util.List;
-
+import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 
 import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
 import org.apache.poi.hssf.record.formula.AddPtg;
+import org.apache.poi.hssf.record.formula.AreaPtg;
 import org.apache.poi.hssf.record.formula.AttrPtg;
 import org.apache.poi.hssf.record.formula.BoolPtg;
+import org.apache.poi.hssf.record.formula.ConcatPtg;
 import org.apache.poi.hssf.record.formula.DividePtg;
 import org.apache.poi.hssf.record.formula.EqualPtg;
+import org.apache.poi.hssf.record.formula.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;
 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.record.formula.SubtractPtg;
 import org.apache.poi.hssf.record.formula.UnaryMinusPtg;
 import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
 import org.apache.poi.hssf.usermodel.HSSFCell;
@@ -64,15 +71,25 @@ public class TestFormulaParser extends TestCase {
     public void tearDown() {
         
     }
+    /**
+     * @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();
+        assertNotNull("Ptg array should not be null", result);
+        return result;
+    }
     
     public void testSimpleFormula() {
-        FormulaParser fp = new FormulaParser("2+2;",null);
+        FormulaParser fp = new FormulaParser("2+2",null);
         fp.parse();
         Ptg[] ptgs = fp.getRPNPtg();
         assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3);
     }
     public void testFormulaWithSpace1() {
-        FormulaParser fp = new FormulaParser(" 2 + 2 ;",null);
+        FormulaParser fp = new FormulaParser(" 2 + 2 ",null);
         fp.parse();
         Ptg[] ptgs = fp.getRPNPtg();
         assertTrue("three tokens expected, got "+ptgs.length,ptgs.length == 3);
@@ -85,7 +102,7 @@ public class TestFormulaParser extends TestCase {
     public void testFormulaWithSpace2() {
         Ptg[] ptgs;
         FormulaParser fp;
-        fp = new FormulaParser("2+ sum( 3 , 4) ;",null);
+        fp = new FormulaParser("2+ sum( 3 , 4) ",null);
         fp.parse();
         ptgs = fp.getRPNPtg();
         assertTrue("five tokens expected, got "+ptgs.length,ptgs.length == 5);
@@ -94,7 +111,7 @@ public class TestFormulaParser extends TestCase {
      public void testFormulaWithSpaceNRef() {
         Ptg[] ptgs;
         FormulaParser fp;
-        fp = new FormulaParser("sum( A2:A3 );",null);
+        fp = new FormulaParser("sum( A2:A3 )",null);
         fp.parse();
         ptgs = fp.getRPNPtg();
         assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2);
@@ -103,7 +120,7 @@ public class TestFormulaParser extends TestCase {
     public void testFormulaWithString() {
         Ptg[] ptgs;
         FormulaParser fp;
-        fp = new FormulaParser("\"hello\" & \"world\" ;",null);
+        fp = new FormulaParser("\"hello\" & \"world\" ",null);
         fp.parse();
         ptgs = fp.getRPNPtg();
         assertTrue("three token expected, got " + ptgs.length, ptgs.length == 3);
@@ -276,20 +293,21 @@ public class TestFormulaParser extends TestCase {
        }
            
     public void testMacroFunction() {
-        Workbook w = new Workbook();
+        Workbook w = Workbook.createWorkbook();
         FormulaParser fp = new FormulaParser("FOO()", w);
         fp.parse();
         Ptg[] ptg = fp.getRPNPtg();
 
-        AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[0];
-        assertEquals("externalflag", tfunc.getName());
-
-        NamePtg tname = (NamePtg) ptg[1];
+        // the name gets encoded as the first arg
+        NamePtg tname = (NamePtg) ptg[0];
         assertEquals("FOO", tname.toFormulaString(w));
+        
+        AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[1];
+        assertEquals("externalflag", tfunc.getName());
     }
 
     public void testEmbeddedSlash() {
-        FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\");",null);
+        FormulaParser fp = new FormulaParser("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",null);
         fp.parse();
         Ptg[] ptg = fp.getRPNPtg();
         assertTrue("first ptg is string",ptg[0] instanceof StringPtg);
@@ -589,4 +607,234 @@ public class TestFormulaParser extends TestCase {
         };
         assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs));
     }
+
+    public void testPercent() {
+        Ptg[] ptgs;
+        ptgs = parseFormula("5%");
+        assertEquals(2, ptgs.length);
+        assertEquals(ptgs[0].getClass(), IntPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+
+        // spaces OK
+        ptgs = parseFormula(" 250 % ");
+        assertEquals(2, ptgs.length);
+        assertEquals(ptgs[0].getClass(), IntPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+        
+        
+        // double percent OK 
+        ptgs = parseFormula("12345.678%%");
+        assertEquals(3, ptgs.length);
+        assertEquals(ptgs[0].getClass(), NumberPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+        assertEquals(ptgs[2].getClass(), PercentPtg.class);
+        
+        // percent of a bracketed expression
+        ptgs = parseFormula("(A1+35)%*B1%");
+        assertEquals(8, ptgs.length);
+        assertEquals(ptgs[4].getClass(), PercentPtg.class);
+        assertEquals(ptgs[6].getClass(), PercentPtg.class);
+        
+        // percent of a text quantity
+        ptgs = parseFormula("\"8.75\"%");
+        assertEquals(2, ptgs.length);
+        assertEquals(ptgs[0].getClass(), StringPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+
+        // percent to the power of
+        ptgs = parseFormula("50%^3");
+        assertEquals(4, ptgs.length);
+        assertEquals(ptgs[0].getClass(), IntPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+        assertEquals(ptgs[2].getClass(), IntPtg.class);
+        assertEquals(ptgs[3].getClass(), PowerPtg.class);
+
+        //
+        // things that parse OK but would *evaluate* to an error
+        
+        ptgs = parseFormula("\"abc\"%");
+        assertEquals(2, ptgs.length);
+        assertEquals(ptgs[0].getClass(), StringPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+        
+        ptgs = parseFormula("#N/A%");
+        assertEquals(2, ptgs.length);
+        assertEquals(ptgs[0].getClass(), ErrPtg.class);
+        assertEquals(ptgs[1].getClass(), PercentPtg.class);
+    }
+    
+    /**
+     * Tests combinations of various operators in the absence of brackets
+     */
+    public void testPrecedenceAndAssociativity() {
+
+        Class[] expClss;
+        
+        // TRUE=TRUE=2=2  evaluates to FALSE
+        expClss = new Class[] { BoolPtg.class, BoolPtg.class, EqualPtg.class, 
+                IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class,  };
+        confirmTokenClasses("TRUE=TRUE=2=2", expClss);
+       
+
+        //  2^3^2    evaluates to 64 not 512
+        expClss = new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, 
+                IntPtg.class, PowerPtg.class, };
+        confirmTokenClasses("2^3^2", expClss);
+        
+        // "abc" & 2 + 3 & "def"   evaluates to "abc5def"
+        expClss = new Class[] { StringPtg.class, IntPtg.class, IntPtg.class, 
+                AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class, };
+        confirmTokenClasses("\"abc\"&2+3&\"def\"", expClss);
+        
+        
+        //  (1 / 2) - (3 * 4)
+        expClss = new Class[] { IntPtg.class, IntPtg.class, DividePtg.class, 
+                IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class, };
+        confirmTokenClasses("1/2-3*4", expClss);
+        
+        // 2 * (2^2)
+        expClss = new Class[] { IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class, };
+        // NOT: (2 *2) ^ 2 -> int int multiply int power
+        confirmTokenClasses("2*2^2", expClss);
+        
+        //  2^200% -> 2 not 1.6E58
+        expClss = new Class[] { IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class, };
+        confirmTokenClasses("2^200%", expClss);
+    }
+    
+    private static void confirmTokenClasses(String formula, Class[] expectedClasses) {
+        Ptg[] ptgs = parseFormula(formula);
+        assertEquals(expectedClasses.length, ptgs.length);
+        for (int i = 0; i < expectedClasses.length; i++) {
+            if(expectedClasses[i] != ptgs[i].getClass()) {
+                fail("difference at token[" + i + "]: expected ("
+                    + expectedClasses[i].getName() + ") but got (" 
+                    + ptgs[i].getClass().getName() + ")");
+            }
+        }
+    }
+
+    public void testPower() {
+        confirmTokenClasses("2^5", new Class[] { IntPtg.class, IntPtg.class, PowerPtg.class, });
+    }
+
+    private static Ptg parseSingleToken(String formula, Class ptgClass) {
+        Ptg[] ptgs = parseFormula(formula);
+        assertEquals(1, ptgs.length);
+        Ptg result = ptgs[0];
+        assertEquals(ptgClass, result.getClass());
+        return result;
+    }
+
+    public void testParseNumber() {
+        IntPtg ip;
+        
+        // bug 33160
+        ip = (IntPtg) parseSingleToken("40", IntPtg.class);
+        assertEquals(40, ip.getValue());
+        ip = (IntPtg) parseSingleToken("40000", IntPtg.class);
+        assertEquals(40000, ip.getValue());
+        
+        // check the upper edge of the IntPtg range:
+        ip = (IntPtg) parseSingleToken("65535", IntPtg.class);
+        assertEquals(65535, ip.getValue());
+        NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class);
+        assertEquals(65536, np.getValue(), 0);
+        
+        np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class);
+        assertEquals(65534.6, np.getValue(), 0);
+    }
+    
+    public void testMissingArgs() {
+        
+        Class[] expClss;
+        
+        expClss = new Class[] { ReferencePtg.class, MissingArgPtg.class, ReferencePtg.class, 
+                FuncVarPtg.class, };
+        confirmTokenClasses("if(A1, ,C1)", expClss);
+        
+        expClss = new Class[] { MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class,
+                FuncVarPtg.class, };
+        confirmTokenClasses("counta( , A1:B2, )", expClss);
+    }
+
+    public void testParseErrorLiterals() {
+        
+        confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!");
+        confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!");
+        confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!");
+        confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!");
+        confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?");
+        confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!");
+        confirmParseErrorLiteral(ErrPtg.N_A, "#N/A");
+    }
+
+    private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) {
+        assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class));
+    }
+    
+    /**
+     * To aid readability the parameters have been encoded with single quotes instead of double
+     * quotes.  This method converts single quotes to double quotes before performing the parse
+     * and result check.
+     */
+    private static void confirmStringParse(String singleQuotedValue) {
+        // formula: internal quotes become double double, surround with double quotes
+        String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"';
+        String expectedValue = singleQuotedValue.replace('\'', '"');
+        
+        StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class);
+        assertEquals(expectedValue, sp.getValue());
+    }
+    
+    public void testPaseStringLiterals() {
+        confirmStringParse("goto considered harmful");
+        
+        confirmStringParse("goto 'considered' harmful");
+        
+        confirmStringParse("");
+        confirmStringParse("'");
+        confirmStringParse("''");
+        confirmStringParse("' '");
+        confirmStringParse(" ' ");
+    }
+    
+    public void testParseSumIfSum() {
+        String formulaString;
+        Ptg[] ptgs;
+        ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))");
+        formulaString = FormulaParser.toFormulaString(null, ptgs);
+        assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString);
+
+        ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)");
+        formulaString = FormulaParser.toFormulaString(null, ptgs);
+        assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString);
+    }
+    public void testParserErrors() {
+        parseExpectedException("1 2");
+        parseExpectedException(" 12 . 345  ");
+        parseExpectedException("1 .23  ");
+
+        parseExpectedException("sum(#NAME)");
+        parseExpectedException("1 + #N / A * 2");
+        parseExpectedException("#value?");
+        parseExpectedException("#DIV/ 0+2");
+        
+        
+        if (false) { // TODO - add functionality to detect func arg count mismatch
+            parseExpectedException("IF(TRUE)");
+            parseExpectedException("countif(A1:B5, C1, D1)");
+        }
+    }
+    
+    private static void parseExpectedException(String formula) {
+        try {
+            parseFormula(formula);
+            throw new AssertionFailedError("expected parse exception");
+        } catch (RuntimeException e) {
+            // TODO - catch more specific exception
+            // expected during successful test
+            return;
+        }
+    }
 }
diff --git a/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java b/src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
new file mode 100755 (executable)
index 0000000..b1acfea
--- /dev/null
@@ -0,0 +1,104 @@
+/* ====================================================================
+   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;
+
+import org.apache.poi.hssf.record.formula.AllFormulaTests;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for package <tt>org.apache.poi.hssf.record</tt>.
+ * 
+ * @author Josh Micich
+ */
+public class AllRecordTests {
+       
+       public static Test suite() {
+               TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.record");
+
+               result.addTest(AllFormulaTests.suite());
+               
+               result.addTestSuite(TestAreaFormatRecord.class);
+               result.addTestSuite(TestAreaRecord.class);
+               result.addTestSuite(TestAxisLineFormatRecord.class);
+               result.addTestSuite(TestAxisOptionsRecord.class);
+               result.addTestSuite(TestAxisParentRecord.class);
+               result.addTestSuite(TestAxisRecord.class);
+               result.addTestSuite(TestAxisUsedRecord.class);
+               result.addTestSuite(TestBOFRecord.class);
+               result.addTestSuite(TestBarRecord.class);
+               result.addTestSuite(TestBoundSheetRecord.class);
+               result.addTestSuite(TestCategorySeriesAxisRecord.class);
+               result.addTestSuite(TestChartRecord.class);
+               result.addTestSuite(TestChartTitleFormatRecord.class);
+               result.addTestSuite(TestCommonObjectDataSubRecord.class);
+               result.addTestSuite(TestDatRecord.class);
+               result.addTestSuite(TestDataFormatRecord.class);
+               result.addTestSuite(TestDefaultDataLabelTextPropertiesRecord.class);
+               result.addTestSuite(TestDrawingGroupRecord.class);
+               result.addTestSuite(TestEmbeddedObjectRefSubRecord.class);
+               result.addTestSuite(TestEndSubRecord.class);
+               result.addTestSuite(TestEscherAggregate.class);
+               result.addTestSuite(TestFontBasisRecord.class);
+               result.addTestSuite(TestFontIndexRecord.class);
+               result.addTestSuite(TestFormulaRecord.class);
+               result.addTestSuite(TestFrameRecord.class);
+               result.addTestSuite(TestHyperlinkRecord.class);
+               result.addTestSuite(TestLegendRecord.class);
+               result.addTestSuite(TestLineFormatRecord.class);
+               result.addTestSuite(TestLinkedDataRecord.class);
+               result.addTestSuite(TestMergeCellsRecord.class);
+               result.addTestSuite(TestNameRecord.class);
+               result.addTestSuite(TestNoteRecord.class);
+               result.addTestSuite(TestNoteStructureSubRecord.class);
+               result.addTestSuite(TestNumberFormatIndexRecord.class);
+               result.addTestSuite(TestObjRecord.class);
+               result.addTestSuite(TestObjectLinkRecord.class);
+               result.addTestSuite(TestPaletteRecord.class);
+               result.addTestSuite(TestPaneRecord.class);
+               result.addTestSuite(TestPlotAreaRecord.class);
+               result.addTestSuite(TestPlotGrowthRecord.class);
+               result.addTestSuite(TestRecordFactory.class);
+               result.addTestSuite(TestSCLRecord.class);
+               result.addTestSuite(TestSSTDeserializer.class);
+               result.addTestSuite(TestSSTRecord.class);
+               result.addTestSuite(TestSSTRecordSizeCalculator.class);
+               result.addTestSuite(TestSeriesChartGroupIndexRecord.class);
+               result.addTestSuite(TestSeriesIndexRecord.class);
+               result.addTestSuite(TestSeriesLabelsRecord.class);
+               result.addTestSuite(TestSeriesListRecord.class);
+               result.addTestSuite(TestSeriesRecord.class);
+               result.addTestSuite(TestSeriesTextRecord.class);
+               result.addTestSuite(TestSeriesToChartGroupRecord.class);
+               result.addTestSuite(TestSheetPropertiesRecord.class);
+               result.addTestSuite(TestStringRecord.class);
+               result.addTestSuite(TestSubRecord.class);
+               result.addTestSuite(TestSupBookRecord.class);
+               result.addTestSuite(TestTextObjectBaseRecord.class);
+               result.addTestSuite(TestTextObjectRecord.class);
+               result.addTestSuite(TestTextRecord.class);
+               result.addTestSuite(TestTickRecord.class);
+               result.addTestSuite(TestUnicodeNameRecord.class);
+               result.addTestSuite(TestUnicodeString.class);
+               result.addTestSuite(TestUnitsRecord.class);
+               result.addTestSuite(TestValueRangeRecord.class);
+               return result;
+       }
+}
index fca5bae5451c3ad93d504ebda19b2047e17e0489..3727989efb1670ca3004d78d72d16dedf1a2b513 100644 (file)
@@ -29,15 +29,25 @@ import junit.framework.TestCase;
  *
  * @author Andrew C. Oliver (acoliver at apache dot org)
  */
-public class TestSupBookRecord
-        extends TestCase
-{
+public final class TestSupBookRecord extends TestCase {
     /**
      * This contains a fake data section of a SubBookRecord
      */
-    byte[] data = new byte[] {
-        (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04
+    byte[] dataIR = new byte[] {
+        (byte)0x04,(byte)0x00,(byte)0x01,(byte)0x04,
     };
+    byte[] dataAIF = new byte[] {
+        (byte)0x01,(byte)0x00,(byte)0x01,(byte)0x3A,
+    };
+    byte[] dataER = new byte[] {
+        (byte)0x02,(byte)0x00,
+        (byte)0x07,(byte)0x00,   (byte)0x00,   
+                (byte)'t', (byte)'e', (byte)'s', (byte)'t', (byte)'U', (byte)'R', (byte)'L',  
+        (byte)0x06,(byte)0x00,   (byte)0x00,   
+                (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'1', 
+        (byte)0x06,(byte)0x00,   (byte)0x00,   
+                (byte)'S', (byte)'h', (byte)'e', (byte)'e', (byte)'t', (byte)'2', 
+   };
 
     public TestSupBookRecord(String name)
     {
@@ -47,36 +57,67 @@ public class TestSupBookRecord
     /**
      * tests that we can load the record
      */
-    public void testLoad()
-            throws Exception
-    {
+    public void testLoadIR() {
 
-        SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, (short)data.length, data));      
-        assertEquals( 0x401, record.getFlag());             //expected flag
+        SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataIR));      
+        assertTrue( record.isInternalReferences() );             //expected flag
         assertEquals( 0x4, record.getNumberOfSheets() );    //expected # of sheets
 
         assertEquals( 8, record.getRecordSize() );  //sid+size+data
 
         record.validateSid((short)0x01AE);
     }
-    
+    /**
+     * tests that we can load the record
+     */
+    public void testLoadER() {
+
+        SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataER));      
+        assertTrue( record.isExternalReferences() );             //expected flag
+        assertEquals( 0x2, record.getNumberOfSheets() );    //expected # of sheets
+
+        assertEquals( 34, record.getRecordSize() );  //sid+size+data
+        
+        assertEquals("testURL", record.getURL().getString());
+        UnicodeString[] sheetNames = record.getSheetNames();
+        assertEquals(2, sheetNames.length);
+        assertEquals("Sheet1", sheetNames[0].getString());
+        assertEquals("Sheet2", sheetNames[1].getString());
+
+        record.validateSid((short)0x01AE);
+    }
     
+    /**
+     * tests that we can load the record
+     */
+    public void testLoadAIF() {
+
+        SupBookRecord record = new SupBookRecord(new TestcaseRecordInputStream((short)0x01AE, dataAIF));      
+        assertTrue( record.isAddInFunctions() );             //expected flag
+        assertEquals( 0x1, record.getNumberOfSheets() );    //expected # of sheets
+        assertEquals( 8, record.getRecordSize() );  //sid+size+data
+        record.validateSid((short)0x01AE);
+    }
+   
     /**
      * Tests that we can store the record
      *
      */
-    public void testStore()
-    {
-        SupBookRecord record = new SupBookRecord();
-        record.setFlag( (short) 0x401 );
-        record.setNumberOfSheets( (short)0x4 );
-        
-
+    public void testStoreIR() {
+        SupBookRecord record = SupBookRecord.createInternalReferences((short)4);
 
-        byte [] recordBytes = record.serialize();
-        assertEquals(recordBytes.length - 4, data.length);
-        for (int i = 0; i < data.length; i++)
-            assertEquals("At offset " + i, data[i], recordBytes[i+4]);
+        TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataIR, record.serialize());
+    }   
+    
+    public void testStoreER() {
+        UnicodeString url = new UnicodeString("testURL");
+        UnicodeString[] sheetNames = {
+                new UnicodeString("Sheet1"),
+                new UnicodeString("Sheet2"),
+        };
+        SupBookRecord record = SupBookRecord.createExternalReferences(url, sheetNames);
+
+        TestcaseRecordInputStream.confirmRecordEncoding(0x01AE, dataER, record.serialize());
     }    
     
     public static void main(String [] args) {
@@ -84,6 +125,4 @@ public class TestSupBookRecord
         .println("Testing org.apache.poi.hssf.record.SupBookRecord");
         junit.textui.TestRunner.run(TestSupBookRecord.class);
     }
-    
-
 }
index 767f507e73ebaf5b12a248913960f5743b49c666..ecb55ca82c1da95192744f0f55681489f09fb3b6 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;
 
 import java.io.ByteArrayInputStream;
+
+import junit.framework.Assert;
+
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -33,6 +33,14 @@ import org.apache.poi.util.LittleEndian;
 public class TestcaseRecordInputStream
         extends RecordInputStream
 {
+    /**
+     * Convenience constructor
+     */
+    public TestcaseRecordInputStream(int sid, byte[] data)
+    {
+      super(new ByteArrayInputStream(mergeDataAndSid((short)sid, (short)data.length, data)));
+      nextRecord();
+    }
     public TestcaseRecordInputStream(short sid, short length, byte[] data)
     {
       super(new ByteArrayInputStream(mergeDataAndSid(sid, length, data)));
@@ -46,4 +54,18 @@ public class TestcaseRecordInputStream
       System.arraycopy(data, 0, result, 4, data.length);
       return result;
     }
+    /**
+     * Confirms data sections are equal
+     * @param expectedData - just raw data (without sid or size short ints)
+     * @param actualRecordBytes this includes 4 prefix bytes (sid & size)
+     */
+    public static void confirmRecordEncoding(int expectedSid, byte[] expectedData, byte[] actualRecordBytes) {
+        int expectedDataSize = expectedData.length;
+        Assert.assertEquals(actualRecordBytes.length - 4, expectedDataSize);
+        Assert.assertEquals(expectedSid, LittleEndian.getShort(actualRecordBytes, 0));
+        Assert.assertEquals(expectedDataSize, LittleEndian.getShort(actualRecordBytes, 2));
+        for (int i = 0; i < expectedDataSize; i++)
+            Assert.assertEquals("At offset " + i, expectedData[i], actualRecordBytes[i+4]);
+        
+    }
 }
index 21e6a8ba3c3d2a12ad8610ac5b18ec4b25f36b6a..0912b97611b7c4e891e4e2385e4ccd6ab571ba84 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
@@ -28,6 +27,7 @@ import junit.framework.TestCase;
 
 import org.apache.poi.hssf.model.Workbook;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
 
 /**
  * Convenient abstract class to reduce the amount of boilerplate code needed
@@ -35,8 +35,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  *
  * @author Daniel Noll (daniel at nuix dot com dot au)
  */
-public abstract class AbstractPtgTestCase extends TestCase
-{
+public abstract class AbstractPtgTestCase extends TestCase {
     /** Directory containing the test data. */
     private static String dataDir = System.getProperty("HSSF.testdata.path");
 
@@ -51,16 +50,16 @@ public abstract class AbstractPtgTestCase extends TestCase
             throws IOException {
         File file = new File(dataDir, filename);
         InputStream stream = new BufferedInputStream(new FileInputStream(file));
-        try
-        {
-            return new HSSFWorkbook(stream);
-        }
-        finally
-        {
+        // TODO - temp workaround to keep stdout quiet due to warning msg in POIFS 
+        // When that warning msg is disabled, remove this wrapper and the close() call, 
+        InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(stream);
+        try {
+            return new HSSFWorkbook(wrappedStream);
+        } finally {
             stream.close();
         }
     }
-    
+
     /**
      * Creates a new Workbook and adds one sheet with the specified name
      */
@@ -73,5 +72,4 @@ public abstract class AbstractPtgTestCase extends TestCase
                book.setSheetName(0, sheetName); 
                return book;
        }
-    
 }
index b12681338777d3c1aa47f3d401495a38e546b8d9..92ca4ba04419583cbbe4c54bc2f6987f57c0306b 100644 (file)
@@ -14,7 +14,6 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
-        
 
 package org.apache.poi.hssf.record.formula;
 
@@ -33,7 +32,8 @@ public class AllFormulaTests {
                result.addTestSuite(TestArea3DPtg.class);
                result.addTestSuite(TestAreaErrPtg.class);
         result.addTestSuite(TestAreaPtg.class);
-               result.addTestSuite(TestErrPtg.class);
+        result.addTestSuite(TestErrPtg.class);
+        result.addTestSuite(TestExternalFunctionFormulas.class);
                result.addTestSuite(TestFuncPtg.class);
                result.addTestSuite(TestIntersectionPtg.class);
                result.addTestSuite(TestPercentPtg.class);
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java b/src/testcases/org/apache/poi/hssf/record/formula/TestExternalFunctionFormulas.java
new file mode 100755 (executable)
index 0000000..8c89dad
--- /dev/null
@@ -0,0 +1,56 @@
+/* ====================================================================
+   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;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+/**
+ * Tests for functions from external workbooks (e.g. YEARFRAC).
+ * 
+ * 
+ * @author Josh Micich
+ */
+public final class TestExternalFunctionFormulas extends TestCase {
+
+    
+    /**
+     * tests <tt>NameXPtg.toFormulaString(Workbook)</tt> and logic in Workbook below that   
+     */
+    public void testReadFormulaContainingExternalFunction() {
+        String filePath = System.getProperty("HSSF.testdata.path")+ "/" 
+        + "externalFunctionExample.xls";
+        HSSFWorkbook wb;
+        try {
+            FileInputStream fin = new FileInputStream(filePath);
+            wb = new HSSFWorkbook( fin );
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        
+        String expectedFormula = "YEARFRAC(B1,C1)";
+        HSSFSheet sht = wb.getSheetAt(0);
+        String cellFormula = sht.getRow(0).getCell((short)0).getCellFormula();
+        assertEquals(expectedFormula, cellFormula);
+    }
+    
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java
new file mode 100755 (executable)
index 0000000..cbc555d
--- /dev/null
@@ -0,0 +1,71 @@
+/* ====================================================================
+   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 junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Collects all tests for the <tt>org.apache.poi.hssf.usermodel</tt> package.
+ * 
+ * @author Josh Micich
+ */
+public class AllUserModelTests {
+       
+       public static Test suite() {
+               TestSuite result = new TestSuite("Tests for org.apache.poi.hssf.usermodel");
+               
+               result.addTestSuite(TestBugs.class);
+               result.addTestSuite(TestCellStyle.class);
+               result.addTestSuite(TestCloneSheet.class);
+               result.addTestSuite(TestDataValidation.class);
+               result.addTestSuite(TestEscherGraphics.class);
+               result.addTestSuite(TestEscherGraphics2d.class);
+               result.addTestSuite(TestFontDetails.class);
+               result.addTestSuite(TestFormulas.class);
+               result.addTestSuite(TestHSSFCell.class);
+               result.addTestSuite(TestHSSFClientAnchor.class);
+               result.addTestSuite(TestHSSFComment.class);
+               result.addTestSuite(TestHSSFDateUtil.class);
+               result.addTestSuite(TestHSSFHeaderFooter.class);
+               result.addTestSuite(TestHSSFHyperlink.class);
+               result.addTestSuite(TestHSSFPalette.class);
+               result.addTestSuite(TestHSSFPicture.class);
+               result.addTestSuite(TestHSSFPictureData.class);
+               result.addTestSuite(TestHSSFRichTextString.class);
+               result.addTestSuite(TestHSSFRow.class);
+               result.addTestSuite(TestHSSFSheet.class);
+               result.addTestSuite(TestHSSFSheetOrder.class);
+               result.addTestSuite(TestHSSFSheetSetOrder.class);
+               result.addTestSuite(TestHSSFWorkbook.class);
+               result.addTestSuite(TestNamedRange.class);
+               result.addTestSuite(TestOLE2Embeding.class);
+               result.addTestSuite(TestReadWriteChart.class);
+               result.addTestSuite(TestSanityChecker.class);
+               result.addTestSuite(TestSheetHiding.class);
+               result.addTestSuite(TestSheetShiftRows.class);
+               if (false) { // deliberately avoiding this one
+            result.addTestSuite(TestUnfixedBugs.class);
+        }
+        result.addTestSuite(TestUnicodeWorkbook.class);
+               result.addTestSuite(TestUppercaseWorkbook.class);
+               result.addTestSuite(TestWorkbook.class);                
+               
+               return result;
+       }
+}
index 25d6684c4714c8f36c96e56a2df35185348f1420..fd01001f84928a5eaf150750d8c1bb14820107fd 100644 (file)
 */\r
 package org.apache.poi.hssf.usermodel;\r
 \r
-import junit.framework.TestCase;\r
-\r
-import java.io.IOException;\r
-import java.io.FileInputStream;\r
 import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+\r
+import junit.framework.TestCase;\r
 \r
 /**\r
  * Test <code>HSSFPicture</code>.\r
  *\r
  * @author Yegor Kozlov (yegor at apache.org)\r
  */\r
-public class TestHSSFPicture extends TestCase{\r
+public final class TestHSSFPicture extends TestCase{\r
 \r
-    public void testResize() throws Exception {\r
+    public void testResize() {\r
         HSSFWorkbook wb = new HSSFWorkbook();\r
         HSSFSheet sh1 = wb.createSheet();\r
         HSSFPatriarch p1 = sh1.createDrawingPatriarch();\r
 \r
-        int idx1 = loadPicture( "src/resources/logos/logoKarmokar4.png", wb);\r
+        byte[] pictureData = getTestDataFileContent("logoKarmokar4.png");\r
+        int idx1 = wb.addPicture( pictureData, HSSFWorkbook.PICTURE_TYPE_PNG );\r
         HSSFPicture picture1 = p1.createPicture(new HSSFClientAnchor(), idx1);\r
         HSSFClientAnchor anchor1 = picture1.getPreferredSize();\r
 \r
@@ -52,28 +55,25 @@ public class TestHSSFPicture extends TestCase{
     /**\r
      * Copied from org.apache.poi.hssf.usermodel.examples.OfficeDrawing\r
      */\r
-    private static int loadPicture( String path, HSSFWorkbook wb ) throws IOException\r
-    {\r
-        int pictureIndex;\r
-        FileInputStream fis = null;\r
-        ByteArrayOutputStream bos = null;\r
-        try\r
-        {\r
-            fis = new FileInputStream( path);\r
-            bos = new ByteArrayOutputStream( );\r
-            int c;\r
-            while ( (c = fis.read()) != -1)\r
-                bos.write( c );\r
-            pictureIndex = wb.addPicture( bos.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG );\r
-        }\r
-        finally\r
-        {\r
-            if (fis != null)\r
-                fis.close();\r
-            if (bos != null)\r
-                bos.close();\r
-        }\r
-        return pictureIndex;\r
-    }\r
+     private static byte[] getTestDataFileContent(String fileName) {\r
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+\r
+        String readFilename = System.getProperty("HSSF.testdata.path");\r
+        try {\r
+            InputStream fis = new FileInputStream(readFilename+File.separator+fileName);\r
 \r
+            byte[] buf = new byte[512];\r
+            while(true) {\r
+                int bytesRead = fis.read(buf);\r
+                if(bytesRead < 1) {\r
+                    break;\r
+                }\r
+                bos.write(buf, 0, bytesRead);\r
+            }\r
+            fis.close();\r
+        } catch (IOException e) {\r
+            throw new RuntimeException(e);\r
+        }\r
+        return bos.toByteArray();\r
+     }\r
 }\r