]> source.dussan.org Git - poi.git/commitdiff
Bugzilla 47721 - Added implementation for INDIRECT()
authorJosh Micich <josh@apache.org>
Fri, 21 Aug 2009 23:37:17 +0000 (23:37 +0000)
committerJosh Micich <josh@apache.org>
Fri, 21 Aug 2009 23:37:17 +0000 (23:37 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@806759 13f79535-47bb-0310-9956-ffa450edef68

21 files changed:
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java
src/java/org/apache/poi/hssf/record/formula/atp/ParityFunction.java
src/java/org/apache/poi/hssf/record/formula/atp/YearFrac.java
src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java
src/java/org/apache/poi/hssf/record/formula/eval/OperationEval.java
src/java/org/apache/poi/hssf/record/formula/eval/UserDefinedFunction.java
src/java/org/apache/poi/hssf/record/formula/functions/FreeRefFunction.java
src/java/org/apache/poi/hssf/record/formula/functions/Indirect.java
src/java/org/apache/poi/ss/formula/CollaboratingWorkbooksEnvironment.java
src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
src/java/org/apache/poi/ss/formula/LazyRefEval.java
src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/OperationEvaluatorFactory.java
src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
src/testcases/org/apache/poi/hssf/record/formula/eval/TestEqualEval.java
src/testcases/org/apache/poi/hssf/record/formula/eval/TestRangeEval.java
src/testcases/org/apache/poi/hssf/record/formula/eval/TestUnaryPlusEval.java
src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndirect.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java

index bded2bc4a5dbdaebb402737909127dc0e7afb04e..491b65e1da0e3c3a0e5b8e0e9f0a1e326afe9c25 100644 (file)
@@ -33,6 +33,7 @@
 
     <changes>
         <release version="3.5-beta7" date="2009-??-??">
+           <action dev="POI-DEVELOPERS" type="add">47721 - Added implementation for INDIRECT()</action>
            <action dev="POI-DEVELOPERS" type="add">45583 - Avoid exception when reading ClipboardData packet in OLE property sets</action>
            <action dev="POI-DEVELOPERS" type="add">47652 - Added support for reading encrypted workbooks</action>
            <action dev="POI-DEVELOPERS" type="add">47604 - Implementation of an XML to XLSX Importer using Custom XML Mapping</action>
@@ -42,7 +43,7 @@
            <action dev="POI-DEVELOPERS" type="fix">47571 - Fixed XWPFWordExtractor to extract inserted/deleted text</action>
            <action dev="POI-DEVELOPERS" type="fix">47548 - Fixed RecordFactoryInputStream to properly read continued DrawingRecords</action>
            <action dev="POI-DEVELOPERS" type="fix">46419 - Fixed compatibility issue with OpenOffice 3.0</action>
-           <action dev="POI-DEVELOPERS" type="fix">47559 - Fixed compatibility issue with Excel 2008 Mac sp2. Please see 
+           <action dev="POI-DEVELOPERS" type="fix">47559 - Fixed compatibility issue with Excel 2008 Mac sp2. Please see
              <link href="./spreadsheet/index.html"> the HSSF+XSSF project page</link> for more information. </action>
            <action dev="POI-DEVELOPERS" type="fix">47540 - Fix for saving custom and extended OOXML properties</action>
            <action dev="POI-DEVELOPERS" type="fix">47535 - Fixed WordExtractor to tolerate files with empty footnote block</action>
index af5604152184228908354d84766b87bf7c39b258..24423d04a003e9eed726d1fe9ac12cf6ecfd3711 100644 (file)
@@ -22,7 +22,7 @@ import java.util.Map;
 
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 import org.apache.poi.ss.formula.eval.NotImplementedException;
 
 public final class AnalysisToolPak {
@@ -34,8 +34,7 @@ public final class AnalysisToolPak {
                        _functionName = functionName;
                }
 
-               public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet,
-                               int srcCellRow, int srcCellCol) {
+               public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
                        throw new NotImplementedException(_functionName);
                }
        };
index 9235d15b1ce6a42cec1354681c380d5d43ace425..5ab9a70b7bc5ca85181c820a354a9c9c839d979e 100644 (file)
@@ -23,7 +23,7 @@ import org.apache.poi.hssf.record.formula.eval.EvaluationException;
 import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 /**
  * Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
  *
@@ -39,15 +39,14 @@ final class ParityFunction implements FreeRefFunction {
                _desiredParity = desiredParity;
        }
 
-       public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow,
-                       int srcCellCol) {
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
                if (args.length != 1) {
                        return ErrorEval.VALUE_INVALID;
                }
 
                int val;
                try {
-                       val = evaluateArgParity(args[0], srcCellRow, srcCellCol);
+                       val = evaluateArgParity(args[0], ec.getRowIndex(), ec.getColumnIndex());
                } catch (EvaluationException e) {
                        return e.getErrorEval();
                }
index 4b2ba89071d66f4e66b5ce0df717d9a583d61c02..d36b05a36b3c20bc3bc115029dbca7ba1e810a02 100644 (file)
@@ -28,7 +28,7 @@ import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.StringEval;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
 import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 import org.apache.poi.ss.usermodel.DateUtil;
 /**
  * Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
@@ -58,9 +58,9 @@ final class YearFrac implements FreeRefFunction {
                // enforce singleton
        }
 
-       public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow,
-                       int srcCellCol) {
-
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+               int srcCellRow = ec.getRowIndex();
+               int srcCellCol = ec.getColumnIndex();
                double result;
                try {
                        int basis = 0; // default
index 5d04de0fc30f865478bfa89755f2c940dc4b36fd..19476c9f137f1da98047079578be738b00576095 100644 (file)
@@ -20,63 +20,8 @@ package org.apache.poi.hssf.record.formula.eval;
 import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
 import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
 import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
-import org.apache.poi.hssf.record.formula.functions.AggregateFunction;
-import org.apache.poi.hssf.record.formula.functions.And;
-import org.apache.poi.hssf.record.formula.functions.CalendarFieldFunction;
-import org.apache.poi.hssf.record.formula.functions.Choose;
-import org.apache.poi.hssf.record.formula.functions.Column;
-import org.apache.poi.hssf.record.formula.functions.Columns;
-import org.apache.poi.hssf.record.formula.functions.Count;
-import org.apache.poi.hssf.record.formula.functions.Counta;
-import org.apache.poi.hssf.record.formula.functions.Countif;
-import org.apache.poi.hssf.record.formula.functions.DateFunc;
-import org.apache.poi.hssf.record.formula.functions.Errortype;
-import org.apache.poi.hssf.record.formula.functions.Even;
-import org.apache.poi.hssf.record.formula.functions.False;
-import org.apache.poi.hssf.record.formula.functions.FinanceFunction;
-import org.apache.poi.hssf.record.formula.functions.Find;
-import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.hssf.record.formula.functions.Function;
-import org.apache.poi.hssf.record.formula.functions.Hlookup;
-import org.apache.poi.hssf.record.formula.functions.Hyperlink;
-import org.apache.poi.hssf.record.formula.functions.If;
-import org.apache.poi.hssf.record.formula.functions.Index;
-import org.apache.poi.hssf.record.formula.functions.Indirect;
-import org.apache.poi.hssf.record.formula.functions.IsError;
-import org.apache.poi.hssf.record.formula.functions.IsNa;
-import org.apache.poi.hssf.record.formula.functions.Isblank;
-import org.apache.poi.hssf.record.formula.functions.Isref;
-import org.apache.poi.hssf.record.formula.functions.LogicalFunction;
-import org.apache.poi.hssf.record.formula.functions.Lookup;
-import org.apache.poi.hssf.record.formula.functions.Match;
-import org.apache.poi.hssf.record.formula.functions.MinaMaxa;
-import org.apache.poi.hssf.record.formula.functions.Mode;
-import org.apache.poi.hssf.record.formula.functions.Na;
-import org.apache.poi.hssf.record.formula.functions.Not;
-import org.apache.poi.hssf.record.formula.functions.NotImplementedFunction;
-import org.apache.poi.hssf.record.formula.functions.Now;
-import org.apache.poi.hssf.record.formula.functions.NumericFunction;
-import org.apache.poi.hssf.record.formula.functions.Odd;
-import org.apache.poi.hssf.record.formula.functions.Offset;
-import org.apache.poi.hssf.record.formula.functions.Or;
-import org.apache.poi.hssf.record.formula.functions.Pi;
-import org.apache.poi.hssf.record.formula.functions.Rand;
-import org.apache.poi.hssf.record.formula.functions.Replace;
-import org.apache.poi.hssf.record.formula.functions.Row;
-import org.apache.poi.hssf.record.formula.functions.Rows;
-import org.apache.poi.hssf.record.formula.functions.Substitute;
-import org.apache.poi.hssf.record.formula.functions.Sumif;
-import org.apache.poi.hssf.record.formula.functions.Sumproduct;
-import org.apache.poi.hssf.record.formula.functions.Sumx2my2;
-import org.apache.poi.hssf.record.formula.functions.Sumx2py2;
-import org.apache.poi.hssf.record.formula.functions.Sumxmy2;
-import org.apache.poi.hssf.record.formula.functions.T;
-import org.apache.poi.hssf.record.formula.functions.TextFunction;
-import org.apache.poi.hssf.record.formula.functions.Time;
-import org.apache.poi.hssf.record.formula.functions.Today;
-import org.apache.poi.hssf.record.formula.functions.True;
-import org.apache.poi.hssf.record.formula.functions.Value;
-import org.apache.poi.hssf.record.formula.functions.Vlookup;
+import org.apache.poi.hssf.record.formula.functions.*;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 import org.apache.poi.ss.formula.eval.NotImplementedException;
 
 /**
@@ -99,26 +44,10 @@ public final class FunctionEval implements OperationEval {
        // convenient access to namespace
        private static final FunctionID ID = null;
 
-       protected static final Function[] functions = produceFunctions();
-
-
        /**
-        * @return <code>null</code> if specified function
+        * Array elements corresponding to unimplemented functions are <code>null</code>
         */
-       private Function getFunction() {
-               short fidx = getFunctionIndex();
-               return functions[fidx];
-       }
-       public boolean isFreeRefFunction() {
-               return getFreeRefFunction() != null;
-       }
-       public FreeRefFunction getFreeRefFunction() {
-               switch (getFunctionIndex()) {
-                       case FunctionID.INDIRECT: return Indirect.instance;
-                       case FunctionID.EXTERNAL_FUNC: return UserDefinedFunction.instance;
-               }
-               return null;
-       }
+       protected static final Function[] functions = produceFunctions();
 
        private static Function[] produceFunctions() {
                Function[] retval = new Function[368];
@@ -299,19 +228,26 @@ public final class FunctionEval implements OperationEval {
                _delegate = funcPtg;
        }
 
-       public ValueEval evaluate(ValueEval[] operands, int srcRow, short srcCol) {
-               Function f = getFunction();
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+               int fidx = _delegate.getFunctionIndex();
+               // check for 'free ref' functions first
+               switch (fidx) {
+                       case FunctionID.INDIRECT:
+                               return Indirect.instance.evaluate(args, ec);
+                       case FunctionID.EXTERNAL_FUNC:
+                               return UserDefinedFunction.instance.evaluate(args, ec);
+               }
+               // else - must be plain function
+               Function f = functions[fidx];
                if (f == null) {
-                       throw new NotImplementedException("FuncIx=" + getFunctionIndex());
+                       throw new NotImplementedException("FuncIx=" + fidx);
                }
-               return f.evaluate(operands, srcRow, srcCol);
+               int srcCellRow = ec.getRowIndex();
+               int srcCellCol = ec.getColumnIndex();
+               return f.evaluate(args, srcCellRow, (short) srcCellCol);
        }
 
        public int getNumberOfOperands() {
                return _delegate.getNumberOfOperands();
        }
-
-       private short getFunctionIndex() {
-               return _delegate.getFunctionIndex();
-       }
 }
index d66c314d7d7325368b0b086cd08c9943bfede107..481caa0b6b0ff0331c5c05ee39acadbaeea5479c 100644 (file)
@@ -17,6 +17,8 @@
 
 package org.apache.poi.hssf.record.formula.eval;
 
+import org.apache.poi.ss.formula.OperationEvaluationContext;
+
 /**
  * Common interface for implementations of Excel formula operations.
  *
@@ -29,11 +31,10 @@ public interface OperationEval {
         * @param args the evaluated operation arguments. Elements of this array typically implement
         * {@link ValueEval}.  Empty values are represented with {@link BlankEval} or {@link
         * MissingArgEval}, never <code>null</code>.
-        * @param srcRowIndex row index of the cell containing the formula under evaluation
-        * @param srcColumnIndex column index of the cell containing the formula under evaluation
+        * @param ec used to identify the current cell under evaluation, and potentially to
+        * dynamically create references
         * @return The evaluated result, possibly an {@link ErrorEval}, never <code>null</code>.
         */
-       ValueEval evaluate(ValueEval[] args, int srcRowIndex, short srcColumnIndex);
-
+       ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec);
        int getNumberOfOperands();
 }
index 384b576eeca75164e46a9a8870bf7692c16496a5..f12c96e59f0b4cb4922b9cf3d2d77d61a6ed1d8a 100755 (executable)
@@ -20,6 +20,7 @@ package org.apache.poi.hssf.record.formula.eval;
 import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak;
 import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
 import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 import org.apache.poi.ss.formula.eval.NotImplementedException;
 /**
  *
@@ -36,8 +37,7 @@ final class UserDefinedFunction implements FreeRefFunction {
                // enforce singleton
        }
 
-       public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook,
-                       int srcCellSheet, int srcCellRow,int srcCellCol) {
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
 
                int nIncomingArgs = args.length;
                if(nIncomingArgs < 1) {
@@ -49,7 +49,7 @@ final class UserDefinedFunction implements FreeRefFunction {
                if (nameArg instanceof NameEval) {
                        targetFunc = findInternalUserDefinedFunction((NameEval) nameArg);
                } else if (nameArg instanceof NameXEval) {
-                       targetFunc = findExternalUserDefinedFunction(workbook, (NameXEval) nameArg);
+                       targetFunc = findExternalUserDefinedFunction(ec.getWorkbook(), (NameXEval) nameArg);
                } else {
                        throw new RuntimeException("First argument should be a NameEval, but got ("
                                        + nameArg.getClass().getName() + ")");
@@ -57,7 +57,7 @@ final class UserDefinedFunction implements FreeRefFunction {
                int nOutGoingArgs = nIncomingArgs -1;
                ValueEval[] outGoingArgs = new ValueEval[nOutGoingArgs];
                System.arraycopy(args, 1, outGoingArgs, 0, nOutGoingArgs);
-               return targetFunc.evaluate(outGoingArgs, workbook, srcCellSheet, srcCellRow, srcCellCol);
+               return targetFunc.evaluate(outGoingArgs, ec);
        }
 
        private static FreeRefFunction findExternalUserDefinedFunction(EvaluationWorkbook workbook,
index c80647206842958b8f7860e6eeacc132099391eb..119592101d73cdc794b8bbfb83a7f72f90db8f0f 100755 (executable)
@@ -18,7 +18,7 @@
 package org.apache.poi.hssf.record.formula.functions;
 
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 
 
 /**
@@ -28,28 +28,24 @@ import org.apache.poi.ss.formula.EvaluationWorkbook;
  * 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>
+ * When POI evaluates formulas, each reference argument is capable of evaluating any cell inside
+ * its range.  Actually, even cells outside the reference range but on the same sheet can be
+ * evaluated.  This allows <b>OFFSET</b> to be implemented like most other functions - taking only
+ * the arguments, and source cell coordinates.
  *
- * This interface recognises the requirement of some functions to freely create and evaluate
- * references beyond those passed in as arguments.
+ * For the moment this interface only exists to serve the <b>INDIRECT</b> which can decode
+ * arbitrary text into cell references, and evaluate them..
  *
  * @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 srcCellSheet zero based sheet index of the cell containing the currently evaluating formula
-        * @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
+        *             nor are any of its elements.
+        * @param ec primarily used to identify the source cell containing the formula being evaluated.
+        *             may also be used to dynamically create reference evals.
         * @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(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol);
+       ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec);
 }
index d7b031d565e6f2205f4fc48ab0683fd553eb6f23..80d515ac14d1bd323d028ab90d760eecfb2f92d8 100644 (file)
 
 package org.apache.poi.hssf.record.formula.functions;
 
+import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+import org.apache.poi.hssf.record.formula.eval.EvaluationException;
+import org.apache.poi.hssf.record.formula.eval.MissingArgEval;
+import org.apache.poi.hssf.record.formula.eval.OperandResolver;
 import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.ss.formula.EvaluationWorkbook;
-import org.apache.poi.ss.formula.eval.NotImplementedException;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
 
 /**
  * Implementation for Excel function INDIRECT<p/>
@@ -29,11 +33,10 @@ import org.apache.poi.ss.formula.eval.NotImplementedException;
  * <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.
- *
+ * <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
  */
@@ -45,8 +48,192 @@ public final class Indirect implements FreeRefFunction {
                // enforce singleton
        }
 
-       public ValueEval evaluate(ValueEval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) {
-               // TODO - implement INDIRECT()
-               throw new NotImplementedException("INDIRECT");
+       public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+               if (args.length < 1) {
+                       return ErrorEval.VALUE_INVALID;
+               }
+
+               boolean isA1style;
+               String text;
+               try {
+                       ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
+                                       .getColumnIndex());
+                       text = OperandResolver.coerceValueToString(ve);
+                       switch (args.length) {
+                               case 1:
+                                       isA1style = true;
+                                       break;
+                               case 2:
+                                       isA1style = evaluateBooleanArg(args[1], ec);
+                                       break;
+                               default:
+                                       return ErrorEval.VALUE_INVALID;
+                       }
+               } catch (EvaluationException e) {
+                       return e.getErrorEval();
+               }
+
+               return evaluateIndirect(ec, text, isA1style);
+       }
+
+       private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
+                       throws EvaluationException {
+               ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
+
+               if (ve == BlankEval.INSTANCE || ve == MissingArgEval.instance) {
+                       return false;
+               }
+               // numeric quantities follow standard boolean conversion rules
+               // for strings, only "TRUE" and "FALSE" (case insensitive) are valid
+               return OperandResolver.coerceValueToBoolean(ve, false).booleanValue();
+       }
+
+       private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text,
+                       boolean isA1style) {
+               // Search backwards for '!' because sheet names can contain '!'
+               int plingPos = text.lastIndexOf('!');
+
+               String workbookName;
+               String sheetName;
+               String refText; // whitespace around this gets trimmed OK
+               if (plingPos < 0) {
+                       workbookName = null;
+                       sheetName = null;
+                       refText = text;
+               } else {
+                       String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
+                       if (parts == null) {
+                               return ErrorEval.REF_INVALID;
+                       }
+                       workbookName = parts[0];
+                       sheetName = parts[1];
+                       refText = text.substring(plingPos + 1);
+               }
+
+               String refStrPart1;
+               String refStrPart2;
+
+               int colonPos = refText.indexOf(':');
+               if (colonPos < 0) {
+                       refStrPart1 = refText.trim();
+                       refStrPart2 = null;
+               } else {
+                       refStrPart1 = refText.substring(0, colonPos).trim();
+                       refStrPart2 = refText.substring(colonPos + 1).trim();
+               }
+               return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
+       }
+
+       /**
+        * @return array of length 2: {workbookName, sheetName,}.  Second element will always be
+        * present.  First element may be null if sheetName is unqualified.
+        * Returns <code>null</code> if text cannot be parsed.
+        */
+       private static String[] parseWorkbookAndSheetName(CharSequence text) {
+               int lastIx = text.length() - 1;
+               if (lastIx < 0) {
+                       return null;
+               }
+               if (canTrim(text)) {
+                       return null;
+               }
+               char firstChar = text.charAt(0);
+               if (Character.isWhitespace(firstChar)) {
+                       return null;
+               }
+               if (firstChar == '\'') {
+                       // workbookName or sheetName needs quoting
+                       // quotes go around both
+                       if (text.charAt(lastIx) != '\'') {
+                               return null;
+                       }
+                       firstChar = text.charAt(1);
+                       if (Character.isWhitespace(firstChar)) {
+                               return null;
+                       }
+                       String wbName;
+                       int sheetStartPos;
+                       if (firstChar == '[') {
+                               int rbPos = text.toString().lastIndexOf(']');
+                               if (rbPos < 0) {
+                                       return null;
+                               }
+                               wbName = unescapeString(text.subSequence(2, rbPos));
+                               if (wbName == null || canTrim(wbName)) {
+                                       return null;
+                               }
+                               sheetStartPos = rbPos + 1;
+                       } else {
+                               wbName = null;
+                               sheetStartPos = 1;
+                       }
+
+                       // else - just sheet name
+                       String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
+                       if (sheetName == null) { // note - when quoted, sheetName can
+                                                                        // start/end with whitespace
+                               return null;
+                       }
+                       return new String[] { wbName, sheetName, };
+               }
+
+               if (firstChar == '[') {
+                       int rbPos = text.toString().lastIndexOf(']');
+                       if (rbPos < 0) {
+                               return null;
+                       }
+                       CharSequence wbName = text.subSequence(1, rbPos);
+                       if (canTrim(wbName)) {
+                               return null;
+                       }
+                       CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
+                       if (canTrim(sheetName)) {
+                               return null;
+                       }
+                       return new String[] { wbName.toString(), sheetName.toString(), };
+               }
+               // else - just sheet name
+               return new String[] { null, text.toString(), };
+       }
+
+       /**
+        * @return <code>null</code> if there is a syntax error in any escape sequence
+        * (the typical syntax error is a single quote character not followed by another).
+        */
+       private static String unescapeString(CharSequence text) {
+               int len = text.length();
+               StringBuilder sb = new StringBuilder(len);
+               int i = 0;
+               while (i < len) {
+                       char ch = text.charAt(i);
+                       if (ch == '\'') {
+                               // every quote must be followed by another
+                               i++;
+                               if (i >= len) {
+                                       return null;
+                               }
+                               ch = text.charAt(i);
+                               if (ch != '\'') {
+                                       return null;
+                               }
+                       }
+                       sb.append(ch);
+                       i++;
+               }
+               return sb.toString();
+       }
+
+       private static boolean canTrim(CharSequence text) {
+               int lastIx = text.length() - 1;
+               if (lastIx < 0) {
+                       return false;
+               }
+               if (Character.isWhitespace(text.charAt(0))) {
+                       return true;
+               }
+               if (Character.isWhitespace(text.charAt(lastIx))) {
+                       return true;
+               }
+               return false;
        }
 }
index 9a0cd8511f38dc94b3386c55156911865ce34768..21d3fd0c6de41923bb6b9bca27c0997f6bb3c556 100644 (file)
@@ -34,9 +34,15 @@ import java.util.Set;
  * @author Josh Micich
  */
 public final class CollaboratingWorkbooksEnvironment {
-       
+
+       public static final class WorkbookNotFoundException extends Exception {
+               WorkbookNotFoundException(String msg) {
+                       super(msg);
+               }
+       }
+
        public static final CollaboratingWorkbooksEnvironment EMPTY = new CollaboratingWorkbooksEnvironment();
-       
+
        private final Map<String, WorkbookEvaluator> _evaluatorsByName;
        private final WorkbookEvaluator[] _evaluators;
 
@@ -48,7 +54,7 @@ public final class CollaboratingWorkbooksEnvironment {
        public static void setup(String[] workbookNames, WorkbookEvaluator[] evaluators) {
                int nItems = workbookNames.length;
                if (evaluators.length != nItems) {
-                       throw new IllegalArgumentException("Number of workbook names is " + nItems 
+                       throw new IllegalArgumentException("Number of workbook names is " + nItems
                                        + " but number of evaluators is " + evaluators.length);
                }
                if (nItems < 1) {
@@ -82,7 +88,7 @@ public final class CollaboratingWorkbooksEnvironment {
        }
 
        private static void hookNewEnvironment(WorkbookEvaluator[] evaluators, CollaboratingWorkbooksEnvironment env) {
-               
+
                // All evaluators will need to share the same cache.
                // but the cache takes an optional evaluation listener.
                int nItems = evaluators.length;
@@ -95,12 +101,15 @@ public final class CollaboratingWorkbooksEnvironment {
                        }
                }
                EvaluationCache cache = new EvaluationCache(evalListener);
-               
+
                for(int i=0; i<nItems; i++) {
                        evaluators[i].attachToEnvironment(env, cache, i);
                }
-               
        }
+
+       /**
+        * Completely dismantles all workbook environments that the supplied evaluators are part of
+        */
        private void unhookOldEnvironments(WorkbookEvaluator[] evaluators) {
                Set<CollaboratingWorkbooksEnvironment> oldEnvs = new HashSet<CollaboratingWorkbooksEnvironment>();
                for(int i=0; i<evaluators.length; i++) {
@@ -114,10 +123,11 @@ public final class CollaboratingWorkbooksEnvironment {
        }
 
        /**
-        * 
+        * Tell all contained evaluators that this environment should be discarded
         */
        private void unhook() {
                if (_evaluators.length < 1) {
+                       // Never dismantle the EMPTY environment
                        return;
                }
                for (int i = 0; i < _evaluators.length; i++) {
@@ -126,7 +136,7 @@ public final class CollaboratingWorkbooksEnvironment {
                _unhooked = true;
        }
 
-       public WorkbookEvaluator getWorkbookEvaluator(String workbookName) {
+       public WorkbookEvaluator getWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException {
                if (_unhooked) {
                        throw new IllegalStateException("This environment has been unhooked");
                }
@@ -148,7 +158,7 @@ public final class CollaboratingWorkbooksEnvironment {
                                }
                                sb.append(")");
                        }
-                       throw new RuntimeException(sb.toString());
+                       throw new WorkbookNotFoundException(sb.toString());
                }
                return result;
        }
index 28f31eb497331888149fe82fe84c507a476185d1..a3b232571945a15d7fe7d64e8fad37ecdd1d77c4 100644 (file)
@@ -34,6 +34,10 @@ public interface EvaluationWorkbook {
         * @return -1 if the specified sheet is from a different book
         */
        int getSheetIndex(EvaluationSheet sheet);
+       /**
+        * Finds a sheet index by case insensitive name.
+        * @return the index of the sheet matching the specified name.  -1 if not found
+        */
        int getSheetIndex(String sheetName);
 
        EvaluationSheet getSheet(int sheetIndex);
index 69f0834f50e13469ebf74452538081f887cee890..2a97c67c4818dbe5e0b38a2ddd0e669d878d9973 100644 (file)
@@ -34,13 +34,18 @@ final class LazyRefEval extends RefEvalBase {
 
        private final SheetRefEvaluator _evaluator;
 
-       public LazyRefEval(RefPtg ptg, SheetRefEvaluator sre) {
-               super(ptg.getRow(), ptg.getColumn());
+       public LazyRefEval(int rowIndex, int columnIndex, SheetRefEvaluator sre) {
+               super(rowIndex, columnIndex);
+               if (sre == null) {
+                       throw new IllegalArgumentException("sre must not be null");
+               }
                _evaluator = sre;
        }
+       public LazyRefEval(RefPtg ptg, SheetRefEvaluator sre) {
+               this(ptg.getRow(), ptg.getColumn(), sre);
+       }
        public LazyRefEval(Ref3DPtg ptg, SheetRefEvaluator sre) {
-               super(ptg.getRow(), ptg.getColumn());
-               _evaluator = sre;
+               this(ptg.getRow(), ptg.getColumn(), sre);
        }
 
        public ValueEval getInnerValueEval() {
diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java
new file mode 100644 (file)
index 0000000..2ec5f57
--- /dev/null
@@ -0,0 +1,259 @@
+/* ====================================================================
+   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.ss.formula;
+
+import org.apache.poi.hssf.record.formula.AreaI;
+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.RefEval;
+import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.SpreadsheetVersion;
+import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
+import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
+import org.apache.poi.ss.util.CellReference;
+import org.apache.poi.ss.util.CellReference.NameType;
+
+/**
+ * Contains all the contextual information required to evaluate an operation
+ * within a formula
+ *
+ * For POI internal use only
+ *
+ * @author Josh Micich
+ */
+public final class OperationEvaluationContext {
+
+       private final EvaluationWorkbook _workbook;
+       private final int _sheetIndex;
+       private final int _rowIndex;
+       private final int _columnIndex;
+       private final EvaluationTracker _tracker;
+       private final WorkbookEvaluator _bookEvaluator;
+
+       public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
+                       int srcColNum, EvaluationTracker tracker) {
+               _bookEvaluator = bookEvaluator;
+               _workbook = workbook;
+               _sheetIndex = sheetIndex;
+               _rowIndex = srcRowNum;
+               _columnIndex = srcColNum;
+               _tracker = tracker;
+       }
+
+       public EvaluationWorkbook getWorkbook() {
+               return _workbook;
+       }
+
+       public int getRowIndex() {
+               return _rowIndex;
+       }
+
+       public int getColumnIndex() {
+               return _columnIndex;
+       }
+
+       /* package */ SheetRefEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
+               int externSheetIndex = ptg.getExternSheetIndex();
+               ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
+               WorkbookEvaluator targetEvaluator;
+               int otherSheetIndex;
+               if (externalSheet == null) {
+                       // sheet is in same workbook
+                       otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
+                       targetEvaluator = _bookEvaluator;
+               } else {
+                       // look up sheet by name from external workbook
+                       String workbookName = externalSheet.getWorkbookName();
+                       try {
+                               targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
+                       } catch (WorkbookNotFoundException e) {
+                               throw new RuntimeException(e.getMessage());
+                       }
+                       otherSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
+                       if (otherSheetIndex < 0) {
+                               throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName()
+                                               + "' in bool '" + workbookName + "'.");
+                       }
+               }
+               return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
+       }
+
+       /**
+        * @return <code>null</code> if either workbook or sheet is not found
+        */
+       private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) {
+               WorkbookEvaluator targetEvaluator;
+               if (workbookName == null) {
+                       targetEvaluator = _bookEvaluator;
+               } else {
+                       if (sheetName == null) {
+                               throw new IllegalArgumentException("sheetName must not be null if workbookName is provided");
+                       }
+                       try {
+                               targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
+                       } catch (WorkbookNotFoundException e) {
+                               return null;
+                       }
+               }
+               int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName);
+               if (otherSheetIndex < 0) {
+                       return null;
+               }
+               return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
+       }
+
+       public SheetRefEvaluator getRefEvaluatorForCurrentSheet() {
+               return new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex);
+       }
+
+
+
+       /**
+        * Resolves a cell or area reference dynamically.
+        * @param workbookName the name of the workbook containing the reference.  If <code>null</code>
+        * the current workbook is assumed.  Note - to evaluate formulas which use multiple workbooks,
+        * a {@link CollaboratingWorkbooksEnvironment} must be set up.
+        * @param sheetName the name of the sheet containing the reference.  May be <code>null</code>
+        * (when <tt>workbookName</tt> is also null) in which case the current workbook and sheet is
+        * assumed.
+        * @param refStrPart1 the single cell reference or first part of the area reference.  Must not
+        * be <code>null</code>.
+        * @param refStrPart2 the second part of the area reference. For single cell references this
+        * parameter must be <code>null</code>
+        * @param isA1Style specifies the format for <tt>refStrPart1</tt> and <tt>refStrPart2</tt>.
+        * Pass <code>true</code> for 'A1' style and <code>false</code> for 'R1C1' style.
+        * TODO - currently POI only supports 'A1' reference style
+        * @return a {@link RefEval} or {@link AreaEval}
+        */
+       public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1,
+                       String refStrPart2, boolean isA1Style) {
+               if (!isA1Style) {
+                       throw new RuntimeException("R1C1 style not supported yet");
+               }
+               SheetRefEvaluator sre = createExternSheetRefEvaluator(workbookName, sheetName);
+               if (sre == null) {
+                       return ErrorEval.REF_INVALID;
+               }
+               // ugly typecast - TODO - make spreadsheet version more easily accessible
+               SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion();
+
+               NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
+               switch (part1refType) {
+                       case BAD_CELL_OR_NAMED_RANGE:
+                               return ErrorEval.REF_INVALID;
+                       case NAMED_RANGE:
+                               throw new RuntimeException("Cannot evaluate '" + refStrPart1
+                                               + "'. Indirect evaluation of defined names not supported yet");
+               }
+               if (refStrPart2 == null) {
+                       // no ':'
+                       switch (part1refType) {
+                               case COLUMN:
+                               case ROW:
+                                       return ErrorEval.REF_INVALID;
+                               case CELL:
+                                       CellReference cr = new CellReference(refStrPart1);
+                                       return new LazyRefEval(cr.getRow(), cr.getCol(), sre);
+                       }
+                       throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
+               }
+               NameType part2refType = classifyCellReference(refStrPart1, ssVersion);
+               switch (part2refType) {
+                       case BAD_CELL_OR_NAMED_RANGE:
+                               return ErrorEval.REF_INVALID;
+                       case NAMED_RANGE:
+                               throw new RuntimeException("Cannot evaluate '" + refStrPart1
+                                               + "'. Indirect evaluation of defined names not supported yet");
+               }
+
+               if (part2refType != part1refType) {
+                       // LHS and RHS of ':' must be compatible
+                       return ErrorEval.REF_INVALID;
+               }
+               int firstRow, firstCol, lastRow, lastCol;
+               switch (part1refType) {
+                       case COLUMN:
+                               firstRow =0;
+                               lastRow = ssVersion.getLastRowIndex();
+                               firstCol = parseColRef(refStrPart1);
+                               lastCol = parseColRef(refStrPart2);
+                               break;
+                       case ROW:
+                               firstCol = 0;
+                               lastCol = ssVersion.getLastColumnIndex();
+                               firstRow = parseRowRef(refStrPart1);
+                               lastRow = parseRowRef(refStrPart2);
+                               break;
+                       case CELL:
+                               CellReference cr;
+                               cr = new CellReference(refStrPart1);
+                               firstRow = cr.getRow();
+                               firstCol = cr.getCol();
+                               cr = new CellReference(refStrPart2);
+                               lastRow = cr.getRow();
+                               lastCol = cr.getCol();
+                               break;
+                       default:
+                               throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
+               }
+               return new LazyAreaEval(new AI(firstRow, firstCol, lastRow, lastCol), sre);
+       }
+
+       private static int parseRowRef(String refStrPart) {
+               return CellReference.convertColStringToIndex(refStrPart);
+       }
+
+       private static int parseColRef(String refStrPart) {
+               return Integer.parseInt(refStrPart) - 1;
+       }
+
+       private static final class AI implements AreaI {
+
+               private final int _fr;
+               private final int _lr;
+               private final int _fc;
+               private final int _lc;
+
+               public AI(int fr, int fc, int lr, int lc) {
+                       _fr = Math.min(fr, lr);
+                       _lr = Math.max(fr, lr);
+                       _fc = Math.min(fc, lc);
+                       _lc = Math.max(fc, lc);
+               }
+               public int getFirstColumn() {
+                       return _fc;
+               }
+               public int getFirstRow() {
+                       return _fr;
+               }
+               public int getLastColumn() {
+                       return _lc;
+               }
+               public int getLastRow() {
+                       return _lr;
+               }
+       }
+
+       private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) {
+               int len = str.length();
+               if (len < 1) {
+                       return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE;
+               }
+               return CellReference.classifyCellReference(str, ssVersion);
+       }
+}
index b459b4c99c6bdc800914c283fc91a236b10ab3e6..449c0b18c6dd06903e650a36a6640d47a1c4dbd4 100755 (executable)
@@ -106,8 +106,8 @@ final class OperationEvaluatorFactory {
                        _numberOfOperands = argCount;
                }
 
-               public ValueEval evaluate(ValueEval[] args, int rowIndex, short columnIndex) {
-                       return _function.evaluate(args, rowIndex, columnIndex);
+               public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+                       return _function.evaluate(args, ec.getRowIndex(), (short) ec.getColumnIndex());
                }
 
                public int getNumberOfOperands() {
index b2a6ed123db71eb0b8dd5959a56cd45511d8f285..97568cfec4beb6f5bcf16f8855755b594d10cd31 100644 (file)
@@ -27,14 +27,15 @@ final class SheetRefEvaluator {
 
        private final WorkbookEvaluator _bookEvaluator;
        private final EvaluationTracker _tracker;
-       private final EvaluationSheet _sheet;
        private final int _sheetIndex;
+       private EvaluationSheet _sheet;
 
-       public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker,
-                       EvaluationWorkbook _workbook, int sheetIndex) {
+       public SheetRefEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker, int sheetIndex) {
+               if (sheetIndex < 0) {
+                       throw new IllegalArgumentException("Invalid sheetIndex: " + sheetIndex + ".");
+               }
                _bookEvaluator = bookEvaluator;
                _tracker = tracker;
-               _sheet = _workbook.getSheet(sheetIndex);
                _sheetIndex = sheetIndex;
        }
 
@@ -43,6 +44,13 @@ final class SheetRefEvaluator {
        }
 
        public ValueEval getEvalForCell(int rowIndex, int columnIndex) {
-               return _bookEvaluator.evaluateReference(_sheet, _sheetIndex, rowIndex, columnIndex, _tracker);
+               return _bookEvaluator.evaluateReference(getSheet(), _sheetIndex, rowIndex, columnIndex, _tracker);
+       }
+
+       private EvaluationSheet getSheet() {
+               if (_sheet == null) {
+                       _sheet = _bookEvaluator.getSheet(_sheetIndex);
+               }
+               return _sheet;
        }
 }
index abb925713edec000abc3fe863a1bee97da9ddb3e..806c002bb199737083fad3608c4fcc9082b407f8 100644 (file)
@@ -51,7 +51,6 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval;
 import org.apache.poi.hssf.record.formula.eval.BlankEval;
 import org.apache.poi.hssf.record.formula.eval.BoolEval;
 import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.FunctionEval;
 import org.apache.poi.hssf.record.formula.eval.MissingArgEval;
 import org.apache.poi.hssf.record.formula.eval.NameEval;
 import org.apache.poi.hssf.record.formula.eval.NameXEval;
@@ -61,7 +60,7 @@ 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.CellReference;
-import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
+import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
 import org.apache.poi.ss.formula.eval.NotImplementedException;
 import org.apache.poi.ss.usermodel.Cell;
 
@@ -80,10 +79,12 @@ public final class WorkbookEvaluator {
 
        private final EvaluationWorkbook _workbook;
        private EvaluationCache _cache;
+       /** part of cache entry key (useful when evaluating multiple workbooks) */
        private int _workbookIx;
 
        private final IEvaluationListener _evaluationListener;
        private final Map<EvaluationSheet, Integer> _sheetIndexesBySheet;
+       private final Map<String, Integer> _sheetIndexesByName;
        private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
        private final IStabilityClassifier _stabilityClassifier;
 
@@ -96,6 +97,7 @@ public final class WorkbookEvaluator {
                _evaluationListener = evaluationListener;
                _cache = new EvaluationCache(evaluationListener);
                _sheetIndexesBySheet = new IdentityHashMap<EvaluationSheet, Integer>();
+               _sheetIndexesByName = new IdentityHashMap<String, Integer>();
                _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
                _workbookIx = 0;
                _stabilityClassifier = stabilityClassifier;
@@ -108,6 +110,10 @@ public final class WorkbookEvaluator {
                return _workbook.getSheetName(sheetIndex);
        }
 
+       /* package */ EvaluationSheet getSheet(int sheetIndex) {
+               return _workbook.getSheet(sheetIndex);
+       }
+
        private static boolean isDebugLogEnabled() {
                return false;
        }
@@ -125,11 +131,22 @@ public final class WorkbookEvaluator {
                return _collaboratingWorkbookEnvironment;
        }
 
+       /**
+        * Discards the current workbook environment and attaches to the default 'empty' environment.
+        * Also resets evaluation cache.
+        */
        /* package */ void detachFromEnvironment() {
                _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
                _cache = new EvaluationCache(_evaluationListener);
                _workbookIx = 0;
        }
+       /**
+        * @return the evaluator for another workbook which is part of the same {@link CollaboratingWorkbooksEnvironment}
+        */
+       /* package */ WorkbookEvaluator getOtherWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException {
+               return _collaboratingWorkbookEnvironment.getWorkbookEvaluator(workbookName);
+       }
+
        /* package */ IEvaluationListener getEvaluationListener() {
                return _evaluationListener;
        }
@@ -179,6 +196,23 @@ public final class WorkbookEvaluator {
                return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache));
        }
 
+       /**
+        * Case-insensitive.
+        * @return -1 if sheet with specified name does not exist
+        */
+       /* package */ int getSheetIndex(String sheetName) {
+               Integer result = _sheetIndexesByName.get(sheetName);
+               if (result == null) {
+                       int sheetIndex = _workbook.getSheetIndex(sheetName);
+                       if (sheetIndex < 0) {
+                               return -1;
+                       }
+                       result = new Integer(sheetIndex);
+                       _sheetIndexesByName.put(sheetName, result);
+               }
+               return result.intValue();
+       }
+
 
        /**
         * @return never <code>null</code>, never {@link BlankEval}
@@ -207,15 +241,16 @@ public final class WorkbookEvaluator {
                        if (!tracker.startEvaluate(cce)) {
                                return ErrorEval.CIRCULAR_REF_ERROR;
                        }
+                       OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker);
 
                        try {
 
                                Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
                                if (evalListener == null) {
-                                       result = evaluateFormula(sheetIndex, rowIndex, columnIndex, ptgs, tracker);
+                                       result = evaluateFormula(ec, ptgs);
                                } else {
                                        evalListener.onStartEvaluate(srcCell, cce, ptgs);
-                                       result = evaluateFormula(sheetIndex, rowIndex, columnIndex, ptgs, tracker);
+                                       result = evaluateFormula(ec, ptgs);
                                        evalListener.onEndEvaluate(cce, result);
                                }
 
@@ -286,7 +321,7 @@ public final class WorkbookEvaluator {
                throw new RuntimeException("Unexpected cell type (" + cellType + ")");
        }
        // visibility raised for testing
-       /* package */ ValueEval evaluateFormula(int sheetIndex, int srcRowNum, int srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
+       /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) {
 
                Stack<ValueEval> stack = new Stack<ValueEval>();
                for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
@@ -329,12 +364,12 @@ public final class WorkbookEvaluator {
                                        ops[j] = p;
                                }
 //                             logDebug("invoke " + operation + " (nAgs=" + numops + ")");
-                               opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum);
+                               opResult = operation.evaluate(ops, ec);
                                if (opResult == MissingArgEval.instance) {
                                        opResult = BlankEval.INSTANCE;
                                }
                        } else {
-                               opResult = getEvalForPtg(ptg, sheetIndex, tracker);
+                               opResult = getEvalForPtg(ptg, ec);
                        }
                        if (opResult == null) {
                                throw new RuntimeException("Evaluation result must not be null");
@@ -347,7 +382,7 @@ public final class WorkbookEvaluator {
                if (!stack.isEmpty()) {
                        throw new IllegalStateException("evaluation stack not empty");
                }
-               value = dereferenceValue(value, srcRowNum, srcColNum);
+               value = dereferenceValue(value, ec.getRowIndex(), ec.getColumnIndex());
                if (value == BlankEval.INSTANCE) {
                        // Note Excel behaviour here. A blank final final value is converted to zero.
                        return NumberEval.ZERO;
@@ -384,31 +419,6 @@ public final class WorkbookEvaluator {
                return evaluationResult;
        }
 
-       private static ValueEval invokeOperation(OperationEval operation, ValueEval[] ops,
-                       EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
-
-               if(operation instanceof FunctionEval) {
-                       FunctionEval fe = (FunctionEval) operation;
-                       if(fe.isFreeRefFunction()) {
-                               return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum);
-                       }
-               }
-               return operation.evaluate(ops, srcRowNum, (short)srcColNum);
-       }
-       private SheetRefEvaluator createExternSheetRefEvaluator(EvaluationTracker tracker,
-                       ExternSheetReferenceToken ptg) {
-               int externSheetIndex = ptg.getExternSheetIndex();
-               ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
-               if (externalSheet != null) {
-                       WorkbookEvaluator otherEvaluator = _collaboratingWorkbookEnvironment.getWorkbookEvaluator(externalSheet.getWorkbookName());
-                       EvaluationWorkbook otherBook = otherEvaluator._workbook;
-                       int otherSheetIndex = otherBook.getSheetIndex(externalSheet.getSheetName());
-                       return new SheetRefEvaluator(otherEvaluator, tracker, otherBook, otherSheetIndex);
-               }
-               int otherSheetIndex = _workbook.convertFromExternSheetIndex(externSheetIndex);
-               return new SheetRefEvaluator(this, tracker, _workbook, otherSheetIndex);
-
-       }
 
        /**
         * returns an appropriate Eval impl instance for the Ptg. The Ptg must be
@@ -416,7 +426,7 @@ public final class WorkbookEvaluator {
         * StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be
         * passed here!
         */
-       private ValueEval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
+       private ValueEval getEvalForPtg(Ptg ptg, OperationEvaluationContext ec) {
                //  consider converting all these (ptg instanceof XxxPtg) expressions to (ptg.getClass() == XxxPtg.class)
 
                if (ptg instanceof NamePtg) {
@@ -427,7 +437,7 @@ public final class WorkbookEvaluator {
                                return new NameEval(nameRecord.getNameText());
                        }
                        if (nameRecord.hasFormula()) {
-                               return evaluateNameFormula(nameRecord.getNameDefinition(), sheetIndex, tracker);
+                               return evaluateNameFormula(nameRecord.getNameDefinition(), ec);
                        }
 
                        throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'");
@@ -460,15 +470,15 @@ public final class WorkbookEvaluator {
                }
                if (ptg instanceof Ref3DPtg) {
                        Ref3DPtg refPtg = (Ref3DPtg) ptg;
-                       SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, refPtg);
+                       SheetRefEvaluator sre = ec.createExternSheetRefEvaluator(refPtg);
                        return new LazyRefEval(refPtg, sre);
                }
                if (ptg instanceof Area3DPtg) {
                        Area3DPtg aptg = (Area3DPtg) ptg;
-                       SheetRefEvaluator sre = createExternSheetRefEvaluator(tracker, aptg);
+                       SheetRefEvaluator sre = ec.createExternSheetRefEvaluator(aptg);
                        return new LazyAreaEval(aptg, sre);
                }
-               SheetRefEvaluator sre = new SheetRefEvaluator(this, tracker, _workbook, sheetIndex);
+               SheetRefEvaluator sre = ec.getRefEvaluatorForCurrentSheet();
                if (ptg instanceof RefPtg) {
                        return new LazyRefEval(((RefPtg) ptg), sre);
                }
@@ -490,11 +500,11 @@ public final class WorkbookEvaluator {
 
                throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")");
        }
-       private ValueEval evaluateNameFormula(Ptg[] ptgs, int sheetIndex, EvaluationTracker tracker) {
+       private ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) {
                if (ptgs.length > 1) {
                        throw new RuntimeException("Complex name formulas not supported yet");
                }
-               return getEvalForPtg(ptgs[0], sheetIndex, tracker);
+               return getEvalForPtg(ptgs[0], ec);
        }
 
        /**
index 78ef47cfc6972a175222edc40663db2bd328c531..34505dc60129f79acf9b137b2910f3da77df3ddc 100644 (file)
@@ -89,7 +89,7 @@ public final class TestEqualEval extends TestCase {
                        new StringEval(a),
                        new StringEval(b),
                };
-               ValueEval result = cmpOp.evaluate(args, 10, (short)20);
+               ValueEval result = evaluate(cmpOp, args, 10, 20);
                assertEquals(BoolEval.class, result.getClass());
                BoolEval be = (BoolEval) result;
                return be.getBooleanValue();
index afda62b332c4182182e084a13ee844f6c90201db..dcf53132ecfe6d61bc54b77f6c897b7d4036d804 100644 (file)
@@ -64,7 +64,7 @@ public final class TestRangeEval extends TestCase {
                        createRefEval(refB),
                };
                AreaReference ar = new AreaReference(expectedAreaRef);
-               ValueEval result = RangeEval.instance.evaluate(args, 0, (short)0);
+               ValueEval result = EvalInstances.Range.evaluate(args, 0, (short)0);
                assertTrue(result instanceof AreaEval);
                AreaEval ae = (AreaEval) result;
                assertEquals(ar.getFirstCell().getRow(), ae.getFirstRow());
index 6a73dd637e5f8febe969abaafc1bb86f06d8f940..ffa42b3339e8511912ffb2aba0592ff17ffd4d44 100755 (executable)
@@ -51,7 +51,7 @@ public final class TestUnaryPlusEval extends TestCase {
                        EvalFactory.createAreaEval(areaPtg, values),
                };
 
-               double result = NumericFunctionInvoker.invoke(UnaryPlusEval.instance, args, 10, (short)20);
+               double result = NumericFunctionInvoker.invoke(EvalInstances.UnaryPlus, args, 10, (short)20);
 
                assertEquals(35, result, 0);
        }
diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndirect.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestIndirect.java
new file mode 100644 (file)
index 0000000..50c2067
--- /dev/null
@@ -0,0 +1,182 @@
+/* ====================================================================
+   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.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
+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.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellValue;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+
+/**
+ * Tests for the INDIRECT() function.</p>
+ *
+ * @author Josh Micich
+ */
+public final class TestIndirect extends TestCase {
+       // convenient access to namespace
+       private static final ErrorEval EE = null;
+
+       private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) {
+               HSSFRow row = sheet.createRow(rowIndex);
+               for (int i = 0; i < vals.length; i++) {
+                       row.createCell(i).setCellValue(vals[i]);
+               }
+       }
+
+       private static HSSFWorkbook createWBA() {
+               HSSFWorkbook wb = new HSSFWorkbook();
+               HSSFSheet sheet1 = wb.createSheet("Sheet1");
+               HSSFSheet sheet2 = wb.createSheet("Sheet2");
+               HSSFSheet sheet3 = wb.createSheet("John's sales");
+
+               createDataRow(sheet1, 0, 11, 12, 13, 14);
+               createDataRow(sheet1, 1, 21, 22, 23, 24);
+               createDataRow(sheet1, 2, 31, 32, 33, 34);
+
+               createDataRow(sheet2, 0, 50, 55, 60, 65);
+               createDataRow(sheet2, 1, 51, 56, 61, 66);
+               createDataRow(sheet2, 2, 52, 57, 62, 67);
+
+               createDataRow(sheet3, 0, 30, 31, 32);
+               createDataRow(sheet3, 1, 33, 34, 35);
+               return wb;
+       }
+
+       private static HSSFWorkbook createWBB() {
+               HSSFWorkbook wb = new HSSFWorkbook();
+               HSSFSheet sheet1 = wb.createSheet("Sheet1");
+               HSSFSheet sheet2 = wb.createSheet("Sheet2");
+               HSSFSheet sheet3 = wb.createSheet("## Look here!");
+
+               createDataRow(sheet1, 0, 400, 440, 480, 520);
+               createDataRow(sheet1, 1, 420, 460, 500, 540);
+
+               createDataRow(sheet2, 0, 50, 55, 60, 65);
+               createDataRow(sheet2, 1, 51, 56, 61, 66);
+
+               createDataRow(sheet3, 0, 42);
+
+               return wb;
+       }
+
+       public void testBasic() {
+
+               HSSFWorkbook wbA = createWBA();
+               HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2);
+               HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
+
+               // non-error cases
+               confirm(feA, c, "INDIRECT(\"C2\")", 23);
+               confirm(feA, c, "INDIRECT(\"$C2\")", 23);
+               confirm(feA, c, "INDIRECT(\"C$2\")", 23);
+               confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref
+               confirm(feA, c, "SUM(INDIRECT(\"Sheet2! B1 : C3 \"))", 351); // spaces in area ref
+               confirm(feA, c, "SUM(INDIRECT(\"'John''s sales'!A1:C1\"))", 93); // special chars in sheet name
+               confirm(feA, c, "INDIRECT(\"'Sheet1'!B3\")", 32); // redundant sheet name quotes
+               confirm(feA, c, "INDIRECT(\"sHeet1!B3\")", 32); // case-insensitive sheet name
+               confirm(feA, c, "INDIRECT(\" D3 \")", 34); // spaces around cell ref
+               confirm(feA, c, "INDIRECT(\"Sheet1! D3 \")", 34); // spaces around cell ref
+               confirm(feA, c, "INDIRECT(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far
+
+               confirm(feA, c, "INDIRECT(\"A1:G1\")", 13); // de-reference area ref (note formula is in C4)
+
+
+               // simple error propagation:
+
+               // arg0 is evaluated to text first
+               confirm(feA, c, "INDIRECT(#DIV/0!)", EE.DIV_ZERO);
+               confirm(feA, c, "INDIRECT(#DIV/0!)", EE.DIV_ZERO);
+               confirm(feA, c, "INDIRECT(#NAME?, \"x\")", EE.NAME_INVALID);
+               confirm(feA, c, "INDIRECT(#NUM!, #N/A)", EE.NUM_ERROR);
+
+               // arg1 is evaluated to boolean before arg0 is decoded
+               confirm(feA, c, "INDIRECT(\"garbage\", #N/A)", EE.NA);
+               confirm(feA, c, "INDIRECT(\"garbage\", \"\")", EE.VALUE_INVALID); // empty string is not valid boolean
+               confirm(feA, c, "INDIRECT(\"garbage\", \"flase\")", EE.VALUE_INVALID); // must be "TRUE" or "FALSE"
+
+
+               // spaces around sheet name (with or without quotes makes no difference)
+               confirm(feA, c, "INDIRECT(\"'Sheet1 '!D3\")", EE.REF_INVALID);
+               confirm(feA, c, "INDIRECT(\" Sheet1!D3\")", EE.REF_INVALID);
+               confirm(feA, c, "INDIRECT(\"'Sheet1' !D3\")", EE.REF_INVALID);
+
+
+               confirm(feA, c, "SUM(INDIRECT(\"'John's sales'!A1:C1\"))", EE.REF_INVALID); // bad quote escaping
+               confirm(feA, c, "INDIRECT(\"[Book1]Sheet1!A1\")", EE.REF_INVALID); // unknown external workbook
+               confirm(feA, c, "INDIRECT(\"Sheet3!A1\")", EE.REF_INVALID); // unknown sheet
+               if (false) { // TODO - support evaluation of defined names
+                       confirm(feA, c, "INDIRECT(\"Sheet1!IW1\")", EE.REF_INVALID); // bad column
+                       confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", EE.REF_INVALID); // bad row
+               }
+               confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", EE.REF_INVALID); // space in cell ref
+       }
+
+       public void testMultipleWorkbooks() {
+               HSSFWorkbook wbA = createWBA();
+               HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0);
+               HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
+
+               HSSFWorkbook wbB = createWBB();
+               HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0);
+               HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB);
+
+               String[] workbookNames = { "MyBook", "Figures for January", };
+               HSSFFormulaEvaluator[] evaluators = { feA, feB, };
+               HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators);
+
+               confirm(feB, cellB, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // same wb
+               confirm(feA, cellA, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // across workbooks
+
+               // 2 level recursion
+               confirm(feB, cellB, "INDIRECT(\"[MyBook]Sheet2!A1\")", 50); // set up (and check) first level
+               confirm(feA, cellA, "INDIRECT(\"'[Figures for January]Sheet1'!A11\")", 50); // points to cellB
+       }
+
+       private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
+                       double expectedResult) {
+               fe.clearAllCachedResultValues();
+               cell.setCellFormula(formula);
+               CellValue cv = fe.evaluate(cell);
+               if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
+                       throw new AssertionFailedError("expected numeric cell type but got " + cv.formatAsString());
+               }
+               assertEquals(expectedResult, cv.getNumberValue(), 0.0);
+       }
+       private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
+                       ErrorEval expectedResult) {
+               fe.clearAllCachedResultValues();
+               cell.setCellFormula(formula);
+               CellValue cv = fe.evaluate(cell);
+               if (cv.getCellType() != Cell.CELL_TYPE_ERROR) {
+                       throw new AssertionFailedError("expected error cell type but got " + cv.formatAsString());
+               }
+               int expCode = expectedResult.getErrorCode();
+               if (cv.getErrorValue() != expCode) {
+                       throw new AssertionFailedError("Expected error '" + EE.getText(expCode)
+                                       + "' but got '" + cv.formatAsString() + "'.");
+               }
+       }
+}
index 81dad57d16d93119e0a9a3d0b6fbbccae6fca9fc..9035ecbedbdbcc3f32e71e12f1b8a568c6e4b67c 100644 (file)
@@ -42,8 +42,9 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  */
 public class TestWorkbookEvaluator extends TestCase {
 
-       private static WorkbookEvaluator createEvaluator() {
-               return new WorkbookEvaluator(null, null);
+       private static ValueEval evaluateFormula(Ptg[] ptgs) {
+               OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 0, 0, 0, null);
+               return new WorkbookEvaluator(null, null).evaluateFormula(ec, ptgs);
        }
 
        /**
@@ -57,7 +58,7 @@ public class TestWorkbookEvaluator extends TestCase {
                        AttrPtg.SUM,
                };
 
-               ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
+               ValueEval result = evaluateFormula(ptgs);
                assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
        }
 
@@ -78,7 +79,7 @@ public class TestWorkbookEvaluator extends TestCase {
                        ptg,
                };
 
-               ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
+               ValueEval result = evaluateFormula(ptgs);
                assertEquals(ErrorEval.REF_INVALID, result);
        }
 
@@ -93,7 +94,7 @@ public class TestWorkbookEvaluator extends TestCase {
                        AttrPtg.SUM,
                };
 
-               ValueEval result = createEvaluator().evaluateFormula(0, 0, 0, ptgs, null);
+               ValueEval result = evaluateFormula(ptgs);
                assertEquals(42, ((NumberEval)result).getNumberValue(), 0.0);
        }