]> source.dussan.org Git - poi.git/commitdiff
Patch from Josh from bug #44371 - support for OFFSET function, and various tweaks...
authorNick Burch <nick@apache.org>
Thu, 21 Feb 2008 15:08:44 +0000 (15:08 +0000)
committerNick Burch <nick@apache.org>
Thu, 21 Feb 2008 15:08:44 +0000 (15:08 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@629821 13f79535-47bb-0310-9956-ffa450edef68

13 files changed:
src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/formula/Area3DPtg.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Area3DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/eval/Ref3DEval.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java [new file with mode: 0755]
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Indirect.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Offset.java
src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/Rows.java
src/scratchpad/src/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java
src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java [new file with mode: 0755]

index eea475fd2c2861f18850a1a44290be6717918f68..3b4364fef7e6892e6576d5309dd7e7823cf102fb 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">44371 - Support for the Offset function</action>
            <action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
            <action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
            <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
index 2d176896f5d93dccc96078fb24e47cdcb87a79c6..43839ae8b014ab41aa073cf4f037c2fb27e14556 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">44371 - Support for the Offset function</action>
            <action dev="POI-DEVELOPERS" type="fix">38921 - Have HSSFPalette.findSimilar() work properly</action>
            <action dev="POI-DEVELOPERS" type="fix">44456 - Fix the contrib SViewer / SViewerPanel to not fail on sheets with missing rows</action>
            <action dev="POI-DEVELOPERS" type="fix">44403 - Further support for unusual, but valid, arguments to the Mid function</action>
index 91417994e4d1377cb5af98b66ae6bcffc239c988..c3de3758f5f27cb7bb48ff0bcb4b89853efb9261 100644 (file)
@@ -70,7 +70,21 @@ public class Area3DPtg extends Ptg implements AreaI
                field_5_last_column = in.readShort();
        }
 
-       public String toString()
+       public Area3DPtg(short firstRow, short lastRow, short firstColumn, short lastColumn,
+               boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative,
+               short externalSheetIndex) {
+             setFirstRow(firstRow);
+             setLastRow(lastRow);
+             setFirstColumn(firstColumn);
+             setLastColumn(lastColumn);
+             setFirstRowRelative(firstRowRelative);
+             setLastRowRelative(lastRowRelative);
+             setFirstColRelative(firstColRelative);
+             setLastColRelative(lastColRelative);
+             setExternSheetIndex(externalSheetIndex);
+    }
+
+    public String toString()
        {
                StringBuffer buffer = new StringBuffer();
 
index d371a0985502fdf8c19bce52304deb4f4e0f1f17..b997b95c6da394519667c9a5a14316473abaf94c 100644 (file)
@@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.formula.Ptg;
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
  *  
  */
-public class Area3DEval implements AreaEval {
+public final class Area3DEval implements AreaEval {
 
     private Area3DPtg delegate;
 
@@ -90,4 +90,8 @@ public class Area3DEval implements AreaEval {
         return delegate.getFirstRow() == delegate.getLastRow();
     }
 
+    public int getExternSheetIndex() {
+        return delegate.getExternSheetIndex();
+    }
+
 }
index d1420b2e852ed7c9d955589521d63f0ed1366ad5..d1c17b9f5442ad73c83c0cedd09605ede35122a6 100644 (file)
@@ -20,6 +20,9 @@
  */
 package org.apache.poi.hssf.record.formula.eval;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import org.apache.poi.hssf.record.formula.functions.*;
 
 /**
@@ -27,12 +30,47 @@ import org.apache.poi.hssf.record.formula.functions.*;
  *  
  */
 public abstract class FunctionEval implements OperationEval {
+    /**
+     * Some function IDs that require special treatment
+     */
+    private static final class FunctionID {
+        /** 78 */
+        public static final int OFFSET = 78;
+        /** 148 */
+        public static final int INDIRECT = 148;
+        
+    }
+    // convenient access to namespace
+    private static final FunctionID ID = null;
+    
     protected static Function[] functions = produceFunctions();
 
+    private static Map freeRefFunctionsByIdMap;
+     
+    static {
+        Map m = new HashMap();
+        addMapping(m, ID.OFFSET, new Offset());
+        addMapping(m, ID.INDIRECT, new Indirect());
+        freeRefFunctionsByIdMap = m;
+    }
+    private static void addMapping(Map m, int offset, FreeRefFunction frf) {
+        m.put(createFRFKey(offset), frf);
+    }
+    private static Integer createFRFKey(int functionIndex) {
+        return new Integer(functionIndex);
+    }
+    
+    
     public Function getFunction() {
         short fidx = getFunctionIndex();
         return functions[fidx];
     }
+    public boolean isFreeRefFunction() {
+        return freeRefFunctionsByIdMap.containsKey(createFRFKey(getFunctionIndex()));
+    }
+    public FreeRefFunction getFreeRefFunction() {
+        return (FreeRefFunction) freeRefFunctionsByIdMap.get(createFRFKey(getFunctionIndex()));
+    }
 
     public abstract short getFunctionIndex();
 
@@ -115,7 +153,7 @@ public abstract class FunctionEval implements OperationEval {
         retval[75] = new Areas(); // AREAS
         retval[76] = new Rows(); // ROWS
         retval[77] = new Columns(); // COLUMNS
-        retval[78] = new Offset(); // OFFSET
+        retval[ID.OFFSET] = null; // Offset.evaluate has a different signature
         retval[79] = new Absref(); // ABSREF
         retval[80] = new Relref(); // RELREF
         retval[81] = new Argument(); // ARGUMENT
@@ -185,7 +223,7 @@ public abstract class FunctionEval implements OperationEval {
         retval[145] = new NotImplementedFunction(); // GETDEF
         retval[146] = new Reftext(); // REFTEXT
         retval[147] = new Textref(); // TEXTREF
-        retval[148] = new Indirect(); // INDIRECT
+        retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
         retval[149] = new NotImplementedFunction(); // REGISTER
         retval[150] = new Call(); // CALL
         retval[151] = new NotImplementedFunction(); // ADDBAR
index acedbe766d697a987683de7ac68c4198c6d1524c..dcf8f0f712a815031dcb9a2bcb71a234c7e05167 100644 (file)
@@ -27,7 +27,7 @@ import org.apache.poi.hssf.record.formula.Ref3DPtg;
  * @author Amol S. Deshmukh
  *  
  */
-public class Ref3DEval implements RefEval {
+public final class Ref3DEval implements RefEval {
 
     private ValueEval value;
 
@@ -57,4 +57,8 @@ public class Ref3DEval implements RefEval {
         return evaluated;
     }
 
+    public int getExternSheetIndex() {
+        return delegate.getExternSheetIndex();
+    }
+
 }
diff --git a/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java b/src/scratchpad/src/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java
new file mode 100755 (executable)
index 0000000..56d2855
--- /dev/null
@@ -0,0 +1,57 @@
+/* ====================================================================
+   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.Eval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+
+/**
+ * For most Excel functions, involving references ((cell, area), (2d, 3d)), the references are 
+ * passed in as arguments, and the exact location remains fixed.  However, a select few Excel
+ * functions have the ability to access cells that were not part of any reference passed as an
+ * argument.<br/>
+ * Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/>
+ *  
+ * In POI, the <tt>HSSFFormulaEvaluator</tt> evaluates every cell in each reference argument before
+ * calling the function.  This means that functions using fixed references do not need access to
+ * the rest of the workbook to execute.  Hence the <tt>evaluate()</tt> method on the common
+ * interface <tt>Function</tt> does not take a workbook parameter.<p>  
+ * 
+ * This interface recognises the requirement of some functions to freely create and evaluate 
+ * references beyond those passed in as arguments.
+ * 
+ * @author Josh Micich
+ */
+public interface FreeRefFunction {
+       /**
+        * 
+        * @param args the pre-evaluated arguments for this function. args is never <code>null</code>,
+        *                nor are any of its elements.
+        * @param srcCellRow zero based row index of the cell containing the currently evaluating formula 
+        * @param srcCellCol zero based column index of the cell containing the currently evaluating formula 
+        * @param workbook is the workbook containing the formula/cell being evaluated
+        * @param sheet is the sheet containing the formula/cell being evaluated
+        * @return never <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> in the case of 
+        * a specified Excel error (Exceptions are never thrown to represent Excel errors).
+        * 
+        */
+       ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet);
+}
index c7464ffed804f49fbbea0a837d753df162b7bd5a..935e7cdbbdaebb968132013efa276563a4373a90 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 Indirect extends NotImplementedFunction {
+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.ValueEval;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+
+/**
+ * Implementation for Excel function INDIRECT<p/>
+ * 
+ * INDIRECT() returns the cell or area reference denoted by the text argument.<p/> 
+ * 
+ * <b>Syntax</b>:</br>
+ * <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/>
+ * 
+ * <b>ref_text</b> a string representation of the desired reference as it would normally be written
+ * in a cell formula.<br/>
+ * <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be interpreted as A1-style
+ * or R1C1-style.
+ * 
+ * 
+ * @author Josh Micich
+ */
+public final class Indirect implements FreeRefFunction {
+
+       public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
+               // TODO - implement INDIRECT()
+               return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
+       }
 
 }
index 80539d15f94f9fdad5c181baa3bdd3deac403ef9..9497a5f21a908ae2baaed4d1cb81f57c19863340 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 Offset extends NotImplementedFunction {
+import org.apache.poi.hssf.record.formula.Area3DPtg;
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.hssf.record.formula.eval.Area3DEval;
+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.NumericValueEval;
+import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
+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.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+/**
+ * Implementation for Excel function OFFSET()<p/>
+ * 
+ * OFFSET returns an area reference that is a specified number of rows and columns from a 
+ * reference cell or area.<p/>
+ * 
+ * <b>Syntax</b>:<br/>
+ * <b>OFFSET</b>(<b>reference</b>, <b>rows</b>, <b>cols</b>, height, width)<p/>
+ * <b>reference</b> is the base reference.<br/>
+ * <b>rows</b> is the number of rows up or down from the base reference.<br/>
+ * <b>cols</b> is the number of columns left or right from the base reference.<br/>
+ * <b>height</b> (default same height as base reference) is the row count for the returned area reference.<br/>
+ * <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/>
+ * 
+ * @author Josh Micich
+ */
+public final class Offset implements FreeRefFunction {
+       // These values are specific to BIFF8
+       private static final int LAST_VALID_ROW_INDEX = 0xFFFF;
+       private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
+       
+
+       /**
+        * Exceptions are used within this class to help simplify flow control when error conditions
+        * are encountered 
+        */
+       private static final class EvalEx extends Exception {
+               private final ErrorEval _error;
+
+               public EvalEx(ErrorEval error) {
+                       _error = error;
+               }
+               public ErrorEval getError() {
+                       return _error;
+               }
+       }
+       
+       /** 
+        * A one dimensional base + offset.  Represents either a row range or a column range.
+        * Two instances of this class together specify an area range.
+        */
+       /* package */ static final class LinearOffsetRange {
+
+               private final int _offset;
+               private final int _length;
+
+               public LinearOffsetRange(int offset, int length) {
+                       if(length == 0) {
+                               // handled that condition much earlier
+                               throw new RuntimeException("length may not be zero");
+                       }
+                       _offset = offset;
+                       _length = length;
+               }
+               
+               public short getFirstIndex() {
+                       return (short) _offset;
+               }
+               public short getLastIndex() {
+                       return (short) (_offset + _length - 1);
+               }
+               /**
+                * Moves the range by the specified translation amount.<p/>
+                * 
+                * This method also 'normalises' the range: Excel specifies that the width and height 
+                * parameters (length field here) cannot be negative.  However, OFFSET() does produce
+                * sensible results in these cases.  That behavior is replicated here. <p/>
+                * 
+                * @param translationAmount may be zero negative or positive
+                * 
+                * @return the equivalent <tt>LinearOffsetRange</tt> with a positive length, moved by the
+                * specified translationAmount.
+                */
+               public LinearOffsetRange normaliseAndTranslate(int translationAmount) {
+                       if (_length > 0) {
+                               if(translationAmount == 0) {
+                                       return this;
+                               }
+                               return new LinearOffsetRange(translationAmount + _offset, _length);
+                       }
+                       return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length);
+               }
+
+               public boolean isOutOfBounds(int lowValidIx, int highValidIx) {
+                       if(_offset < lowValidIx) {
+                               return true;
+                       }
+                       if(getLastIndex() > highValidIx) {
+                               return true;
+                       }
+                       return false;
+               }
+               public String toString() {
+                       StringBuffer sb = new StringBuffer(64);
+                       sb.append(getClass().getName()).append(" [");
+                       sb.append(_offset).append("...").append(getLastIndex());
+                       sb.append("]");
+                       return sb.toString();
+               }
+       }
+       
+       
+       /**
+        * Encapsulates either an area or cell reference which may be 2d or 3d.
+        */
+       private static final class BaseRef {
+               private static final int INVALID_SHEET_INDEX = -1;
+               private final int _firstRowIndex;
+               private final int _firstColumnIndex;
+               private final int _width;
+               private final int _height;
+               private final int _externalSheetIndex;
+               
+               public BaseRef(RefEval re) {
+                       _firstRowIndex = re.getRow();
+                       _firstColumnIndex = re.getColumn();
+                       _height = 1;
+                       _width = 1;
+                       if (re instanceof Ref3DEval) {
+                               Ref3DEval r3e = (Ref3DEval) re;
+                               _externalSheetIndex = r3e.getExternSheetIndex();
+                       } else {
+                               _externalSheetIndex = INVALID_SHEET_INDEX;
+                       }
+               }
+
+               public BaseRef(AreaEval ae) {
+                       _firstRowIndex = ae.getFirstRow();
+                       _firstColumnIndex = ae.getFirstColumn();
+                       _height = ae.getLastRow() - ae.getFirstRow() + 1;
+                       _width = ae.getLastColumn() - ae.getFirstColumn() + 1;
+                       if (ae instanceof Area3DEval) {
+                               Area3DEval a3e = (Area3DEval) ae;
+                               _externalSheetIndex = a3e.getExternSheetIndex();
+                       } else {
+                               _externalSheetIndex = INVALID_SHEET_INDEX;
+                       }
+               }
+
+               public int getWidth() {
+                       return _width;
+               }
+
+               public int getHeight() {
+                       return _height;
+               }
+
+               public int getFirstRowIndex() {
+                       return _firstRowIndex;
+               }
+
+               public int getFirstColumnIndex() {
+                       return _firstColumnIndex;
+               }
+
+               public boolean isIs3d() {
+                       return _externalSheetIndex > 0;
+               }
+
+               public short getExternalSheetIndex() {
+                       if(_externalSheetIndex < 0) {
+                               throw new IllegalStateException("external sheet index only available for 3d refs");
+                       }
+                       return (short) _externalSheetIndex;
+               }
+
+       }
+       
+       public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
+               
+               if(args.length < 3 || args.length > 5) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+               
+               
+               try {
+                       BaseRef baseRef = evaluateBaseRef(args[0]);
+                       int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
+                       int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol);
+                       int height = baseRef.getHeight();
+                       int width = baseRef.getWidth();
+                       switch(args.length) {
+                               case 5:
+                                       width = evaluateIntArg(args[4], srcCellRow, srcCellCol);
+                               case 4:
+                                       height = evaluateIntArg(args[3], srcCellRow, srcCellCol);
+                       }
+                       // Zero height or width raises #REF! error
+                       if(height == 0 || width == 0) {
+                               return ErrorEval.REF_INVALID;
+                       }
+                       LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
+                       LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
+                       return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
+               } catch (EvalEx e) {
+                       return e.getError();
+               }
+       }
+
+
+       private static AreaEval createOffset(BaseRef baseRef, 
+                       LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange, 
+                       HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx {
+
+               LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
+               LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
+               
+               if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
+                       throw new EvalEx(ErrorEval.REF_INVALID);
+               }
+               if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
+                       throw new EvalEx(ErrorEval.REF_INVALID);
+               }
+               if(baseRef.isIs3d()) {
+                       Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(), 
+                                       cols.getFirstIndex(), cols.getLastIndex(),
+                                       false, false, false, false,
+                                       baseRef.getExternalSheetIndex());
+                       return HSSFFormulaEvaluator.evaluateArea3dPtg(workbook, a3dp);
+               }
+               
+               AreaPtg ap = new AreaPtg(rows.getFirstIndex(), rows.getLastIndex(), 
+                               cols.getFirstIndex(), cols.getLastIndex(),
+                               false, false, false, false);
+               return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
+       }
+
+
+       private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
+               
+               if(eval instanceof RefEval) {
+                       return new BaseRef((RefEval)eval);
+               }
+               if(eval instanceof AreaEval) {
+                       return new BaseRef((AreaEval)eval);
+               }
+               if (eval instanceof ErrorEval) {
+                       throw new EvalEx((ErrorEval) eval);
+               }
+               throw new EvalEx(ErrorEval.VALUE_INVALID);
+       }
+
+
+       /**
+        * OFFSET's numeric arguments (2..5) have similar processing rules
+        */
+       private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
+
+               double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
+               return convertDoubleToInt(d);
+       }
+
+       /**
+        * Fractional values are silently truncated by Excel.
+        * Truncation is toward negative infinity.
+        */
+       /* package */ static int convertDoubleToInt(double d) {
+               // Note - the standard java type conversion from double to int truncates toward zero.
+               // but Math.floor() truncates toward negative infinity
+               return (int)Math.floor(d);
+       }
+       
+       
+       private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
+               ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
+               
+               if (ve instanceof NumericValueEval) {
+                       return ((NumericValueEval) ve).getNumberValue();
+               }
+               if (ve instanceof StringEval) {
+                       StringEval se = (StringEval) ve;
+                       Double d = parseDouble(se.getStringValue());
+                       if(d == null) {
+                               throw new EvalEx(ErrorEval.VALUE_INVALID);
+                       }
+                       return d.doubleValue();
+               }
+               if (ve instanceof BoolEval) {
+                       // in the context of OFFSET, booleans resolve to 0 and 1.
+                       if(((BoolEval) ve).getBooleanValue()) {
+                               return 1;
+                       }
+                       return 0;
+               }
+               throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
+       }
+       
+       private static Double parseDouble(String s) {
+               // TODO - find a home for this method
+               // TODO - support various number formats: sign char, dollars, commas
+               // OFFSET and COUNTIF seem to handle these
+               return Countif.parseDouble(s);
+       }
+       
+       private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
+               if(eval instanceof RefEval) {
+                       return ((RefEval)eval).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() + ")");
+       }
 
+       // TODO - this code seems to get repeated a bit
+       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);
+       }
 }
index 6a4eb8edb7b45f46149c1c805e38a3641dc01bdc..aabffab2f6a8867d5e2c07555d021670440c5ad2 100644 (file)
@@ -25,7 +25,7 @@ import org.apache.poi.hssf.record.formula.eval.NumberEval;
 import org.apache.poi.hssf.record.formula.eval.RefEval;
 
 /**
- * Implementation for Excel COLUMNS function.
+ * Implementation for Excel ROWS function.
  * 
  * @author Josh Micich
  */
index 7cad38f1029594d4c9023672918ee871a96cafa3..acbca05e6257aa4316ae7679ed668ae568616456 100644 (file)
@@ -74,6 +74,7 @@ import org.apache.poi.hssf.record.formula.eval.EqualEval;
 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.FuncVarEval;
+import org.apache.poi.hssf.record.formula.eval.FunctionEval;
 import org.apache.poi.hssf.record.formula.eval.GreaterEqualEval;
 import org.apache.poi.hssf.record.formula.eval.GreaterThanEval;
 import org.apache.poi.hssf.record.formula.eval.LessEqualEval;
@@ -91,7 +92,6 @@ import org.apache.poi.hssf.record.formula.eval.SubtractEval;
 import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval;
 import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFSheet;
 
 /**
  * @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
@@ -379,7 +379,7 @@ public class HSSFFormulaEvaluator {
         Stack stack = new Stack();
         for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
 
-            // since we dont know how to handle these yet :(
+            // since we don't know how to handle these yet :(
             if (ptgs[i] instanceof ControlPtg) { continue; }
             if (ptgs[i] instanceof MemErrPtg) { continue; }
             if (ptgs[i] instanceof MissingArgPtg) { continue; }
@@ -405,7 +405,7 @@ public class HSSFFormulaEvaluator {
                     Eval p = (Eval) stack.pop();
                     ops[j] = p;
                 }
-                Eval opresult = operation.evaluate(ops, srcRowNum, srcColNum);
+                Eval opresult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet);
                 stack.push(opresult);
             }
             else if (ptgs[i] instanceof ReferencePtg) {
@@ -428,56 +428,12 @@ public class HSSFFormulaEvaluator {
             }
             else if (ptgs[i] instanceof AreaPtg) {
                 AreaPtg ap = (AreaPtg) ptgs[i];
-                short row0 = ap.getFirstRow();
-                short col0 = ap.getFirstColumn();
-                short row1 = ap.getLastRow();
-                short col1 = ap.getLastColumn();
-                
-                // If the last row is -1, then the
-                //  reference is for the rest of the column
-                // (eg C:C)
-                // TODO: Handle whole column ranges properly
-                if(row1 == -1 && row0 >= 0) {
-                       row1 = (short)sheet.getLastRowNum();
-                }
-                
-                ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
-                for (short x = row0; sheet != null && x < row1 + 1; x++) {
-                    HSSFRow row = sheet.getRow(x);
-                    for (short y = col0; row != null && y < col1 + 1; y++) {
-                        values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = 
-                            getEvalForCell(row.getCell(y), row, sheet, workbook);
-                    }
-                }
-                AreaEval ae = new Area2DEval(ap, values);
+                AreaEval ae = evaluateAreaPtg(sheet, workbook, ap);
                 stack.push(ae);
             }
             else if (ptgs[i] instanceof Area3DPtg) {
                 Area3DPtg a3dp = (Area3DPtg) ptgs[i];
-                short row0 = a3dp.getFirstRow();
-                short col0 = a3dp.getFirstColumn();
-                short row1 = a3dp.getLastRow();
-                short col1 = a3dp.getLastColumn();
-                Workbook wb = workbook.getWorkbook();
-                HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
-                
-                // If the last row is -1, then the
-                //  reference is for the rest of the column
-                // (eg C:C)
-                // TODO: Handle whole column ranges properly
-                if(row1 == -1 && row0 >= 0) {
-                       row1 = (short)xsheet.getLastRowNum();
-                }
-                
-                ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
-                for (short x = row0; xsheet != null && x < row1 + 1; x++) {
-                    HSSFRow row = xsheet.getRow(x);
-                    for (short y = col0; row != null && y < col1 + 1; y++) {
-                        values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = 
-                            getEvalForCell(row.getCell(y), row, xsheet, workbook);
-                    }
-                }
-                AreaEval ae = new Area3DEval(a3dp, values);
+                AreaEval ae = evaluateArea3dPtg(workbook, a3dp);
                 stack.push(ae);
             }
             else {
@@ -485,23 +441,95 @@ public class HSSFFormulaEvaluator {
                 stack.push(ptgEval);
             }
         }
+
         ValueEval value = ((ValueEval) stack.pop());
         if (value instanceof RefEval) {
             RefEval rv = (RefEval) value;
-            value = rv.getInnerValueEval();
+            return rv.getInnerValueEval();
         }
-        else if (value instanceof AreaEval) {
+        if (value instanceof AreaEval) {
             AreaEval ae = (AreaEval) value;
-            if (ae.isRow()) 
-                value = ae.getValueAt(ae.getFirstRow(), srcColNum);
-            else if (ae.isColumn()) 
-                value = ae.getValueAt(srcRowNum, ae.getFirstColumn());
-            else
-                value = ErrorEval.VALUE_INVALID;
+            if (ae.isRow()) {
+                if(ae.isColumn()) {
+                    return ae.getValues()[0];
+                }
+                return ae.getValueAt(ae.getFirstRow(), srcColNum);
+            }
+            if (ae.isColumn()) {
+                return ae.getValueAt(srcRowNum, ae.getFirstColumn());
+            }
+            return ErrorEval.VALUE_INVALID;
         }
         return value;
     }
 
+    private static Eval invokeOperation(OperationEval operation, Eval[] ops, int srcRowNum, short srcColNum,
+            HSSFWorkbook workbook, HSSFSheet sheet) {
+
+        if(operation instanceof FunctionEval) {
+            FunctionEval fe = (FunctionEval) operation;
+            if(fe.isFreeRefFunction()) {
+                return fe.getFreeRefFunction().evaluate(ops, srcRowNum, srcColNum, workbook, sheet);
+            }
+        }
+        return operation.evaluate(ops, srcRowNum, srcColNum);
+    }
+    
+    public static AreaEval evaluateAreaPtg(HSSFSheet sheet, HSSFWorkbook workbook, AreaPtg ap) {
+        short row0 = ap.getFirstRow();
+        short col0 = ap.getFirstColumn();
+        short row1 = ap.getLastRow();
+        short col1 = ap.getLastColumn();
+        
+        // If the last row is -1, then the
+        //  reference is for the rest of the column
+        // (eg C:C)
+        // TODO: Handle whole column ranges properly
+        if(row1 == -1 && row0 >= 0) {
+               row1 = (short)sheet.getLastRowNum();
+        }
+        
+        ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
+        for (short x = row0; sheet != null && x < row1 + 1; x++) {
+            HSSFRow row = sheet.getRow(x);
+            for (short y = col0; row != null && y < col1 + 1; y++) {
+                values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = 
+                    getEvalForCell(row.getCell(y), row, sheet, workbook);
+            }
+        }
+        AreaEval ae = new Area2DEval(ap, values);
+        return ae;
+    }
+
+    public static AreaEval evaluateArea3dPtg(HSSFWorkbook workbook, Area3DPtg a3dp) {
+        short row0 = a3dp.getFirstRow();
+        short col0 = a3dp.getFirstColumn();
+        short row1 = a3dp.getLastRow();
+        short col1 = a3dp.getLastColumn();
+        Workbook wb = workbook.getWorkbook();
+        HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(a3dp.getExternSheetIndex()));
+        
+        // If the last row is -1, then the
+        //  reference is for the rest of the column
+        // (eg C:C)
+        // TODO: Handle whole column ranges properly
+        if(row1 == -1 && row0 >= 0) {
+               row1 = (short)xsheet.getLastRowNum();
+        }
+        
+        ValueEval[] values = new ValueEval[(row1 - row0 + 1) * (col1 - col0 + 1)];
+        for (short x = row0; xsheet != null && x < row1 + 1; x++) {
+            HSSFRow row = xsheet.getRow(x);
+            for (short y = col0; row != null && y < col1 + 1; y++) {
+                values[(x - row0) * (col1 - col0 + 1) + (y - col0)] = 
+                    getEvalForCell(row.getCell(y), row, xsheet, workbook);
+            }
+        }
+        AreaEval ae = new Area3DEval(a3dp, values);
+        return ae;
+    }
+
+
     /**
      * returns the OperationEval concrete impl instance corresponding
      * to the suplied operationPtg
index 1987a06376c247a58b2d9ef9cb348588add15f0a..8d21956edb5b60227b7fbaeda5b54646ca344679 100755 (executable)
@@ -38,6 +38,7 @@ public final class AllIndividualFunctionEvaluationTests {
                result.addTestSuite(TestMid.class);
                result.addTestSuite(TestMathX.class);
                result.addTestSuite(TestMatch.class);
+               result.addTestSuite(TestOffset.class);
                result.addTestSuite(TestRowCol.class);
                result.addTestSuite(TestSumproduct.class);
                result.addTestSuite(TestStatsLib.class);
diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java b/src/scratchpad/testcases/org/apache/poi/hssf/record/formula/functions/TestOffset.java
new file mode 100755 (executable)
index 0000000..f114662
--- /dev/null
@@ -0,0 +1,92 @@
+/* ====================================================================
+   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.functions.Offset.LinearOffsetRange;
+
+/**
+ * Tests for OFFSET function implementation
+ * 
+ * @author Josh Micich
+ */
+public final class TestOffset extends TestCase {
+
+       
+       private static void confirmDoubleConvert(double doubleVal, int expected) {
+               assertEquals(expected, Offset.convertDoubleToInt(doubleVal));
+       }
+       /**
+        * Excel's double to int conversion (for function 'OFFSET()') behaves more like Math.floor().
+        * Note - negative values are not symmetrical
+        */
+       public void testDoubleConversion() {
+               
+               confirmDoubleConvert(100.09, 100);
+               confirmDoubleConvert(100.01, 100);
+               confirmDoubleConvert(100.00, 100);
+               confirmDoubleConvert(99.99, 99);
+               
+               confirmDoubleConvert(+2.01, +2);
+               confirmDoubleConvert(+2.00, +2);
+               confirmDoubleConvert(+1.99, +1);
+               confirmDoubleConvert(+1.01, +1);
+               confirmDoubleConvert(+1.00, +1);
+               confirmDoubleConvert(+0.99,  0);
+               confirmDoubleConvert(+0.01,  0);
+               confirmDoubleConvert( 0.00,  0);
+               confirmDoubleConvert(-0.01, -1);
+               confirmDoubleConvert(-0.99, -1);
+               confirmDoubleConvert(-1.00, -1);
+               confirmDoubleConvert(-1.01, -2);
+               confirmDoubleConvert(-1.99, -2);
+               confirmDoubleConvert(-2.00, -2);
+               confirmDoubleConvert(-2.01, -3);
+       }
+
+       public void testLinearOffsetRange() {
+               LinearOffsetRange lor;
+               
+               lor = new LinearOffsetRange(3, 2);
+               assertEquals(3, lor.getFirstIndex());
+               assertEquals(4, lor.getLastIndex());
+               lor = lor.normaliseAndTranslate(0); // expected no change
+               assertEquals(3, lor.getFirstIndex());
+               assertEquals(4, lor.getLastIndex());
+               
+               lor = lor.normaliseAndTranslate(5);
+               assertEquals(8, lor.getFirstIndex());
+               assertEquals(9, lor.getLastIndex());
+               
+               // negative length
+               
+               lor = new LinearOffsetRange(6, -4).normaliseAndTranslate(0);
+               assertEquals(3, lor.getFirstIndex());
+               assertEquals(6, lor.getLastIndex());
+               
+               
+               // bounds checking
+               lor = new LinearOffsetRange(0, 100);
+               assertFalse(lor.isOutOfBounds(0, 16383));
+               lor = lor.normaliseAndTranslate(16300);
+               assertTrue(lor.isOutOfBounds(0, 16383));
+               assertFalse(lor.isOutOfBounds(0, 65535));
+       }
+       
+}