]> source.dussan.org Git - poi.git/commitdiff
Made HSSFFormulaEvaluator capable of handling simple named ranges
authorJosh Micich <josh@apache.org>
Thu, 4 Sep 2008 21:32:17 +0000 (21:32 +0000)
committerJosh Micich <josh@apache.org>
Thu, 4 Sep 2008 21:32:17 +0000 (21:32 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@692255 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hssf/record/NameRecord.java
src/java/org/apache/poi/hssf/record/formula/eval/ExternalFunction.java
src/java/org/apache/poi/hssf/record/formula/eval/NameEval.java
src/java/org/apache/poi/hssf/usermodel/HSSFFormulaEvaluator.java
src/testcases/org/apache/poi/hssf/data/testNames.xls
src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java [new file with mode: 0644]

index bc2c53586961d26bceef2e940cbc633a26f7f0bf..b2cc897b52e0dda72c1981a3f8c55eca930ee187 100644 (file)
@@ -243,12 +243,18 @@ public final class NameRecord extends Record {
                }
        }
        /**
-        * @return true if name is a function
+        * @return <code>true</code> if name is a function
         */
        public boolean isFunctionName() {
                return (field_1_option_flag & Option.OPT_FUNCTION_NAME) != 0;
        }
 
+       /**
+        * @return <code>true</code> if name has a formula (named range or defined value)
+        */
+       public boolean hasFormula() {
+               return field_1_option_flag == 0 && field_13_name_definition.length > 0;
+       }
 
        /**
         * @return true if name is a command
index 6959d146f984eb61547d017a2585552a95733bcf..8abab17a5b6e49f8ec8bab8f72ff3b8381c0a66b 100755 (executable)
@@ -42,7 +42,7 @@ final class ExternalFunction implements FreeRefFunction {
                FreeRefFunction targetFunc;
                try {
                        if (nameArg instanceof NameEval) {
-                               targetFunc = findInternalUserDefinedFunction(workbook, (NameEval) nameArg);
+                               targetFunc = findInternalUserDefinedFunction((NameEval) nameArg);
                        } else if (nameArg instanceof NameXEval) {
                                targetFunc = findExternalUserDefinedFunction(workbook, (NameXEval) nameArg);
                        } else {
@@ -65,7 +65,7 @@ final class ExternalFunction implements FreeRefFunction {
                if(false) {
                        System.out.println("received call to external user defined function (" + functionName + ")");
                }
-               // currently only looking for functions from the 'Analysis TookPak'
+               // currently only looking for functions from the 'Analysis TookPak'  e.g. "YEARFRAC" or "ISEVEN"
                // not sure how much this logic would need to change to support other or multiple add-ins.
                FreeRefFunction result = AnalysisToolPak.findFunction(functionName);
                if (result != null) {
@@ -74,24 +74,13 @@ final class ExternalFunction implements FreeRefFunction {
                throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
        }
 
-       private FreeRefFunction findInternalUserDefinedFunction(HSSFWorkbook workbook, NameEval functionNameEval) throws EvaluationException {
+       private FreeRefFunction findInternalUserDefinedFunction(NameEval functionNameEval) throws EvaluationException {
 
-               int numberOfNames = workbook.getNumberOfNames();
-               
-               int nameIndex = functionNameEval.getIndex();
-               if(nameIndex < 0 || nameIndex >= numberOfNames) {
-                       throw new RuntimeException("Bad name index (" + nameIndex 
-                                       + "). Allowed range is (0.." + (numberOfNames-1) + ")");
-               }
-               
-               String functionName = workbook.getNameName(nameIndex);
+               String functionName = functionNameEval.getFunctionName();
                if(false) {
                        System.out.println("received call to internal user defined function  (" + functionName + ")");
                }
-               // TODO - detect if the NameRecord corresponds to a named range, function, or something undefined
-               // throw the right errors in these cases
-               
-               // TODO find the implementation for the external function e.g. "YEARFRAC" or "ISEVEN"
+               // TODO find the implementation for the user defined function
                
                throw new EvaluationException(ErrorEval.FUNCTION_NOT_IMPLEMENTED);
        }
index 682394b3c25636122d4d006d09e672bc7813f124..1f77deec2aad3922c5e5484bcba2d5e6ca91986f 100755 (executable)
@@ -22,26 +22,24 @@ package org.apache.poi.hssf.record.formula.eval;
  */
 public final class NameEval implements Eval {
 
-       private final int _index;
+       private final String _functionName;
 
        /**
-        * @param index zero based index to a defined name record
+        * Creates a NameEval representing a function name
         */
-       public NameEval(int index) {
-               _index = index;
+       public NameEval(String functionName) {
+               _functionName = functionName;
        }
 
-       /**
-        * @return zero based index to a defined name record
-        */
-       public int getIndex() {
-               return _index;
+
+       public String getFunctionName() {
+               return _functionName;
        }
 
        public String toString() {
                StringBuffer sb = new StringBuffer(64);
                sb.append(getClass().getName()).append(" [");
-               sb.append(_index);
+               sb.append(_functionName);
                sb.append("]");
                return sb.toString();
        }
index f7874d0ca5a540a35f6aeebf07b9b50a537da97c..55cd04ee114d8491d63ecd9495bc192a95a62a11 100644 (file)
@@ -22,6 +22,7 @@ import java.util.Stack;
 
 import org.apache.poi.hssf.model.FormulaParser;
 import org.apache.poi.hssf.model.Workbook;
+import org.apache.poi.hssf.record.NameRecord;
 import org.apache.poi.hssf.record.formula.Area3DPtg;
 import org.apache.poi.hssf.record.formula.AreaPtg;
 import org.apache.poi.hssf.record.formula.BoolPtg;
@@ -62,8 +63,8 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
  */
 public class HSSFFormulaEvaluator {
 
-    protected HSSFSheet _sheet;
-    protected HSSFWorkbook _workbook;
+    private final HSSFSheet _sheet;
+    private final HSSFWorkbook _workbook;
 
     public HSSFFormulaEvaluator(HSSFSheet sheet, HSSFWorkbook workbook) {
         _sheet = sheet;
@@ -76,6 +77,9 @@ public class HSSFFormulaEvaluator {
      */
     public void setCurrentRow(HSSFRow row) {
         // do nothing
+        if (false) {
+            row.getClass(); // suppress unused parameter compiler warning
+        }
     }
 
 
@@ -297,8 +301,9 @@ public class HSSFFormulaEvaluator {
      */
     private static ValueEval internalEvaluate(HSSFCell srcCell, HSSFSheet sheet, HSSFWorkbook workbook) {
         int srcRowNum = srcCell.getRowIndex();
-        short srcColNum = srcCell.getCellNum();
+        int srcColNum = srcCell.getCellNum();
 
+        ValueEval result;
 
         EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker();
 
@@ -306,10 +311,11 @@ public class HSSFFormulaEvaluator {
             return ErrorEval.CIRCULAR_REF_ERROR;
         }
         try {
-            return evaluateCell(workbook, sheet, srcRowNum, srcColNum, srcCell.getCellFormula());
+            result = evaluateCell(workbook, sheet, srcRowNum, (short)srcColNum, srcCell.getCellFormula());
         } finally {
             tracker.endEvaluate(workbook, sheet, srcRowNum, srcColNum);
         }
+        return result;
     }
     private static ValueEval evaluateCell(HSSFWorkbook workbook, HSSFSheet sheet,
             int srcRowNum, short srcColNum, String cellFormulaText) {
@@ -326,19 +332,10 @@ public class HSSFFormulaEvaluator {
                 continue;
             }
             if (ptg instanceof MemErrPtg) { continue; }
-            if (ptg instanceof MissingArgPtg) { continue; }
-            if (ptg instanceof NamePtg) {
-                // named ranges, macro functions
-                NamePtg namePtg = (NamePtg) ptg;
-                stack.push(new NameEval(namePtg.getIndex()));
-                continue;
-            }
-            if (ptg instanceof NameXPtg) {
-                NameXPtg nameXPtg = (NameXPtg) ptg;
-                stack.push(new NameXEval(nameXPtg.getSheetRefIndex(), nameXPtg.getNameIndex()));
+            if (ptg instanceof MissingArgPtg) {
+                // TODO - might need to push BlankEval or MissingArgEval
                 continue;
             }
-            if (ptg instanceof UnknownPtg) { continue; }
             Eval opResult;
             if (ptg instanceof OperationPtg) {
                 OperationPtg optg = (OperationPtg) ptg;
@@ -355,10 +352,13 @@ public class HSSFFormulaEvaluator {
                     Eval p = (Eval) stack.pop();
                     ops[j] = p;
                 }
-                opResult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet);
+                 opResult = invokeOperation(operation, ops, srcRowNum, srcColNum, workbook, sheet);
             } else {
                 opResult = getEvalForPtg(ptg, sheet, workbook);
             }
+            if (opResult == null) {
+                throw new RuntimeException("Evaluation result must not be null");
+            }
             stack.push(opResult);
         }
 
@@ -422,6 +422,29 @@ public class HSSFFormulaEvaluator {
      * passed here!
      */
     private static Eval getEvalForPtg(Ptg ptg, HSSFSheet sheet, HSSFWorkbook workbook) {
+        if (ptg instanceof NamePtg) {
+            // named ranges, macro functions
+            NamePtg namePtg = (NamePtg) ptg;
+            int numberOfNames = workbook.getNumberOfNames();
+            int nameIndex = namePtg.getIndex();
+            if(nameIndex < 0 || nameIndex >= numberOfNames) {
+                throw new RuntimeException("Bad name index (" + nameIndex 
+                        + "). Allowed range is (0.." + (numberOfNames-1) + ")");
+            }
+            NameRecord nameRecord = workbook.getWorkbook().getNameRecord(nameIndex);
+            if (nameRecord.isFunctionName()) {
+                return new NameEval(nameRecord.getNameText());
+            }
+            if (nameRecord.hasFormula()) {
+                return evaluateNameFormula(nameRecord.getNameDefinition(), sheet, workbook);
+            }
+            
+            throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'");
+        }
+        if (ptg instanceof NameXPtg) {
+            NameXPtg nameXPtg = (NameXPtg) ptg;
+            return new NameXEval(nameXPtg.getSheetRefIndex(), nameXPtg.getNameIndex());
+        }
         if (ptg instanceof RefPtg) {
             return new LazyRefEval(((RefPtg) ptg), sheet, workbook);
         }
@@ -456,8 +479,20 @@ public class HSSFFormulaEvaluator {
         if (ptg instanceof ErrPtg) {
             return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode());
         }
+        if (ptg instanceof UnknownPtg) { 
+            // TODO - remove UnknownPtg
+            throw new RuntimeException("UnknownPtg not allowed");
+        }
         throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")");
     }
+    private static Eval evaluateNameFormula(Ptg[] ptgs, HSSFSheet sheet,
+            HSSFWorkbook workbook) {
+        if (ptgs.length > 1) {
+            throw new RuntimeException("Complex name formulas not supported yet");
+        }
+        return getEvalForPtg(ptgs[0], sheet, workbook);
+    }
+
     /**
      * Given a cell, find its type and from that create an appropriate ValueEval
      * impl instance and return that. Since the cell could be an external
index 7ebbb633af8bd6a0a951f1d4335c9242fe2c3f20..c5d43cc038a5d0ee463a66ddb0d1c125c914a750 100644 (file)
Binary files a/src/testcases/org/apache/poi/hssf/data/testNames.xls and b/src/testcases/org/apache/poi/hssf/data/testNames.xls differ
index 772a4fdfc781cfc9c88fa71c5d34686928651de3..72d420cb4f8bf60e5b4234540e438ad90dc1bb7e 100755 (executable)
@@ -46,6 +46,7 @@ public class AllUserModelTests {
                result.addTestSuite(TestHSSFConditionalFormatting.class);
                result.addTestSuite(TestHSSFDataFormatter.class);
                result.addTestSuite(TestHSSFDateUtil.class);
+               result.addTestSuite(TestHSSFFormulaEvaluator.class);
                result.addTestSuite(TestHSSFHeaderFooter.class);
                result.addTestSuite(TestHSSFHyperlink.class);
                result.addTestSuite(TestHSSFOptimiser.class);
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFFormulaEvaluator.java
new file mode 100644 (file)
index 0000000..d1f70a0
--- /dev/null
@@ -0,0 +1,43 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.usermodel;
+
+import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
+
+import junit.framework.TestCase;
+/**
+ * 
+ * @author Josh Micich
+ */
+public final class TestHSSFFormulaEvaluator extends TestCase {
+
+       /**
+        * Test that the HSSFFormulaEvaluator can evaluate simple named ranges
+        *  (single cells and rectangular areas)
+        */
+       public void testEvaluateSimple() {
+               HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("testNames.xls");
+               HSSFSheet sheet = wb.getSheetAt(0);
+               HSSFCell cell = sheet.getRow(8).getCell(0);
+               HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
+               CellValue cv = fe.evaluate(cell);
+               assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
+               assertEquals(3.72, cv.getNumberValue(), 0.0);
+       }
+}