]> source.dussan.org Git - poi.git/commitdiff
Big formula update from Josh from bug #44364 - support for Match, NA and SumProduct...
authorNick Burch <nick@apache.org>
Thu, 14 Feb 2008 16:01:10 +0000 (16:01 +0000)
committerNick Burch <nick@apache.org>
Thu, 14 Feb 2008 16:01:10 +0000 (16:01 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@627788 13f79535-47bb-0310-9956-ffa450edef68

31 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/FormulaParser.java
src/java/org/apache/poi/hssf/record/formula/FuncPtg.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/BoolEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/ErrorEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/UnaryPlusEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Match.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/MultiOperandNumericFunction.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Na.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rounddown.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Roundup.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Sumproduct.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/T.java
src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java [new file with mode: 0644]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.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/NumericFunctionInvoker.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java [deleted file]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java [new file with mode: 0755]
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java [new file with mode: 0755]
src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls
src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls
src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java

index 34345df5d0304aeb3ae106131b4f8f7966164365..26de01f977a0c7dab30836fb2fa817dfc394a60f 100644 (file)
@@ -36,6 +36,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
            <action dev="POI-DEVELOPERS" type="fix">44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored.</action>
            <action dev="POI-DEVELOPERS" type="fix">38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour)</action>
            <action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>
index 91641b13ba2928bb376194c0eaa32736dcceaacd..2407b58fef9c11a25229bf3f1d74ec211f8e5b22 100644 (file)
@@ -33,6 +33,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.1-beta1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
            <action dev="POI-DEVELOPERS" type="fix">44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored.</action>
            <action dev="POI-DEVELOPERS" type="fix">38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour)</action>
            <action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>
index 832dbdef7a63b0b95dd4a6dc876a84e256b82b53..1d78b1b239bcfac2223e8821335d207a4d771cb0 100644 (file)
@@ -947,20 +947,28 @@ end;
 
            // Excel allows to have AttrPtg at position 0 (such as Blanks) which
            // do not have any operands. Skip them.
-        stack.push(ptgs[0].toFormulaString(book));
+        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;
+        } else {
+               i=0;
+        }
                   
-        for (int i = 1; i < ptgs.length; i++) {
-            if (! (ptgs[i] instanceof OperationPtg)) {
-                stack.push(ptgs[i].toFormulaString(book));
+        for ( ; i < ptgs.length; i++) {
+            Ptg ptg = ptgs[i];
+            if (! (ptg instanceof OperationPtg)) {
+                stack.push(ptg.toFormulaString(book));
                 continue;
             }
                       
-            if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
-                ifptg = (AttrPtg) ptgs[i];
+            if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
+                ifptg = (AttrPtg) ptg;
                 continue;
             }
                       
-            final OperationPtg o = (OperationPtg) ptgs[i];
+            final OperationPtg o = (OperationPtg) ptg;
             final String[] operands = new String[o.getNumberOfOperands()];
 
             for (int j = operands.length; j > 0; j--) {
index 410364971c6c12106a07d5bee6ec8444b8e5a977..1123fc803af6eae2f14394b5a2bfc44bce2c7731 100644 (file)
@@ -62,6 +62,10 @@ public class FuncPtg extends AbstractFunctionPtg{
             numParams=0;
         }   
         
+    }
+    public FuncPtg(int functionIndex, int numberOfParameters) {
+        field_2_fnc_index = (short) functionIndex;
+        numParams = numberOfParameters;
     }
     
      public void writeBytes(byte[] array, int offset) {
index 6a04068b76d203a9718b310478a396ed8cd502df..7b625aaa98b8970f930554d616b64d2167cb3d53 100644 (file)
@@ -34,6 +34,16 @@ public class BoolEval implements NumericValueEval, StringValueEval {
     public static final BoolEval FALSE = new BoolEval(false);
     
     public static final BoolEval TRUE = new BoolEval(true);
+    
+    /**
+     * Convenience method for the following:<br/>
+     * <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
+     * @return a <tt>BoolEval</tt> instance representing <tt>b</tt>.
+     */
+    public static final BoolEval valueOf(boolean b) {
+        // TODO - find / replace all occurrences
+        return b ? TRUE : FALSE;
+    }
 
     public BoolEval(Ptg ptg) {
         this.value = ((BoolPtg) ptg).getValue();
@@ -48,10 +58,17 @@ public class BoolEval implements NumericValueEval, StringValueEval {
     }
 
     public double getNumberValue() {
-        return value ? (short) 1 : (short) 0;
+        return value ? 1 : 0;
     }
 
     public String getStringValue() {
         return value ? "TRUE" : "FALSE";
     }
+    public String toString() {
+        StringBuffer sb = new StringBuffer(64);
+        sb.append(getClass().getName()).append(" [");
+        sb.append(getStringValue());
+        sb.append("]");
+        return sb.toString();
+    }
 }
index 43ef6c5127c975516b91633f4bb491c65ecea4f5..f57976d3ed674872b62c44a67c94862ed1321353 100644 (file)
@@ -24,31 +24,91 @@ package org.apache.poi.hssf.record.formula.eval;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  * 
  */
-public class ErrorEval implements ValueEval {
-
-    private int errorCode;
-
-
-    public static final ErrorEval NAME_INVALID = new ErrorEval(525);
+public final class ErrorEval implements ValueEval {
+    /**
+     * Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6)
+     */
+    private static final class ErrorCode {
+        /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
+        public static final int NULL = 0x00;
+        /** <b>#DIV/0!</b> - Division by zero */
+        public static final int DIV_0 = 0x07;
+        /** <b>#VALUE!</b> - Wrong type of operand */
+        public static final int VALUE = 0x0F; 
+        /** <b>#REF!</b> - Illegal or deleted cell reference */
+        public static final int REF = 0x17;  
+        /** <b>#NAME?</b> - Wrong function or range name */
+        public static final int NAME = 0x1D; 
+        /** <b>#NUM!</b> - Value range overflow */
+        public static final int NUM = 0x24; 
+        /** <b>#N/A</b> - Argument or function not available */
+        public static final int N_A = 0x2A;   
+        
+        public static final String getText(int errorCode) {
+            switch(errorCode) {
+                case NULL:  return "#NULL!";
+                case DIV_0: return "#DIV/0!";
+                case VALUE: return "#VALUE!";
+                case REF:   return "#REF!";
+                case NAME:  return "#NAME?";
+                case NUM:   return "#NUM!";
+                case N_A:   return "#N/A";
+            }
+            return "???";
+        }
+    }
 
-    public static final ErrorEval VALUE_INVALID = new ErrorEval(519);
+    /** <b>#NULL!</b>  - Intersection of two cell ranges is empty */
+    public static final ErrorEval NULL_INTERSECTION = new ErrorEval(ErrorCode.NULL); 
+    /** <b>#DIV/0!</b> - Division by zero */
+    public static final ErrorEval DIV_ZERO = new ErrorEval(ErrorCode.DIV_0);
+    /** <b>#VALUE!</b> - Wrong type of operand */
+    public static final ErrorEval VALUE_INVALID = new ErrorEval(ErrorCode.VALUE);
+    /** <b>#REF!</b> - Illegal or deleted cell reference */
+    public static final ErrorEval REF_INVALID = new ErrorEval(ErrorCode.REF);
+    /** <b>#NAME?</b> - Wrong function or range name */
+    public static final ErrorEval NAME_INVALID = new ErrorEval(ErrorCode.NAME); 
+    /** <b>#NUM!</b> - Value range overflow */
+    public static final ErrorEval NUM_ERROR = new ErrorEval(ErrorCode.NUM);
+    /** <b>#N/A</b> - Argument or function not available */
+    public static final ErrorEval NA = new ErrorEval(ErrorCode.N_A);
 
     
-    // Non std error codes
+    /**
+     * Translates an Excel internal error code into the corresponding POI ErrorEval instance 
+     * @param errorCode
+     */
+    public static ErrorEval valueOf(int errorCode) {
+        switch(errorCode) {
+            case ErrorCode.NULL: return NULL_INTERSECTION;
+            case ErrorCode.DIV_0: return DIV_ZERO;
+            case ErrorCode.VALUE: return VALUE_INVALID;
+//            case ErrorCode.REF: return REF_INVALID;
+            case ErrorCode.REF: return UNKNOWN_ERROR;
+            case ErrorCode.NAME: return NAME_INVALID;
+            case ErrorCode.NUM: return NUM_ERROR;
+            case ErrorCode.N_A: return NA;
+            
+            // these cases probably shouldn't be coming through here 
+            // but (as of Jan-2008) a lot of code depends on it. 
+//            case -20: return UNKNOWN_ERROR;
+//            case -30: return FUNCTION_NOT_IMPLEMENTED;
+//            case -60: return CIRCULAR_REF_ERROR;
+        }
+        throw new RuntimeException("Unexpected error code (" + errorCode + ")");
+    }
+    
+    // POI internal error codes
     public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
-
     public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
+    // Note - Excel does not seem to represent this condition with an error code
+    public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60); 
 
-    public static final ErrorEval REF_INVALID = new ErrorEval(-40);
-
-    public static final ErrorEval NA = new ErrorEval(-50);
-    
-    public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60);
-    
-    public static final ErrorEval DIV_ZERO = new ErrorEval(-70);
-    
-    public static final ErrorEval NUM_ERROR = new ErrorEval(-80);
 
+    private int errorCode;
+    /**
+     * @param errorCode an 8-bit value
+     */
     private ErrorEval(int errorCode) {
         this.errorCode = errorCode;
     }
@@ -56,9 +116,11 @@ public class ErrorEval implements ValueEval {
     public int getErrorCode() {
         return errorCode;
     }
-
-    public String getStringValue() {
-        return "Err:" + Integer.toString(errorCode);
+    public String toString() {
+        StringBuffer sb = new StringBuffer(64);
+        sb.append(getClass().getName()).append(" [");
+        sb.append(ErrorCode.getText(errorCode));
+        sb.append("]");
+        return sb.toString();
     }
-
 }
index 847aa56fa6cbda27afa8bd25d44a4e5b0f03e653..7816b228f7814d6fb4109227056aca0d5a3f720b 100644 (file)
@@ -58,6 +58,9 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
 //                ));
 
 
+    /**
+     * called by reflection
+     */
     public UnaryPlusEval(Ptg ptg) {
         this.delegate = (UnaryPlusPtg) ptg;
     }
@@ -108,7 +111,7 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
                 }
                 else if (ae.isColumn()) {
                     if (ae.containsRow(srcRow)) {
-                        ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
+                        ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn());
                         if (ve instanceof RefEval) {
                             ve = ((RefEval) ve).getInnerValueEval();
                         }
index 74afaf695c3d3ce011173bebdb772afc70782ea1..b2b252c4f0cc1de95618ec2aa8b24f425e8c9a16 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;
 
-public class Match extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.AreaEval;
+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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.util.AreaReference;
+
+/**
+ * Implementation for the MATCH() Excel function.<p/>
+ * 
+ * <b>Syntax:</b><br/>
+ * <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/>
+ * 
+ * Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified 
+ * <b>lookup_value</b> is found.<p/>
+ * 
+ * Specific matching behaviour can be modified with the optional <b>match_type</b> parameter.
+ * 
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description">
+ *      <tr><th>Value</th><th>Matching Behaviour</th></tr>
+ *      <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value.
+ *        The lookup_array must be in ascending <i>order</i>*.</td></tr>
+ *      <tr><td>0</td><td>find the first value that is exactly equal to lookup_value.
+ *        The lookup_array can be in any order.</td></tr>
+ *      <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value.
+ *        The lookup_array must be in descending <i>order</i>*.</td></tr>
+ *    </table>
+ * 
+ * * Note regarding <i>order</i> - For the <b>match_type</b> cases that require the lookup_array to
+ *  be ordered, MATCH() can produce incorrect results if this requirement is not met.  Observed
+ *  behaviour in Excel is to return the lowest index value for which every item after that index
+ *  breaks the match rule.<br>
+ *  The (ascending) sort order expected by MATCH() is:<br/>
+ *  numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/>
+ *  MATCH() ignores all elements in the lookup_array with a different type to the lookup_value. 
+ *  Type conversion of the lookup_array elements is never performed.
+ *  
+ *  
+ * @author Josh Micich
+ */
+public final class Match implements Function {
+       
+       private static final class EvalEx extends Exception {
+               private final ErrorEval _error;
+
+               public EvalEx(ErrorEval error) {
+                       _error = error;
+               }
+               public ErrorEval getError() {
+                       return _error;
+               }
+       }
+       
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               
+               double match_type = 1; // default
+               
+               switch(args.length) {
+                       case 3:
+                               try {
+                                       match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
+                               } catch (EvalEx e) {
+                                       // Excel/MATCH() seems to have slightly abnormal handling of errors with
+                                       // the last parameter.  Errors do not propagate up.  Every error gets
+                                       // translated into #REF!
+                                       return ErrorEval.REF_INVALID;
+                               }
+                       case 2:
+                               break;
+                       default:
+                               return ErrorEval.VALUE_INVALID;
+               }
+               
+               boolean matchExact = match_type == 0;
+               // Note - Excel does not strictly require -1 and +1
+               boolean findLargestLessThanOrEqual = match_type > 0;
+               
+               
+               try {
+                       ValueEval lookupValue = evaluateLookupValue(args[0], srcCellRow, srcCellCol);
+                       ValueEval[] lookupRange = evaluateLookupRange(args[1]);
+                       int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
+                       return new NumberEval(index + 1); // +1 to convert to 1-based
+               } catch (EvalEx e) {
+                       return e.getError();
+               }
+       }
+
+       private static ValueEval chooseSingleElementFromArea(AreaEval ae, 
+                       int srcCellRow, short srcCellCol) throws EvalEx {
+               if (ae.isColumn()) {
+                       if(ae.isRow()) {
+                               return ae.getValues()[0];
+                       }
+                       if(!ae.containsRow(srcCellRow)) {
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       return ae.getValueAt(srcCellRow, ae.getFirstColumn());
+               }
+               if(!ae.isRow()) {
+                       throw new EvalEx(ErrorEval.VALUE_INVALID);
+               }
+               if(!ae.containsColumn(srcCellCol)) {
+                       throw new EvalEx(ErrorEval.VALUE_INVALID);
+               }
+               return ae.getValueAt(ae.getFirstRow(), srcCellCol);
+               
+       }
+
+       private static ValueEval evaluateLookupValue(Eval eval, int srcCellRow, short srcCellCol)
+                       throws EvalEx {
+               if (eval instanceof RefEval) {
+                       RefEval re = (RefEval) eval;
+                       return re.getInnerValueEval();
+               }
+               if (eval instanceof AreaEval) {
+                       return chooseSingleElementFromArea((AreaEval) eval, srcCellRow, srcCellCol);
+               }
+               if (eval instanceof ValueEval) {
+                       return (ValueEval) eval;
+               }
+               throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+       }
+
+
+       private static ValueEval[] evaluateLookupRange(Eval eval) throws EvalEx {
+               if (eval instanceof RefEval) {
+                       RefEval re = (RefEval) eval;
+                       return new ValueEval[] { re.getInnerValueEval(), };
+               }
+               if (eval instanceof AreaEval) {
+                       AreaEval ae = (AreaEval) eval;
+                       if(!ae.isColumn() && !ae.isRow()) {
+                               throw new EvalEx(ErrorEval.NA);
+                       }
+                       return ae.getValues();
+               }
+               
+               // Error handling for lookup_range arg is also unusual
+               if(eval instanceof NumericValueEval) {
+                       throw new EvalEx(ErrorEval.NA);
+               }
+               if (eval instanceof StringEval) {
+                       StringEval se = (StringEval) eval;
+                       Double d = parseDouble(se.getStringValue());
+                       if(d == null) {
+                               // plain string
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       // else looks like a number
+                       throw new EvalEx(ErrorEval.NA);
+               }
+               throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
+       }
+
+
+       private static Double parseDouble(String stringValue) {
+               // TODO find better home for parseDouble
+               return Countif.parseDouble(stringValue);
+       }
+
+
+
+       private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol) 
+                       throws EvalEx {
+               Eval match_type = arg;
+               if(arg instanceof AreaReference) {
+                       AreaEval ae = (AreaEval) arg;
+                       // an area ref can work as a scalar value if it is 1x1
+                       if(ae.isColumn() &&  ae.isRow()) {
+                               match_type = ae.getValues()[0];
+                       } else {
+                               match_type = chooseSingleElementFromArea(ae, srcCellRow, srcCellCol);
+                       }
+               }
+               
+               if(match_type instanceof RefEval) {
+                       RefEval re = (RefEval) match_type;
+                       match_type = re.getInnerValueEval();
+               }
+               if(match_type instanceof ErrorEval) {
+                       throw new EvalEx((ErrorEval)match_type);
+               }
+               if(match_type instanceof NumericValueEval) {
+                       NumericValueEval ne = (NumericValueEval) match_type;
+                       return ne.getNumberValue();
+               }
+               if (match_type instanceof StringEval) {
+                       StringEval se = (StringEval) match_type;
+                       Double d = parseDouble(se.getStringValue());
+                       if(d == null) {
+                               // plain string
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       // if the string parses as a number, it is ok
+                       return d.doubleValue();
+               }
+               throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
+       }
+       
+       /**
+        * @return zero based index
+        */
+       private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
+                       boolean matchExact, boolean findLargestLessThanOrEqual) throws EvalEx {
+               // TODO - wildcard matching when matchExact and lookupValue is text containing * or ?
+               if(matchExact) {
+                       for (int i = 0; i < lookupRange.length; i++) {
+                               ValueEval lri = lookupRange[i];
+                               if(lri.getClass() != lookupValue.getClass()) {
+                                       continue;
+                               }
+                               if(compareValues(lookupValue, lri) == 0) {
+                                       return i;
+                               }
+                       }
+               } else {
+                       // Note - backward iteration
+                       if(findLargestLessThanOrEqual) {
+                               for (int i = lookupRange.length - 1; i>=0;  i--) {
+                                       ValueEval lri = lookupRange[i];
+                                       if(lri.getClass() != lookupValue.getClass()) {
+                                               continue;
+                                       }
+                                       int cmp = compareValues(lookupValue, lri);
+                                       if(cmp == 0) {
+                                               return i;
+                                       }
+                                       if(cmp > 0) {
+                                               return i;
+                                       }
+                               }
+                       } else {
+                               // find smallest greater than or equal to
+                               for (int i = 0; i<lookupRange.length; i++) {
+                                       ValueEval lri = lookupRange[i];
+                                       if(lri.getClass() != lookupValue.getClass()) {
+                                               continue;
+                                       }
+                                       int cmp = compareValues(lookupValue, lri);
+                                       if(cmp == 0) {
+                                               return i;
+                                       }
+                                       if(cmp > 0) {
+                                               if(i<1) {
+                                                       throw new EvalEx(ErrorEval.NA);
+                                               }
+                                               return i-1;
+                                       }
+                               }
+                               
+                       }
+               }
+
+               throw new EvalEx(ErrorEval.NA);
+       }
+
 
+       /**
+        * This method can only compare a pair of <tt>NumericValueEval</tt>s, <tt>StringEval</tt>s
+        * or <tt>BoolEval</tt>s
+        * @return negative for a&lt;b, positive for a&gt;b and 0 for a = b
+        */
+       private static int compareValues(ValueEval a, ValueEval b) {
+               if (a instanceof StringEval) {
+                       StringEval sa = (StringEval) a;
+                       StringEval sb = (StringEval) b;
+                       return sa.getStringValue().compareToIgnoreCase(sb.getStringValue());
+               }
+               if (a instanceof NumericValueEval) {
+                       NumericValueEval na = (NumericValueEval) a;
+                       NumericValueEval nb = (NumericValueEval) b;
+                       return Double.compare(na.getNumberValue(), nb.getNumberValue());
+               }
+               if (a instanceof BoolEval) {
+                       boolean ba = ((BoolEval) a).getBooleanValue();
+                       boolean bb = ((BoolEval) b).getBooleanValue();
+                       if(ba == bb) {
+                               return 0;
+                       }
+                       // TRUE > FALSE
+                       if(ba) {
+                               return +1;
+                       }
+                       return -1;
+               }
+               throw new RuntimeException("bad eval type (" + a.getClass().getName() + ")");
+       }
 }
index 2840c898836b83e3b2cf964c15362ac320370b56..f7dc60fea751e1e338707199729c4a835e6cb2fc 100644 (file)
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
-/*
- * Created on May 22, 2005
- *
- */
+
 package org.apache.poi.hssf.record.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.AreaEval;
@@ -36,6 +33,49 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
  * where the order of operands does not matter
  */
 public abstract class MultiOperandNumericFunction extends NumericFunction {
+    static final double[] EMPTY_DOUBLE_ARRAY = { };
+    
+    private static class DoubleList {
+        private double[] _array;
+        private int _count;
+
+        public DoubleList() {
+            _array = new double[8];
+            _count = 0;
+        }
+        
+        public double[] toArray() {
+            if(_count < 1) {
+                return EMPTY_DOUBLE_ARRAY;
+            }
+            double[] result = new double[_count];
+            System.arraycopy(_array, 0, result, 0, _count);
+            return result;
+        }
+
+        public void add(double[] values) {
+            int addLen = values.length;
+            ensureCapacity(_count + addLen);
+            System.arraycopy(values, 0, _array, _count, addLen);
+            _count += addLen;
+        }
+
+        private void ensureCapacity(int reqSize) {
+            if(reqSize > _array.length) {
+                int newSize = reqSize * 3 / 2; // grow with 50% extra
+                double[] newArr = new double[newSize];
+                System.arraycopy(_array, 0, newArr, 0, _count);
+                _array = newArr;
+            }
+        }
+
+        public void add(double value) {
+            ensureCapacity(_count + 1);
+            _array[_count] = value;
+            _count++;
+        }
+    }
+    
 
     private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
         new ValueEvalToNumericXlator((short) (
@@ -76,40 +116,26 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
      * 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.
+     * excel cause this function to return <code>null</code>.
      * 
      * @param operands
      * @param srcRow
      * @param srcCol
      */
     protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) {
-        double[] retval = new double[30];
-        int count = 0;
+        if (operands.length > getMaxNumOperands()) {
+            return null;
+        }
+        DoubleList retval = new DoubleList();
         
-        outer: do { // goto simulator loop
-            if (operands.length > getMaxNumOperands()) {
-                break outer;
+        for (int i=0, iSize=operands.length; i<iSize; i++) {
+            double[] temp = getNumberArray(operands[i], srcRow, srcCol);
+            if (temp == null) {
+                return null; // error occurred.
             }
-            else {
-                for (int i=0, iSize=operands.length; i<iSize; i++) {
-                    double[] temp = getNumberArray(operands[i], srcRow, srcCol);
-                    if (temp == null) {
-                        retval = null; // error occurred.
-                        break;
-                    }
-                    retval = putInArray(retval, count, temp);
-                    count += temp.length;
-                }
-            }
-        } while (false); // end goto simulator loop
-        
-        if (retval != null) {
-            double[] temp = retval;
-            retval = new double[count];
-            System.arraycopy(temp, 0, retval, 0, count);
+            retval.add(temp);
         }
-        
-        return retval;
+        return retval.toArray();
     }
     
     /**
@@ -120,13 +146,11 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
      * @param srcCol
      */
     protected double[] getNumberArray(Eval operand, int srcRow, short srcCol) {
-        double[] retval;
-        int count = 0;
         
         if (operand instanceof AreaEval) {
             AreaEval ae = (AreaEval) operand;
             ValueEval[] values = ae.getValues();
-            retval = new double[values.length];
+            DoubleList retval = new DoubleList();
             for (int j=0, jSize=values.length; j<jSize; j++) {
                 /*
                  * TODO: For an AreaEval, we are constructing a RefEval
@@ -143,91 +167,61 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
                 
                 if (ve instanceof NumericValueEval) {
                     NumericValueEval nve = (NumericValueEval) ve;
-                    retval = putInArray(retval, count++, nve.getNumberValue());
+                    retval.add(nve.getNumberValue());
                 }
-                else if (ve instanceof BlankEval) {} // ignore operand
+                else if (ve instanceof BlankEval) {
+                    // note - blanks are ignored, so returned array will be smaller.
+                } 
                 else {
-                    retval = null; // null => indicate to calling subclass that error occurred
-                    break;
+                    return null; // indicate to calling subclass that error occurred
                 }
             }
+            return retval.toArray();
         }
-        else { // for ValueEvals other than AreaEval
-            retval = new double[1];
-            ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
-            
-            if (ve instanceof NumericValueEval) {
-                NumericValueEval nve = (NumericValueEval) ve;
-                retval = putInArray(retval, count++, nve.getNumberValue());
-            }
-            else if (ve instanceof BlankEval) {} // ignore operand
-            else {
-                retval = null; // null => indicate to calling subclass that error occurred
-            }
-        }
         
-        if (retval != null && retval.length >= 1) {
-            double[] temp = retval;
-            retval = new double[count];
-            System.arraycopy(temp, 0, retval, 0, count);
+        // for ValueEvals other than AreaEval
+        ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
+        
+        if (ve instanceof NumericValueEval) {
+            NumericValueEval nve = (NumericValueEval) ve;
+            return new double[] { nve.getNumberValue(), };
         }
         
-        return retval;
+        if (ve instanceof BlankEval) {
+            // ignore blanks
+            return EMPTY_DOUBLE_ARRAY;
+        } 
+        return null;
     }
     
     /**
-     * puts d at position pos in array arr. If pos is greater than arr, the 
-     * array is dynamically resized (using a simple doubling rule).
-     * @param arr
-     * @param pos
-     * @param d
+     * Ensures that a two dimensional array has all sub-arrays present and the same length
+     * @return <code>false</code> if any sub-array is missing, or is of different length
      */
-    private static double[] putInArray(double[] arr, int pos, double d) {
-        double[] tarr = arr;
-        while (pos >= arr.length) {
-            arr = new double[arr.length << 1];
-        }
-        if (tarr.length != arr.length) {
-            System.arraycopy(tarr, 0, arr, 0, tarr.length);
-        }
-        arr[pos] = d;
-        return arr;
-    }
-    
-    private static double[] putInArray(double[] arr, int pos, double[] d) {
-        double[] tarr = arr;
-        while (pos+d.length >= arr.length) {
-            arr = new double[arr.length << 1];
+    protected static final boolean areSubArraysConsistent(double[][] values) {
+        
+        if (values == null || values.length < 1) {
+            // TODO this doesn't seem right.  Fix or add comment.
+            return true;
         }
-        if (tarr.length != arr.length) {
-            System.arraycopy(tarr, 0, arr, 0, tarr.length);
+        
+        if (values[0] == null) {
+            return false;
         }
-        for (int i=0, iSize=d.length; i<iSize; i++) {
-            arr[pos+i] = d[i];
+        int outerMax = values.length;
+        int innerMax = values[0].length;
+        for (int i=1; i<outerMax; i++) { // note - 'i=1' start at second sub-array
+            double[] subArr = values[i];
+            if (subArr == null) {
+                return false;
+            }
+            if (innerMax != subArr.length) {
+                return false;
+            }
         }
-        return arr;
+        return true;
     }
     
-    protected static boolean areSubArraysConsistent(double[][] values) {
-        boolean retval = false;
-        
-        outer: do {
-            if (values != null && values.length > 0) {
-                if (values[0] == null)
-                    break outer;
-                int len = values[0].length;
-                for (int i=1, iSize=values.length; i<iSize; i++) {
-                    if (values[i] == null)
-                        break outer;
-                    int tlen = values[i].length;
-                    if (len != tlen) {
-                        break outer;
-                    }
-                }
-            }
-            retval = true;
-        } while (false);
-        return retval;
-    }
+   
     
 }
index 2ab1b8c4e82d24639c2711955b5302eb73daf0ed..eef0f451300b70d8513d5ee1fd0d196cefb63a3c 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;
 
-public class Na extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.Eval;
+
+/**
+ * Implementation of Excel function NA()
+ * 
+ * @author Josh Micich
+ */
+public final class Na implements Function {
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               return ErrorEval.NA;
+       }
 
 }
index ab446c9f2d4d4a4d2470e6ee077fd589e44a81a2..13522294fd4dceb665b086c0005ceb3d4f64fbd3 100644 (file)
@@ -40,6 +40,9 @@ public class Rounddown extends NumericFunction {
             break;
         case 2:
             ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+            if(ve instanceof ErrorEval) {
+               return ve;
+            }
             if (ve instanceof NumericValueEval) {
                 NumericValueEval ne = (NumericValueEval) ve;
                 d0 = ne.getNumberValue();
index 3d8cc1ae344d39011ffa51bc09d6949727356862..4dae76d981008a4be63fff017689db9fbf4670c0 100644 (file)
@@ -40,6 +40,9 @@ public class Roundup extends NumericFunction {
             break;
         case 2:
             ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
+            if(ve instanceof ErrorEval) {
+               return ve;
+            }
             if (ve instanceof NumericValueEval) {
                 NumericValueEval ne = (NumericValueEval) ve;
                 d0 = ne.getNumberValue();
index 12fa5d7bd5f302743f3176a3505ba5c2af9c1eb3..9f6eafa4dcf958c85e2c31c923ace1d4cfc23e25 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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
 
 /**
- * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt; 
- *
+ * Implementation for the Excel function SUMPRODUCT<p/>
+ * 
+ * Syntax : <br/>
+ *  SUMPRODUCT ( array1[, array2[, array3[, ...]]])
+ *    <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
+ *      <tr><th>array1, ... arrayN&nbsp;&nbsp;</th><td>typically area references, 
+ *      possibly cell references or scalar values</td></tr>
+ *    </table><br/>
+ *    
+ * Let A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub> represent the element in the <b>i</b>th row <b>j</b>th column 
+ * of the <b>n</b>th array<br/>   
+ * Assuming each array has the same dimensions (W, H), the result is defined as:<br/>    
+ * SUMPRODUCT = &Sigma;<sub><b>i</b>: 1..H</sub> &nbsp;
+ *     (&nbsp; &Sigma;<sub><b>j</b>: 1..W</sub> &nbsp;
+ *       (&nbsp; &Pi;<sub><b>n</b>: 1..N</sub> 
+ *                     A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub>&nbsp;
+ *    )&nbsp;
+ *  ) 
+ * 
+ * @author Josh Micich
  */
-public class Sumproduct extends NotImplementedFunction {
+public final class Sumproduct implements Function {
+
+       private static final class EvalEx extends Exception {
+               private final ErrorEval _error;
+
+               public EvalEx(ErrorEval error) {
+                       _error = error;
+               }
+               public ErrorEval getError() {
+                       return _error;
+               }
+       }
+
+       public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+               
+               int maxN = args.length;
+               
+               if(maxN < 1) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               Eval firstArg = args[0];
+               try {
+                       if(firstArg instanceof NumericValueEval) {
+                               return evaluateSingleProduct(args);
+                       }
+                       if(firstArg instanceof RefEval) {
+                               return evaluateSingleProduct(args);
+                       }
+                       if(firstArg instanceof AreaEval) {
+                               AreaEval ae = (AreaEval) firstArg;
+                               if(ae.isRow() && ae.isColumn()) {
+                                       return evaluateSingleProduct(args);
+                               }
+                               return evaluateAreaSumProduct(args);
+                       }
+               } catch (EvalEx e) {
+                       return e.getError();
+               }
+               throw new RuntimeException("Invalid arg type for SUMPRODUCT: (" 
+                               + firstArg.getClass().getName() + ")");
+       }
+
+       private Eval evaluateSingleProduct(Eval[] evalArgs) throws EvalEx {
+               int maxN = evalArgs.length;
+
+               double term = 1D;
+               for(int n=0; n<maxN; n++) {
+                       double val = getScalarValue(evalArgs[n]);
+                       term *= val;
+               }
+               return new NumberEval(term);
+       }
+
+       private double getScalarValue(Eval arg) throws EvalEx {
+               
+               Eval eval;
+               if (arg instanceof RefEval) {
+                       RefEval re = (RefEval) arg;
+                       eval = re.getInnerValueEval();
+               } else {
+                       eval = arg;
+               }
+               
+               if (eval == null) {
+                       throw new RuntimeException("parameter may not be null");
+               }
+               if (eval instanceof AreaEval) {
+                       AreaEval ae = (AreaEval) eval;
+                       // an area ref can work as a scalar value if it is 1x1
+                       if(!ae.isColumn() || !ae.isRow()) {
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       eval = ae.getValues()[0];
+               }
+
+               if (!(eval instanceof ValueEval)) {
+                       throw new RuntimeException("Unexpected value eval class (" 
+                                       + eval.getClass().getName() + ")");
+               }
+               
+               return getProductTerm((ValueEval) eval, true);
+       }
+
+       private Eval evaluateAreaSumProduct(Eval[] evalArgs) throws EvalEx {
+               int maxN = evalArgs.length;
+               AreaEval[] args = new AreaEval[maxN];
+               try {
+                       System.arraycopy(evalArgs, 0, args, 0, maxN);
+               } catch (ArrayStoreException e) {
+                       // one of the other args was not an AreaRef
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               
+               AreaEval firstArg = args[0];
+               
+               int height = firstArg.getLastRow() - firstArg.getFirstRow() + 1;
+               int width = firstArg.getLastColumn() - firstArg.getFirstColumn() + 1; // TODO - junit
+               
+               
+
+               double[][][] elements = new double[maxN][][];
+               
+               for (int n = 0; n < maxN; n++) {
+                       elements[n] = evaluateArea(args[n], height, width);
+               }
+               double acc = 0;
+               
+               for(int r=0; r<height; r++) {
+                       for(int c=0; c<width; c++) {
+                               double term = 1D;
+                               for(int n=0; n<maxN; n++) {
+                                       term *= elements[n][r][c];
+                               }
+                               acc += term;
+                       }
+               }
+               
+               return new NumberEval(acc);
+       }
+
+       /**
+        * @return a 2-D array of the specified height and width corresponding to the evaluated cell 
+        *  values of the specified areaEval 
+        * @throws EvalEx if any ErrorEval value was encountered while evaluating the area
+        */
+       private static double[][] evaluateArea(AreaEval areaEval, int height, int width) throws EvalEx {
+               int fr =areaEval.getFirstRow();
+               int fc =areaEval.getFirstColumn();
+               
+               // check that height and width match
+               if(areaEval.getLastRow() - fr + 1 != height) {
+                       throw new EvalEx(ErrorEval.VALUE_INVALID);
+               }
+               if(areaEval.getLastColumn() - fc + 1 != width) {
+                       throw new EvalEx(ErrorEval.VALUE_INVALID);
+               }
+               ValueEval[] values = areaEval.getValues();
+               double[][] result = new double[height][width];
+               for(int r=0; r<height; r++) {
+                       for(int c=0; c<width; c++) {
+                               ValueEval ve = values[r*width + c];
+                               result[r][c] = getProductTerm(ve, false);
+                       }
+               }
+               return result;
+       }
+
+       /**
+        * Determines a <code>double</code> value for the specified <code>ValueEval</code>. 
+        * @param isScalarProduct <code>false</code> for SUMPRODUCTs over area refs.
+        * @throws EvalEx if <code>ve</code> represents an error value.
+        * <p/>
+        * Note - string values and empty cells are interpreted differently depending on 
+        * <code>isScalarProduct</code>.  For scalar products, if any term is blank or a string, the
+        * error (#VALUE!) is raised.  For area (sum)products, if any term is blank or a string, the
+        * result is zero.
+        */
+       private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvalEx {
+
+               if(ve instanceof BlankEval || ve == null) {
+                       // TODO - shouldn't BlankEval.INSTANCE be used always instead of null?
+                       // null seems to occur when the blank cell is part of an area ref (but not reliably)
+                       if(isScalarProduct) {
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       return 0;
+               }
+               
+               if(ve instanceof ErrorEval) {
+                       throw new EvalEx((ErrorEval)ve);
+               }
+               if(ve instanceof StringEval) {
+                       if(isScalarProduct) {
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       // Note for area SUMPRODUCTs, string values are interpreted as zero
+                       // even if they would parse as valid numeric values
+                       return 0;
+               }
+               if(ve instanceof NumericValueEval) {
+                       NumericValueEval nve = (NumericValueEval) ve;
+                       return nve.getNumberValue();
+               }
+               throw new RuntimeException("Unexpected value eval class (" 
+                               + ve.getClass().getName() + ")");
+       }
 }
index 686c40b6278b3cadb94a862005e8ce542327f7be..b322cd985c32da0c59d3cc76e190e8a6bae55411 100644 (file)
@@ -22,28 +22,34 @@ 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.RefEval;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
 
-public class T implements Function {
-    
-    
+public final class T implements Function {
 
-    public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
-        ValueEval retval = null;
-        switch (operands.length) {
-        default:
-            retval = ErrorEval.VALUE_INVALID;
-            break;
-        case 1:
-            if (operands[0] instanceof StringEval
-             || operands[0] instanceof ErrorEval) {
-                retval = (ValueEval) operands[0];
-            }
-            else if (operands[0] instanceof ErrorEval) {
-                retval = StringEval.EMPTY_INSTANCE;
-            }
+    public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
+        switch (args.length) {
+            default:
+                return ErrorEval.VALUE_INVALID;
+            case 1:
+                 break;
         }
-        return retval;
+        Eval arg = args[0];
+        if (arg instanceof RefEval) {
+            RefEval re = (RefEval) arg;
+            arg = re.getInnerValueEval();
+        }
+        
+        if (arg instanceof StringEval) {
+            // Text values are returned unmodified
+            return arg;
+        }
+        
+        if (arg instanceof ErrorEval) {
+            // Error values also returned unmodified
+            return arg;
+        }
+        // for all other argument types the result is empty string
+        return StringEval.EMPTY_INSTANCE;
     }
 }
index f60a6adaaca3fcf8dce6b438a7c1f024ae5853fe..70b78dfae4a216a31b0304d5f004eb03f4ae9d25 100644 (file)
@@ -173,7 +173,7 @@ public class HSSFFormulaEvaluator {
      *  formula evaluated. 
      */
     public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) {
-       return new FormulaParser(formula, workbook.getWorkbook());
+        return new FormulaParser(formula, workbook.getWorkbook());
     }
     
     /**
@@ -286,19 +286,19 @@ public class HSSFFormulaEvaluator {
                 CellValue cv = getCellValueForEval(internalEvaluate(cell, row, sheet, workbook));
                 switch (cv.getCellType()) {
                 case HSSFCell.CELL_TYPE_BOOLEAN:
-                       cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN);
+                    cell.setCellType(HSSFCell.CELL_TYPE_BOOLEAN);
                     cell.setCellValue(cv.getBooleanValue());
                     break;
                 case HSSFCell.CELL_TYPE_ERROR:
-                       cell.setCellType(HSSFCell.CELL_TYPE_ERROR);
+                    cell.setCellType(HSSFCell.CELL_TYPE_ERROR);
                     cell.setCellValue(cv.getErrorValue());
                     break;
                 case HSSFCell.CELL_TYPE_NUMERIC:
-                       cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
+                    cell.setCellType(HSSFCell.CELL_TYPE_NUMERIC);
                     cell.setCellValue(cv.getNumberValue());
                     break;
                 case HSSFCell.CELL_TYPE_STRING:
-                       cell.setCellType(HSSFCell.CELL_TYPE_STRING);
+                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                     cell.setCellValue(cv.getRichTextStringValue());
                     break;
                 case HSSFCell.CELL_TYPE_BLANK:
@@ -337,6 +337,11 @@ public class HSSFFormulaEvaluator {
             else if (eval instanceof BlankEval) {
                 retval = new CellValue(HSSFCell.CELL_TYPE_BLANK);
             }
+            else if (eval instanceof ErrorEval) {
+                retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
+                retval.setErrorValue((byte)((ErrorEval)eval).getErrorCode());
+//                retval.setRichTextStringValue(new HSSFRichTextString("#An error occurred. check cell.getErrorCode()"));
+            }
             else {
                 retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
             }
@@ -401,7 +406,7 @@ public class HSSFFormulaEvaluator {
                 short rownum = ptg.getRow();
                 HSSFRow row = sheet.getRow(rownum);
                 HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
-                pushRef2DEval(ptg, stack, cell, row, sheet, workbook);
+                stack.push(createRef2DEval(ptg, cell, row, sheet, workbook));
             }
             else if (ptgs[i] instanceof Ref3DPtg) {
                 Ref3DPtg ptg = (Ref3DPtg) ptgs[i];
@@ -411,7 +416,7 @@ public class HSSFFormulaEvaluator {
                 HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex()));
                 HSSFRow row = xsheet.getRow(rownum);
                 HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
-                pushRef3DEval(ptg, stack, cell, row, xsheet, workbook);
+                stack.push(createRef3DEval(ptg, cell, row, xsheet, workbook));
             }
             else if (ptgs[i] instanceof AreaPtg) {
                 AreaPtg ap = (AreaPtg) ptgs[i];
@@ -544,104 +549,77 @@ public class HSSFFormulaEvaluator {
      * @param workbook
      */
     protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
-        ValueEval retval = BlankEval.INSTANCE;
-        if (cell != null) {
-            switch (cell.getCellType()) {
+
+        if (cell == null) {
+            return BlankEval.INSTANCE;
+        }
+        switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                retval = new NumberEval(cell.getNumericCellValue());
-                break;
+                return new NumberEval(cell.getNumericCellValue());
             case HSSFCell.CELL_TYPE_STRING:
-                retval = new StringEval(cell.getRichStringCellValue().getString());
-                break;
+                return new StringEval(cell.getRichStringCellValue().getString());
             case HSSFCell.CELL_TYPE_FORMULA:
-                retval = internalEvaluate(cell, row, sheet, workbook);
-                break;
+                return internalEvaluate(cell, row, sheet, workbook);
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                retval = cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE;
-                break;
+                return BoolEval.valueOf(cell.getBooleanCellValue());
             case HSSFCell.CELL_TYPE_BLANK:
-                retval = BlankEval.INSTANCE;
-                break;
+                return BlankEval.INSTANCE;
             case HSSFCell.CELL_TYPE_ERROR:
-                retval = ErrorEval.UNKNOWN_ERROR; // TODO: think about this...
-                break;
-            }
+                return ErrorEval.valueOf(cell.getErrorCellValue());
         }
-        return retval;
+        throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
     }
 
     /**
-     * create a Ref2DEval for ReferencePtg and push it on the stack.
+     * Creates a Ref2DEval for ReferencePtg.
      * Non existent cells are treated as RefEvals containing BlankEval.
-     * @param ptg
-     * @param stack
-     * @param cell
-     * @param sheet
-     * @param workbook
      */
-    protected static void pushRef2DEval(ReferencePtg ptg, Stack stack, 
-            HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
-        if (cell != null)
-            switch (cell.getCellType()) {
+    private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell, 
+            HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
+        if (cell == null) {
+            return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
+        }
+        
+        switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                stack.push(new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
-                break;
+                return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
             case HSSFCell.CELL_TYPE_STRING:
-                stack.push(new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
-                break;
+                return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
             case HSSFCell.CELL_TYPE_FORMULA:
-                stack.push(new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
-                break;
+                return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                stack.push(new Ref2DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
-                break;
+                return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
             case HSSFCell.CELL_TYPE_BLANK:
-                stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
-                break;
+                return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
             case HSSFCell.CELL_TYPE_ERROR:
-                stack.push(new Ref2DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
-                break;
-            }
-        else {
-            stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
+                return new  Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
         }
+        throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
     }
 
     /**
-     * create a Ref3DEval for Ref3DPtg and push it on the stack.
-     * 
-     * @param ptg
-     * @param stack
-     * @param cell
-     * @param sheet
-     * @param workbook
+     * create a Ref3DEval for Ref3DPtg.
      */
-    protected static void pushRef3DEval(Ref3DPtg ptg, Stack stack, HSSFCell cell, 
+    private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell, 
             HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
-        if (cell != null)
-            switch (cell.getCellType()) {
+        if (cell == null) {
+            return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
+        }
+        switch (cell.getCellType()) {
             case HSSFCell.CELL_TYPE_NUMERIC:
-                stack.push(new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
-                break;
+                return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
             case HSSFCell.CELL_TYPE_STRING:
-                stack.push(new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
-                break;
+                return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
             case HSSFCell.CELL_TYPE_FORMULA:
-                stack.push(new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
-                break;
+                return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
             case HSSFCell.CELL_TYPE_BOOLEAN:
-                stack.push(new Ref3DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
-                break;
+                return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
             case HSSFCell.CELL_TYPE_BLANK:
-                stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
-                break;
+                return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
             case HSSFCell.CELL_TYPE_ERROR:
-                stack.push(new Ref3DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
-                break;
-            }
-        else {
-            stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
+                return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
         }
+        throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
     }
     
     /**
@@ -726,15 +704,15 @@ public class HSSFFormulaEvaluator {
         /**
          * @return Returns the richTextStringValue.
          */
-               public HSSFRichTextString getRichTextStringValue() {
-                       return richTextStringValue;
-               }
+        public HSSFRichTextString getRichTextStringValue() {
+            return richTextStringValue;
+        }
         /**
          * @param richTextStringValue The richTextStringValue to set.
          */
-               public void setRichTextStringValue(HSSFRichTextString richTextStringValue) {
-                       this.richTextStringValue = richTextStringValue;
-               }
+        public void setRichTextStringValue(HSSFRichTextString richTextStringValue) {
+            this.richTextStringValue = richTextStringValue;
+        }
     }
 
     /**
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls
deleted file mode 100644 (file)
index 54a7edb..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-bad.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls b/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls
deleted file mode 100644 (file)
index 5ae84bc..0000000
Binary files a/src/scratchpad/testcases/org/apache/poi/hssf/data/42464-ExpPtg-ok.xls and /dev/null differ
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/GenericFormulaTestCase.java
deleted file mode 100644 (file)
index b53e8eb..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
-* 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.
-*/
-/*
- * Created on May 11, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.eval;
-
-import java.io.FileInputStream;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
-
-import org.apache.poi.hssf.record.formula.functions.TestMathX;
-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.util.CellReference;
-
-/**
- * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
- *
- */
-public class GenericFormulaTestCase extends TestCase {
-
-    protected final static String FILENAME = System.getProperty("HSSF.testdata.path")+ "/FormulaEvalTestData.xls";
-
-    protected static HSSFWorkbook workbook = null;
-
-    protected CellReference beginCell;
-    protected int getBeginRow() {
-        return beginCell.getRow();
-    }
-    
-    protected short getBeginCol() {
-        return beginCell.getCol();
-    }
-
-    protected final HSSFCell getExpectedValueCell(HSSFSheet sheet, HSSFRow row, HSSFCell cell) {
-        HSSFCell retval = null;
-        if (sheet != null) {
-            row = sheet.getRow(row.getRowNum()+1);
-            if (row != null) {
-                retval = row.getCell(cell.getCellNum());
-            }
-        }
-        
-        return retval;
-    }
-
-    protected void assertEquals(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
-        if (expected != null && actual!=null) {
-            if (expected!=null && expected.getCellType() == HSSFCell.CELL_TYPE_STRING) {
-                String value = expected.getRichStringCellValue().getString();
-                if (value.startsWith("#")) {
-                    expected.setCellType(HSSFCell.CELL_TYPE_ERROR);
-                }
-            }
-            if (!(expected == null || actual == null)) {
-                switch (expected.getCellType()) {
-                case HSSFCell.CELL_TYPE_BLANK:
-                    assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType());
-                    break;
-                case HSSFCell.CELL_TYPE_BOOLEAN:
-                    assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType());
-                    assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
-                    break;
-                case HSSFCell.CELL_TYPE_ERROR:
-                    assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); // TODO: check if exact error matches
-                    break;
-                case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
-                    throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
-                case HSSFCell.CELL_TYPE_NUMERIC:
-                    assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType());
-                    TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR);
-//                    double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue());
-//                    double pctExpected = Math.abs(0.00001*expected.getNumericCellValue());
-//                    assertTrue(msg, delta <= pctExpected);
-                    break;
-                case HSSFCell.CELL_TYPE_STRING:
-                    assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType());
-                    assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
-                    break;
-                }
-            }
-            else {
-                throw new AssertionFailedError("expected: " + expected + " got:" + actual);
-            }
-        }
-    }
-
-    public GenericFormulaTestCase(String beginCell) throws Exception {
-        super("genericTest");
-        if (workbook == null) {
-          FileInputStream fin = new FileInputStream( FILENAME );
-          workbook = new HSSFWorkbook( fin );
-          fin.close();        
-        }
-        this.beginCell = new CellReference(beginCell);
-    }
-    
-    public void setUp() {
-    }
-    
-    public void genericTest() throws Exception {
-        HSSFSheet s = workbook.getSheetAt( 0 );
-        HSSFRow r = s.getRow(getBeginRow());
-        short endcolnum = r.getLastCellNum();
-        HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(s, workbook);
-        evaluator.setCurrentRow(r);
-
-        HSSFCell c = null;
-        for (short colnum=getBeginCol(); colnum < endcolnum; colnum++) {
-            try {
-            c = r.getCell(colnum);
-            if (c==null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA)
-                continue;
-            
-            HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
-            
-            HSSFCell expectedValueCell = getExpectedValueCell(s, r, c);
-            assertEquals("Formula: " + c.getCellFormula() 
-                    + " @ " + getBeginRow() + ":" + colnum, 
-                    expectedValueCell, actualValue);
-            } catch (RuntimeException re) {
-                throw new RuntimeException("CELL["+getBeginRow()+","+colnum+"]: "+re.getMessage(), re);
-            }
-        }
-    }
-    
-}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestEverything.java
deleted file mode 100644 (file)
index ac3ca2e..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
-* 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.
-*/
-/*
- * Created on May 11, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.eval;
-
-import junit.framework.TestSuite;
-
-/**
- * This is a test of all the Eval functions we have implemented.
- * Add newly implemented Eval functions in here to have them
- *  tested.
- * For newly implemented functions, 
- *  @see org.apache.poi.hssf.record.formula.functions.TestEverything
- *
- * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
- */
-public class TestEverything extends TestSuite {
-
-    public static TestSuite suite() throws Exception {
-        TestSuite suite = new TestSuite("Tests for OperationEval concrete implementation classes.");
-        suite.addTest(new GenericFormulaTestCase("D23")); // Add
-        suite.addTest(new GenericFormulaTestCase("D27")); // ConcatEval
-        suite.addTest(new GenericFormulaTestCase("D31")); // DivideEval
-        suite.addTest(new GenericFormulaTestCase("D35")); // EqualEval
-        suite.addTest(new GenericFormulaTestCase("D39")); // GreaterEqualEval
-        suite.addTest(new GenericFormulaTestCase("D43")); // GreaterThanEval
-        suite.addTest(new GenericFormulaTestCase("D47")); // LessEqualEval
-        suite.addTest(new GenericFormulaTestCase("D51")); // LessThanEval
-        suite.addTest(new GenericFormulaTestCase("D55")); // MultiplyEval
-        suite.addTest(new GenericFormulaTestCase("D59")); // NotEqualEval
-        suite.addTest(new GenericFormulaTestCase("D63")); // PowerEval
-        suite.addTest(new GenericFormulaTestCase("D67")); // SubtractEval
-        suite.addTest(new GenericFormulaTestCase("D71")); // UnaryMinusEval
-        suite.addTest(new GenericFormulaTestCase("D75")); // UnaryPlusEval
-        
-               // Add newly implemented Eval functions here
-               // (Formula functions go in 
-               //  @see org.apache.poi.hssf.record.formula.functions.TestEverything )
-        
-        return suite;
-    }
-}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestFormulasFromSpreadsheet.java
new file mode 100644 (file)
index 0000000..f57221c
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+* 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 java.io.FileInputStream;
+import java.io.PrintStream;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.functions.TestMathX;
+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;
+
+/**
+ * Tests formulas and operators as loaded from a test data spreadsheet.<p/>
+ * This class does not test implementors of <tt>Function</tt> and <tt>OperationEval</tt> in
+ * isolation.  Much of the evaluation engine (i.e. <tt>HSSFFormulaEvaluator</tt>, ...) gets
+ * exercised as well.  Tests for bug fixes and specific/tricky behaviour can be found in the
+ * corresponding test class (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) implementor, 
+ * where execution can be observed more easily.
+ * 
+ * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
+ */
+public final class TestFormulasFromSpreadsheet extends TestCase {
+       
+       private static final class Result {
+               public static final int SOME_EVALUATIONS_FAILED = -1;
+               public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
+               public static final int NO_EVALUATIONS_FOUND = 0;
+       }
+
+       /** 
+        * This class defines constants for navigating around the test data spreadsheet used for these tests.
+        */
+       private static final class SS {
+               
+               /**
+                * Name of the test spreadsheet (found in the standard test data folder)
+                */
+               public final static String FILENAME = "FormulaEvalTestData.xls";
+           /**
+            * Row (zero-based) in the test spreadsheet where the operator examples start.
+            */
+               public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23'
+           /**
+            * Row (zero-based) in the test spreadsheet where the function examples start.
+            */
+               public static final int START_FUNCTIONS_ROW_INDEX = 83; // Row '84' 
+               /** 
+                * Index of the column that contains the function names
+                */
+           public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B'
+       
+           /**
+            * Used to indicate when there are no more functions left
+            */
+               public static final String FUNCTION_NAMES_END_SENTINEL = "<END-OF-FUNCTIONS>";
+       
+               /**
+                * Index of the column where the test values start (for each function)
+                */
+           public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D'
+           
+           /**
+            * Each function takes 4 rows in the test spreadsheet 
+            */
+               public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4;
+       }
+
+    private HSSFWorkbook workbook;
+       private HSSFSheet sheet;
+       // Note - multiple failures are aggregated before ending.  
+       // If one or more functions fail, a single AssertionFailedError is thrown at the end
+       private int _functionFailureCount;
+       private int _functionSuccessCount;
+       private int _evaluationFailureCount;
+       private int _evaluationSuccessCount;
+
+    private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) {
+       if (row == null) {
+               return null;
+       }
+       return row.getCell(columnIndex);
+    }
+
+
+    private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
+        if (expected == null) {
+                       throw new AssertionFailedError(msg + " - Bad setup data expected value is null");
+               }
+               if(actual == null) {
+                       throw new AssertionFailedError(msg + " - actual value was null");
+               }
+        
+               if (expected.getCellType() == HSSFCell.CELL_TYPE_STRING) {
+                   String value = expected.getRichStringCellValue().getString();
+                   if (value.startsWith("#")) {
+                       // TODO - this code never called
+                       expected.setCellType(HSSFCell.CELL_TYPE_ERROR);
+                       // expected.setCellErrorValue(...?);
+                   }
+               }
+               
+               switch (expected.getCellType()) {
+                       case HSSFCell.CELL_TYPE_BLANK:
+                           assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType());
+                           break;
+                       case HSSFCell.CELL_TYPE_BOOLEAN:
+                           assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType());
+                           assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
+                           break;
+                       case HSSFCell.CELL_TYPE_ERROR:
+                           assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType());
+                           if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values
+                               assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue());
+                           }
+                           break;
+                       case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
+                           throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
+                       case HSSFCell.CELL_TYPE_NUMERIC:
+                           assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType());
+                           TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR);
+//                 double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue());
+//                 double pctExpected = Math.abs(0.00001*expected.getNumericCellValue());
+//                 assertTrue(msg, delta <= pctExpected);
+                           break;
+                       case HSSFCell.CELL_TYPE_STRING:
+                           assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType());
+                           assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
+                           break;
+               }
+    }
+
+
+       protected void setUp() throws Exception {
+        if (workbook == null) {
+               String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME;
+            FileInputStream fin = new FileInputStream( filePath );
+            workbook = new HSSFWorkbook( fin );
+            sheet = workbook.getSheetAt( 0 );
+          }
+        _functionFailureCount = 0;
+        _functionSuccessCount = 0;
+        _evaluationFailureCount = 0;
+        _evaluationSuccessCount = 0;
+    }
+    
+    public void testFunctionsFromTestSpreadsheet() {
+        
+        processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null);
+        processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null);
+        // example for debugging individual functions/operators:
+//        processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval");
+//        processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE");
+        
+        // confirm results
+       String successMsg = "There were " 
+                       + _evaluationSuccessCount + " successful evaluation(s) and "
+                               + _functionSuccessCount + " function(s) without error";
+               if(_functionFailureCount > 0) {
+                       String msg = _functionFailureCount + " function(s) failed in "
+                       + _evaluationFailureCount + " evaluation(s).  " + successMsg;
+               throw new AssertionFailedError(msg);
+        }
+               if(false) { // normally no output for successful tests
+                       System.out.println(getClass().getName() + ": " + successMsg);
+               }
+       }
+
+    /**
+     * @param startRowIndex row index in the spreadsheet where the first function/operator is found 
+     * @param testFocusFunctionName name of a single function/operator to test alone. 
+     * Typically pass <code>null</code> to test all functions
+     */
+       private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
+               HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook);
+
+        int rowIndex = startRowIndex;
+        while (true) {
+            HSSFRow r = sheet.getRow(rowIndex);
+            String targetFunctionName = getTargetFunctionName(r);
+               if(targetFunctionName == null) {
+                       throw new AssertionFailedError("Test spreadsheet cell empty on row (" 
+                                       + (rowIndex+1) + "). Expected function name or '"
+                                       + SS.FUNCTION_NAMES_END_SENTINEL + "'");
+               }
+               if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) {
+                       // found end of functions list
+                       break;
+               }
+               if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) {
+                       
+                       // expected results are on the row below
+                   HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1);
+                   if(expectedValuesRow == null) {
+                       int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row
+                       throw new AssertionFailedError("Missing expected values row for function '" 
+                                       + targetFunctionName + " (row " + missingRowNum + ")"); 
+                   }
+                   switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) {
+                       case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break;
+                       case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break;
+                       default:
+                               throw new RuntimeException("unexpected result");
+                       case Result.NO_EVALUATIONS_FOUND: // do nothing
+                   }
+               }
+            rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION;
+        }
+       }
+
+       /**
+        * 
+        * @return a constant from the local Result class denoting whether there were any evaluation
+        * cases, and whether they all succeeded.
+        */
+    private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName, 
+               HSSFRow formulasRow, HSSFRow expectedValuesRow) {
+       
+        int result = Result.NO_EVALUATIONS_FOUND; // so far
+        short endcolnum = formulasRow.getLastCellNum();
+        evaluator.setCurrentRow(formulasRow);
+
+        // iterate across the row for all the evaluation cases
+        for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) {
+            HSSFCell c = formulasRow.getCell(colnum);
+                       if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
+                               continue;
+                       }
+
+                       HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
+
+                       HSSFCell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum);
+                       try {
+                               confirmExpectedResult("Function '" + targetFunctionName + "': Formula: " + c.getCellFormula() + " @ " + formulasRow.getRowNum() + ":" + colnum,
+                                               expectedValueCell, actualValue);
+                               _evaluationSuccessCount ++;
+                               if(result != Result.SOME_EVALUATIONS_FAILED) {
+                                       result = Result.ALL_EVALUATIONS_SUCCEEDED;
+                               }
+                       } catch (AssertionFailedError e) {
+                               _evaluationFailureCount ++;
+                               printShortStackTrace(System.err, e);
+                               result = Result.SOME_EVALUATIONS_FAILED;
+                       }
+        }
+               return result;
+       }
+
+    /**
+     * Useful to keep output concise when expecting many failures to be reported by this test case
+     */
+       private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) {
+               StackTraceElement[] stes = e.getStackTrace();
+               
+               int startIx = 0;
+               // skip any top frames inside junit.framework.Assert
+               while(startIx<stes.length) {
+                       if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
+                               break;
+                       }
+                       startIx++;
+               }
+               // skip bottom frames (part of junit framework)
+               int endIx = startIx+1;
+               while(endIx < stes.length) {
+                       if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
+                               break;
+                       }
+                       endIx++;
+               }
+               if(startIx >= endIx) {
+                       // something went wrong. just print the whole stack trace
+                       e.printStackTrace(ps);
+               }
+               endIx -= 4; // skip 4 frames of reflection invocation
+               ps.println(e.toString());
+               for(int i=startIx; i<endIx; i++) {
+                       ps.println("\tat " + stes[i].toString());
+               }
+               
+       }
+
+       /**
+     * @return <code>null</code> if cell is missing, empty or blank
+     */
+       private static String getTargetFunctionName(HSSFRow r) {
+               if(r == null) {
+                       System.err.println("Warning - given null row, can't figure out function name");
+                       return null;
+               }
+               HSSFCell cell = r.getCell(SS.COLUMN_INDEX_FUNCTION_NAME);
+               if(cell == null) {
+                       System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_FUNCTION_NAME + ", can't figure out function name");
+                       return null;
+               }
+               if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
+                       return null;
+               }
+               if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
+                       return cell.getRichStringCellValue().getString();
+               }
+               
+               throw new AssertionFailedError("Bad cell type for 'function name' column: ("
+                               + cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")");
+       }
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java
new file mode 100755 (executable)
index 0000000..724c54c
--- /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 org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
+import org.apache.poi.hssf.record.formula.functions.NumericFunctionInvoker;
+
+import junit.framework.TestCase;
+
+/**
+ * Test for unary plus operator evaluator.
+ * 
+ * @author Josh Micich
+ */
+public final class TestUnaryPlusEval extends TestCase {
+       
+       /**
+        * Test for bug observable at svn revision 618865 (5-Feb-2008)<br/>
+        * The code for handling column operands had been copy-pasted from the row handling code. 
+        */
+       public void testColumnOperand() {
+               
+               short firstRow = (short)8;
+               short lastRow = (short)12;
+               short colNum = (short)5;
+               AreaPtg areaPtg = new AreaPtg(firstRow, lastRow, colNum, colNum, false, false, false, false);
+               ValueEval[] values = {
+                               new NumberEval(27),     
+                               new NumberEval(29),     
+                               new NumberEval(35),     // value in row 10 
+                               new NumberEval(37),     
+                               new NumberEval(38),     
+               };
+               Eval areaEval = new Area2DEval(areaPtg, values);
+               Eval[] args = { 
+                       areaEval,       
+               };
+               
+               double result = NumericFunctionInvoker.invoke(new UnaryPlusEval(new UnaryPlusPtg()), args, 10, (short)20);
+               
+               assertEquals(35, result, 0);
+       }
+
+}
index b5e08436714339246c6fe5db5dba0ba93ea41e42..c504aaa0a672588ee1e7be8f20554028af42f975 100755 (executable)
@@ -36,8 +36,11 @@ public final class AllIndividualFunctionEvaluationTests {
                result.addTestSuite(TestFinanceLib.class);
                result.addTestSuite(TestIndex.class);
                result.addTestSuite(TestMathX.class);
+               result.addTestSuite(TestMatch.class);
                result.addTestSuite(TestRowCol.class);
+               result.addTestSuite(TestSumproduct.class);
                result.addTestSuite(TestStatsLib.class);
+               result.addTestSuite(TestTFunc.class);
                return result;
        }
 
index 87405a49180eaa477ebbc5b89d09a4d2281dbe82..2093d35b1ed8b08b9d44d54a126b4f85a58cf7e1 100755 (executable)
@@ -23,13 +23,14 @@ import junit.framework.AssertionFailedError;
 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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.OperationEval;
 
 /**
  * Test helper class for invoking functions with numeric results.
  * 
  * @author Josh Micich
  */
-final class NumericFunctionInvoker {
+public final class NumericFunctionInvoker {
 
        private NumericFunctionInvoker() {
                // no instances of this class
@@ -59,13 +60,37 @@ final class NumericFunctionInvoker {
                                        + ") failed: " + e.getMessage());
                }
                
+       }
+       /**
+        * Invokes the specified operator with the arguments.
+        * <p/>
+        * This method cannot be used for confirming error return codes.  Any non-numeric evaluation
+        * result causes the current junit test to fail.
+        */
+       public static double invoke(OperationEval f, Eval[] args, int srcCellRow, int srcCellCol) {
+               try {
+                       return invokeInternal(f, args, srcCellRow, srcCellCol);
+               } catch (NumericEvalEx e) {
+                       throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName() 
+                                       + ") failed: " + e.getMessage());
+               }
+               
        }
        /**
         * Formats nicer error messages for the junit output
         */
-       private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
+       private static double invokeInternal(Object target, Eval[] args, int srcCellRow, int srcCellCol)
                                throws NumericEvalEx {
-               Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
+               Eval evalResult;
+               // TODO - make OperationEval extend Function
+               if (target instanceof Function) {
+                       Function ff = (Function) target;
+                       evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
+               } else {
+                       OperationEval ff = (OperationEval) target;
+                       evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
+               }
+               
                if(evalResult == null) {
                        throw new NumericEvalEx("Result object was null");
                }
@@ -89,6 +114,9 @@ final class NumericFunctionInvoker {
                if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
                        return "Unknown error";
                }
+               if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) {
+                       return "Error code: #VALUE! (invalid value)";
+               }
                return "Error code=" + ee.getErrorCode();
        }
        private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestEverything.java
deleted file mode 100644 (file)
index 8337810..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-* 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.
-*/
-/*
- * Created on May 11, 2005
- *
- */
-package org.apache.poi.hssf.record.formula.functions;
-
-import org.apache.poi.hssf.record.formula.eval.GenericFormulaTestCase;
-
-import junit.framework.TestSuite;
-
-/**
- * This is a test of all the normal formula functions we have implemented.
- * It should pick up newly implemented functions which are correctly added
- *  to the test formula excel file, but tweak the rows below if you
- *  add any past the end of what's currently checked.
- * For newly implemented eval functions, 
- *  @see org.apache.poi.hssf.record.formula.eval.TestEverything
- *
- * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
- */
-public class TestEverything extends TestSuite {
-    public static TestSuite suite() throws Exception {
-        TestSuite suite = new TestSuite("Tests for individual function classes");
-        String s;
-        for(int i=80; i<1485;i=i+4) {
-               s = "D"+Integer.toString(i).trim();
-               suite.addTest(new GenericFormulaTestCase(s));
-        }
-//        suite.addTest(new GenericFormulaTestCase("D1164"));
-        return suite;
-    }
-}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestMatch.java
new file mode 100755 (executable)
index 0000000..7ad528d
--- /dev/null
@@ -0,0 +1,184 @@
+/* ====================================================================
+   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.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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+/**
+ * Test cases for MATCH()
+ * 
+ * @author Josh Micich
+ */
+public final class TestMatch extends TestCase {
+       /** less than or equal to */
+       private static final NumberEval MATCH_LARGEST_LTE = new NumberEval(1);
+       private static final NumberEval MATCH_EXACT = new NumberEval(0);
+       /** greater than or equal to */
+       private static final NumberEval MATCH_SMALLEST_GTE = new NumberEval(-1);
+       
+       
+       private static Eval invokeMatch(Eval lookup_value, Eval lookup_array, Eval match_type) {
+               Eval[] args = { lookup_value, lookup_array, match_type, };
+               return new Match().evaluate(args, -1, (short)-1);
+       }
+       private static void confirmInt(int expected, Eval actualEval) {
+               if(!(actualEval instanceof NumericValueEval)) {
+                       fail("Expected numeric result");
+               }
+               NumericValueEval nve = (NumericValueEval)actualEval;
+               assertEquals(expected, nve.getNumberValue(), 0);
+       }
+       
+       public void testSimpleNumber() {
+               
+               ValueEval[] values = {
+                       new NumberEval(4),      
+                       new NumberEval(5),      
+                       new NumberEval(10),     
+                       new NumberEval(10),     
+                       new NumberEval(25),     
+               };
+               
+               AreaPtg areaPtg = new AreaPtg("A1:A5");
+               Area2DEval ae = new Area2DEval(areaPtg, values);
+               
+               
+               confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE));
+               confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
+               confirmInt(4, invokeMatch(new NumberEval(10), ae, MATCH_LARGEST_LTE));
+               confirmInt(3, invokeMatch(new NumberEval(10), ae, MATCH_EXACT));
+               confirmInt(4, invokeMatch(new NumberEval(20), ae, MATCH_LARGEST_LTE));
+               assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT));
+       }
+       
+       public void testReversedNumber() {
+               
+               ValueEval[] values = {
+                       new NumberEval(25),     
+                       new NumberEval(10),     
+                       new NumberEval(10),     
+                       new NumberEval(10),     
+                       new NumberEval(4),      
+               };
+               
+               AreaPtg areaPtg = new AreaPtg("A1:A5");
+               Area2DEval ae = new Area2DEval(areaPtg, values);
+               
+               
+               confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_SMALLEST_GTE));
+               confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_EXACT));
+               confirmInt(4, invokeMatch(new NumberEval(9), ae, MATCH_SMALLEST_GTE));
+               confirmInt(1, invokeMatch(new NumberEval(20), ae, MATCH_SMALLEST_GTE));
+               assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT));
+               assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(26), ae, MATCH_SMALLEST_GTE));
+       }
+       
+       public void testSimpleString() {
+               
+               ValueEval[] values = {
+                       new StringEval("Albert"),       
+                       new StringEval("Charles"),      
+                       new StringEval("Ed"),   
+                       new StringEval("Greg"), 
+                       new StringEval("Ian"),  
+               };
+               
+               AreaPtg areaPtg = new AreaPtg("A1:A5");
+               Area2DEval ae = new Area2DEval(areaPtg, values);
+               
+               // Note String comparisons are case insensitive
+               confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_LARGEST_LTE));
+               confirmInt(3, invokeMatch(new StringEval("eD"), ae, MATCH_LARGEST_LTE));
+               confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_EXACT));
+               confirmInt(3, invokeMatch(new StringEval("ed"), ae, MATCH_EXACT));
+               confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
+               assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT));
+       }
+       
+       public void testSimpleBoolean() {
+               
+               ValueEval[] values = {
+                               BoolEval.FALSE, 
+                               BoolEval.FALSE, 
+                               BoolEval.TRUE,  
+                               BoolEval.TRUE,  
+               };
+               
+               AreaPtg areaPtg = new AreaPtg("A1:A4");
+               Area2DEval ae = new Area2DEval(areaPtg, values);
+               
+               // Note String comparisons are case insensitive
+               confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE));
+               confirmInt(1, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT));
+               confirmInt(4, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE));
+               confirmInt(3, invokeMatch(BoolEval.TRUE, ae, MATCH_EXACT));
+       }
+       
+       public void testHeterogeneous() {
+               
+               ValueEval[] values = {
+                               new NumberEval(4),      
+                               BoolEval.FALSE, 
+                               new NumberEval(5),
+                               new StringEval("Albert"),       
+                               BoolEval.FALSE, 
+                               BoolEval.TRUE,  
+                               new NumberEval(10),     
+                               new StringEval("Charles"),      
+                               new StringEval("Ed"),   
+                               new NumberEval(10),     
+                               new NumberEval(25),     
+                               BoolEval.TRUE,  
+                               new StringEval("Ed"),   
+               };
+               
+               AreaPtg areaPtg = new AreaPtg("A1:A13");
+               Area2DEval ae = new Area2DEval(areaPtg, values);
+               
+               assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Aaron"), ae, MATCH_LARGEST_LTE));
+               
+               confirmInt(5, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE));
+               confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT));
+               confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE));
+               confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
+               
+               confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT));
+               
+               confirmInt(4, invokeMatch(new StringEval("Ben"), ae, MATCH_LARGEST_LTE));
+               
+               confirmInt(13, invokeMatch(new StringEval("ED"), ae, MATCH_LARGEST_LTE));
+               confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT));
+                       
+               confirmInt(13, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
+               assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT));
+               
+               confirmInt(11, invokeMatch(new NumberEval(30), ae, MATCH_LARGEST_LTE));
+               confirmInt(12, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE));
+       }
+       
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestRoundFuncs.java
new file mode 100755 (executable)
index 0000000..a6ce345
--- /dev/null
@@ -0,0 +1,49 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.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.StringEval;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for ROUND(), ROUNDUP(), ROUNDDOWN()
+ * 
+ * @author Josh Micich
+ */
+public final class TestRoundFuncs extends TestCase {
+       public void testRounddownWithStringArg() {
+               
+               Eval strArg = new StringEval("abc");
+               Eval[] args = { strArg, new NumberEval(2), };
+               Eval result = new Rounddown().evaluate(args, -1, (short)-1);
+               assertEquals(ErrorEval.VALUE_INVALID, result);
+       }
+       
+       public void testRoundupWithStringArg() {
+               
+               Eval strArg = new StringEval("abc");
+               Eval[] args = { strArg, new NumberEval(2), };
+               Eval result = new Roundup().evaluate(args, -1, (short)-1);
+               assertEquals(ErrorEval.VALUE_INVALID, result);
+       }
+       
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestSumproduct.java
new file mode 100755 (executable)
index 0000000..9fbc9e4
--- /dev/null
@@ -0,0 +1,120 @@
+/* ====================================================================
+   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 org.apache.poi.hssf.record.formula.ReferencePtg;
+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.NumberEval;
+import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.RefEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for SUMPRODUCT()
+ * 
+ * @author Josh Micich
+ */
+public final class TestSumproduct extends TestCase {
+       
+       private static Eval invokeSumproduct(Eval[] args) {
+               // srcCellRow and srcCellColumn are ignored by SUMPRODUCT
+               return new Sumproduct().evaluate(args, -1, (short)-1);
+       }
+       private static void confirmDouble(double expected, Eval actualEval) {
+               if(!(actualEval instanceof NumericValueEval)) {
+                       fail("Expected numeric result");
+               }
+               NumericValueEval nve = (NumericValueEval)actualEval;
+               assertEquals(expected, nve.getNumberValue(), 0);
+       }
+
+       public void testScalarSimple() {
+               
+               RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3), true);
+               Eval[] args = {
+                       refEval, 
+                       new NumberEval(2),
+               };
+               Eval result = invokeSumproduct(args);
+               confirmDouble(6D, result);
+       }
+
+
+       public void testAreaSimple() {
+               
+               AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3);
+               AreaEval aeB = EvalFactory.createAreaEval("B1:B3", 1, 3);
+               ValueEval[] aValues = aeA.getValues();
+               ValueEval[] bValues = aeB.getValues();
+               aValues[0] = new NumberEval(2);
+               aValues[1] = new NumberEval(4);
+               aValues[2] = new NumberEval(5);
+               bValues[0] = new NumberEval(3);
+               bValues[1] = new NumberEval(6);
+               bValues[2] = new NumberEval(7);
+               
+               Eval[] args = { aeA, aeB, };
+               Eval result = invokeSumproduct(args);
+               confirmDouble(65D, result);
+       }
+
+       /**
+        * For scalar products, the terms may be 1x1 area refs
+        */
+       public void testOneByOneArea() {
+               
+               AreaEval ae = EvalFactory.createAreaEval("A1:A1", 1, 1);
+               ae.getValues()[0] = new NumberEval(7);
+               
+               Eval[] args = {
+                               ae, 
+                               new NumberEval(2),
+                       };
+               Eval result = invokeSumproduct(args);
+               confirmDouble(14D, result);
+       }
+
+       
+       public void testMismatchAreaDimensions() {
+               
+               AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3);
+               AreaEval aeB = EvalFactory.createAreaEval("B1:D1", 3, 1);
+               
+               Eval[] args;
+               args = new Eval[] { aeA, aeB, };
+               assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args));
+               
+               args = new Eval[] { aeA, new NumberEval(5), };
+               assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args));
+       }
+       
+       public void testAreaWithErrorCell() {
+               AreaEval aeA = EvalFactory.createAreaEval("A1:A2", 1, 2);
+               AreaEval aeB = EvalFactory.createAreaEval("B1:B2", 1, 2);
+               aeB.getValues()[1] = ErrorEval.REF_INVALID;
+               
+               Eval[] args = { aeA, aeB, };
+               assertEquals(ErrorEval.REF_INVALID, invokeSumproduct(args));
+       }
+       
+}
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestTFunc.java
new file mode 100755 (executable)
index 0000000..6ecc361
--- /dev/null
@@ -0,0 +1,118 @@
+/* ====================================================================
+   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 org.apache.poi.hssf.record.formula.ReferencePtg;
+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.Ref2DEval;
+import org.apache.poi.hssf.record.formula.eval.StringEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for Excel function T()
+ * 
+ * @author Josh Micich
+ */
+public final class TestTFunc extends TestCase {
+
+       /**
+        * @return the result of calling function T() with the specified argument
+        */
+       private static Eval invokeT(Eval arg) {
+               Eval[] args = { arg, };
+               Eval result = new T().evaluate(args, -1, (short)-1);
+               assertNotNull("result may never be null", result);
+               return result;
+       }
+       /**
+        * Simulates call: T(A1)
+        * 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);
+               return invokeT(arg);
+       }
+       
+       private static void confirmText(String text) {
+               Eval arg = new StringEval(text);
+               Eval eval = invokeT(arg);
+               StringEval se = (StringEval) eval;
+               assertEquals(text, se.getStringValue());
+       }
+       
+       public void testTextValues() {
+               
+               confirmText("abc");
+               confirmText("");
+               confirmText(" ");
+               confirmText("~");
+               confirmText("123");
+               confirmText("TRUE");
+       }
+       
+       private static void confirmError(Eval arg) {
+               Eval eval = invokeT(arg);
+               assertTrue(arg == eval);
+       }
+
+       public void testErrorValues() {
+               
+               confirmError(ErrorEval.VALUE_INVALID);
+               confirmError(ErrorEval.NA);
+               confirmError(ErrorEval.REF_INVALID);
+       }
+       
+       private static void confirmString(Eval eval, String expected) {
+               assertTrue(eval instanceof StringEval);
+               assertEquals(expected, ((StringEval)eval).getStringValue());
+       }
+
+       private static void confirmOther(Eval arg) {
+               Eval eval = invokeT(arg);
+               confirmString(eval, "");
+       }
+
+       public void testOtherValues() {
+               confirmOther(new NumberEval(2));
+               confirmOther(BoolEval.FALSE);
+               confirmOther(BlankEval.INSTANCE);  // can this particular case be verified?
+       }
+
+       public void testRefValues() {
+               Eval eval;
+               
+               eval = invokeTWithReference(new StringEval("def"));
+               confirmString(eval, "def");
+               eval = invokeTWithReference(new StringEval(" "));
+               confirmString(eval, " ");
+
+               eval = invokeTWithReference(new NumberEval(2));
+               confirmString(eval, "");
+               eval = invokeTWithReference(BoolEval.TRUE);
+               confirmString(eval, "");
+               
+               eval = invokeTWithReference(ErrorEval.NAME_INVALID);
+               assertTrue(eval == ErrorEval.NAME_INVALID);
+       }
+}
index cf4b6fa5011723ad05fd24152a3a3e4a451d2c98..001432f0aaa78d228526d4cb9086e10e8baeb1ec 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
index 001432f0aaa78d228526d4cb9086e10e8baeb1ec..524136921295c334de8c0ac65bbb96c77dc9e63c 100644 (file)
Binary files a/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls and b/src/testcases/org/apache/poi/hssf/data/TestDataValidation.xls differ
index 1b41d87d4d97baccfe8c6d06a21af6dc1fc5ebbe..e7400bc3512fdf1c4fdfda0670a71a202d4e39ae 100644 (file)
@@ -18,6 +18,8 @@
         
 package org.apache.poi.hssf.model;
 
+import java.util.List;
+
 import junit.framework.TestCase;
 
 import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
@@ -26,6 +28,7 @@ import org.apache.poi.hssf.record.formula.AttrPtg;
 import org.apache.poi.hssf.record.formula.BoolPtg;
 import org.apache.poi.hssf.record.formula.DividePtg;
 import org.apache.poi.hssf.record.formula.EqualPtg;
+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;
@@ -397,7 +400,7 @@ public class TestFormulaParser extends TestCase {
        public void testUnderscore() {
                HSSFWorkbook wb = new HSSFWorkbook();
        
-       wb.createSheet("Cash_Flow");;
+       wb.createSheet("Cash_Flow");
        
        HSSFSheet sheet = wb.createSheet("Test");
        HSSFRow row = sheet.createRow(0);
@@ -438,7 +441,7 @@ public class TestFormulaParser extends TestCase {
     public void testExponentialInSheet() throws Exception {
         HSSFWorkbook wb = new HSSFWorkbook();
 
-        wb.createSheet("Cash_Flow");;
+        wb.createSheet("Cash_Flow");
 
         HSSFSheet sheet = wb.createSheet("Test");
         HSSFRow row = sheet.createRow(0);
@@ -514,7 +517,7 @@ public class TestFormulaParser extends TestCase {
     public void testNumbers() {
         HSSFWorkbook wb = new HSSFWorkbook();
         
-        wb.createSheet("Cash_Flow");;
+        wb.createSheet("Cash_Flow");
         
         HSSFSheet sheet = wb.createSheet("Test");
         HSSFRow row = sheet.createRow(0);
@@ -553,7 +556,7 @@ public class TestFormulaParser extends TestCase {
     public void testRanges() {
         HSSFWorkbook wb = new HSSFWorkbook();
         
-        wb.createSheet("Cash_Flow");;
+        wb.createSheet("Cash_Flow");
         
         HSSFSheet sheet = wb.createSheet("Test");
         HSSFRow row = sheet.createRow(0);
@@ -571,5 +574,19 @@ public class TestFormulaParser extends TestCase {
         cell.setCellFormula("A1...A2");
         formula = cell.getCellFormula();
         assertEquals("A1:A2", formula);
-    }        
+    }
+    
+    /**
+     * Test for bug observable at svn revision 618865 (5-Feb-2008)<br/>
+     * a formula consisting of a single no-arg function got rendered without the function braces
+     */
+    public void testToFormulaStringZeroArgFunction() {
+        
+        Workbook book = Workbook.createWorkbook(); // not really used in this test
+        
+        Ptg[] ptgs = {
+                new FuncPtg(10, 0),
+        };
+        assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs));
+    }
 }