From db89d09ca36b73db897d032e33fe19872c78f61d Mon Sep 17 00:00:00 2001
From: Yegor Kozlov
Date: Sun, 28 Nov 2010 12:03:52 +0000
Subject: [PATCH] improved work with UDFs and Analysis Toolpack functions, ATP
functions are enabled by default and user can create / evaluate them just
like built-in functions, both HSSF andf XSSF are supported
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1039870 13f79535-47bb-0310-9956-ffa450edef68
---
.../usermodel/HSSFEvaluationWorkbook.java | 7 +-
.../poi/hssf/usermodel/HSSFWorkbook.java | 32 +-
.../poi/ss/formula/EvaluationWorkbook.java | 3 +
.../poi/ss/formula/WorkbookEvaluator.java | 17 +-
.../poi/ss/formula/atp/AnalysisToolPak.java | 275 ++++++++++--------
.../eval/forked/ForkedEvaluationWorkbook.java | 9 +
.../function/FunctionMetadataRegistry.java | 2 +-
.../ss/formula/udf/AggregatingUDFFinder.java | 20 +-
.../poi/ss/formula/udf/DefaultUDFFinder.java | 4 +-
.../org/apache/poi/ss/usermodel/Workbook.java | 9 +
.../poi/xssf/model/IndexedUDFFinder.java | 56 ++++
.../usermodel/XSSFEvaluationWorkbook.java | 29 +-
.../poi/xssf/usermodel/XSSFWorkbook.java | 41 ++-
.../usermodel/TestXSSFExternalFunctions.java | 35 +++
.../usermodel/TestHSSFExternalFunctions.java | 36 +++
.../ss/formula/BaseTestExternalFunctions.java | 144 +++++++++
test-data/spreadsheet/atp.xls | Bin 0 -> 14336 bytes
test-data/spreadsheet/atp.xlsx | Bin 0 -> 38593 bytes
18 files changed, 558 insertions(+), 161 deletions(-)
create mode 100644 src/ooxml/java/org/apache/poi/xssf/model/IndexedUDFFinder.java
create mode 100644 src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFExternalFunctions.java
create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestHSSFExternalFunctions.java
create mode 100644 src/testcases/org/apache/poi/ss/formula/BaseTestExternalFunctions.java
create mode 100644 test-data/spreadsheet/atp.xls
create mode 100644 test-data/spreadsheet/atp.xlsx
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
index ac57ba7dac..0d130452ba 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java
@@ -66,9 +66,7 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
}
public NameXPtg getNameXPtg(String name) {
- // TODO YK: passing UDFFinder.DEFAULT is temporary,
- // a proper design should take it from the parent HSSFWorkbook
- return _iBook.getNameXPtg(name, UDFFinder.DEFAULT);
+ return _iBook.getNameXPtg(name, _uBook.getUDFFinder());
}
/**
@@ -147,6 +145,9 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
FormulaRecordAggregate fra = (FormulaRecordAggregate) cell.getCellValueRecord();
return fra.getFormulaTokens();
}
+ public UDFFinder getUDFFinder(){
+ return _uBook.getUDFFinder();
+ }
private static final class Name implements EvaluationName {
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index 3504a6b051..a26d65aa16 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -66,6 +66,8 @@ import org.apache.poi.ss.formula.ptg.UnionPtg;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
+import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.formula.FormulaType;
@@ -148,6 +150,12 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
private static POILogger log = POILogFactory.getLogger(HSSFWorkbook.class);
+ /**
+ * The locator of user-defined functions.
+ * By default includes functions from the Excel Analysis Toolpack
+ */
+ private UDFFinder _udfFinder = UDFFinder.DEFAULT;
+
public static HSSFWorkbook create(InternalWorkbook book) {
return new HSSFWorkbook(book);
}
@@ -1672,11 +1680,33 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
}
}
- public CreationHelper getCreationHelper() {
+ public HSSFCreationHelper getCreationHelper() {
return new HSSFCreationHelper(this);
}
private static byte[] newUID() {
return new byte[16];
}
+
+ /**
+ *
+ * Returns the locator of user-defined functions.
+ * The default instance extends the built-in functions with the Analysis Tool Pack
+ *
+ * @return the locator of user-defined functions
+ */
+ /*package*/ UDFFinder getUDFFinder(){
+ return _udfFinder;
+ }
+
+ /**
+ * Register a new toolpack in this workbook.
+ *
+ * @param toopack the toolpack to register
+ */
+ public void addToolPack(UDFFinder toopack){
+ AggregatingUDFFinder udfs = (AggregatingUDFFinder)_udfFinder;
+ udfs.add(toopack);
+ }
+
}
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
index 0a40bf0ca9..49f4598f5a 100644
--- a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
+++ b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java
@@ -20,6 +20,7 @@ package org.apache.poi.ss.formula;
import org.apache.poi.ss.formula.ptg.NamePtg;
import org.apache.poi.ss.formula.ptg.NameXPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
+import org.apache.poi.ss.formula.udf.UDFFinder;
/**
* Abstracts a workbook for the purpose of formula evaluation.
@@ -49,8 +50,10 @@ public interface EvaluationWorkbook {
int convertFromExternSheetIndex(int externSheetIndex);
ExternalName getExternalName(int externSheetIndex, int externNameIndex);
EvaluationName getName(NamePtg namePtg);
+ EvaluationName getName(String name, int sheetIndex);
String resolveNameXText(NameXPtg ptg);
Ptg[] getFormulaTokens(EvaluationCell cell);
+ UDFFinder getUDFFinder();
class ExternalSheet {
private final String _workbookName;
diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
index cd635898f8..a1393dc485 100644
--- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
+++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
@@ -61,8 +61,8 @@ import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.functions.Choose;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.functions.IfFunc;
+import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder;
-import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.eval.NotImplementedException;
@@ -91,7 +91,7 @@ public final class WorkbookEvaluator {
private final Map _sheetIndexesByName;
private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
private final IStabilityClassifier _stabilityClassifier;
- private final UDFFinder _udfFinder;
+ private final AggregatingUDFFinder _udfFinder;
/**
* @param udfFinder pass null
for default (AnalysisToolPak only)
@@ -109,7 +109,13 @@ public final class WorkbookEvaluator {
_collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
_workbookIx = 0;
_stabilityClassifier = stabilityClassifier;
- _udfFinder = udfFinder == null ? UDFFinder.DEFAULT : udfFinder;
+
+ AggregatingUDFFinder defaultToolkit = // workbook can be null in unit tests
+ workbook == null ? null : (AggregatingUDFFinder)workbook.getUDFFinder();
+ if(defaultToolkit != null && udfFinder != null) {
+ defaultToolkit.add(udfFinder);
+ }
+ _udfFinder = defaultToolkit;
}
/**
@@ -124,10 +130,7 @@ public final class WorkbookEvaluator {
}
/* package */ EvaluationName getName(String name, int sheetIndex) {
- NamePtg namePtg = null;
- if(_workbook instanceof HSSFEvaluationWorkbook){
- namePtg =((HSSFEvaluationWorkbook)_workbook).getName(name, sheetIndex).createPtg();
- }
+ NamePtg namePtg = _workbook.getName(name, sheetIndex).createPtg();
if(namePtg == null) {
return null;
diff --git a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
index 58871a64ba..dca3774f53 100644
--- a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
+++ b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
@@ -32,133 +32,150 @@ import org.apache.poi.ss.formula.eval.NotImplementedException;
*/
public final class AnalysisToolPak implements UDFFinder {
- public static final UDFFinder instance = new AnalysisToolPak();
-
- private static final class NotImplemented implements FreeRefFunction {
- private final String _functionName;
-
- public NotImplemented(String functionName) {
- _functionName = functionName;
- }
-
- public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
- throw new NotImplementedException(_functionName);
- }
- };
-
- private final Map _functionsByName = createFunctionsMap();
-
-
- private AnalysisToolPak() {
- // enforce singleton
- }
-
- public FreeRefFunction findFunction(String name) {
- return _functionsByName.get(name);
- }
-
- private Map createFunctionsMap() {
- Map m = new HashMap(100);
-
- r(m, "ACCRINT", null);
- r(m, "ACCRINTM", null);
- r(m, "AMORDEGRC", null);
- r(m, "AMORLINC", null);
- r(m, "BESSELI", null);
- r(m, "BESSELJ", null);
- r(m, "BESSELK", null);
- r(m, "BESSELY", null);
- r(m, "BIN2DEC", null);
- r(m, "BIN2HEX", null);
- r(m, "BIN2OCT", null);
- r(m, "CO MPLEX", null);
- r(m, "CONVERT", null);
- r(m, "COUPDAYBS", null);
- r(m, "COUPDAYS", null);
- r(m, "COUPDAYSNC", null);
- r(m, "COUPNCD", null);
- r(m, "COUPNUM", null);
- r(m, "COUPPCD", null);
- r(m, "CUMIPMT", null);
- r(m, "CUMPRINC", null);
- r(m, "DEC2BIN", null);
- r(m, "DEC2HEX", null);
- r(m, "DEC2OCT", null);
- r(m, "DELTA", null);
- r(m, "DISC", null);
- r(m, "DOLLARDE", null);
- r(m, "DOLLARFR", null);
- r(m, "DURATION", null);
- r(m, "EDATE", null);
- r(m, "EFFECT", null);
- r(m, "EOMONTH", null);
- r(m, "ERF", null);
- r(m, "ERFC", null);
- r(m, "FACTDOUBLE", null);
- r(m, "FVSCHEDULE", null);
- r(m, "GCD", null);
- r(m, "GESTEP", null);
- r(m, "HEX2BIN", null);
- r(m, "HEX2DEC", null);
- r(m, "HEX2OCT", null);
- r(m, "IMABS", null);
- r(m, "IMAGINARY", null);
- r(m, "IMARGUMENT", null);
- r(m, "IMCONJUGATE", null);
- r(m, "IMCOS", null);
- r(m, "IMDIV", null);
- r(m, "IMEXP", null);
- r(m, "IMLN", null);
- r(m, "IMLOG10", null);
- r(m, "IMLOG2", null);
- r(m, "IMPOWER", null);
- r(m, "IMPRODUCT", null);
- r(m, "IMREAL", null);
- r(m, "IMSIN", null);
- r(m, "IMSQRT", null);
- r(m, "IMSUB", null);
- r(m, "IMSUM", null);
- r(m, "INTRATE", null);
- r(m, "ISEVEN", ParityFunction.IS_EVEN);
- r(m, "ISODD", ParityFunction.IS_ODD);
- r(m, "LCM", null);
- r(m, "MDURATION", null);
- r(m, "MROUND", null);
- r(m, "MULTINOMIAL", null);
- r(m, "NETWORKDAYS", null);
- r(m, "NOMINAL", null);
- r(m, "OCT2BIN", null);
- r(m, "OCT2DEC", null);
- r(m, "OCT2HEX", null);
- r(m, "ODDFPRICE", null);
- r(m, "ODDFYIELD", null);
- r(m, "ODDLPRICE", null);
- r(m, "ODDLYIELD", null);
- r(m, "PRICE", null);
- r(m, "PRICEDISC", null);
- r(m, "PRICEMAT", null);
- r(m, "QUOTIENT", null);
- r(m, "RANDBETWEEN", RandBetween.instance);
- r(m, "RECEIVED", null);
- r(m, "SERIESSUM", null);
- r(m, "SQRTPI", null);
- r(m, "TBILLEQ", null);
- r(m, "TBILLPRICE", null);
- r(m, "TBILLYIELD", null);
- r(m, "WEEKNUM", null);
- r(m, "WORKDAY", null);
- r(m, "XIRR", null);
- r(m, "XNPV", null);
- r(m, "YEARFRAC", YearFrac.instance);
- r(m, "YIELD", null);
- r(m, "YIELDDISC", null);
- r(m, "YIELDMAT", null);
-
- return m;
- }
-
- private static void r(Map m, String functionName, FreeRefFunction pFunc) {
- FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc;
- m.put(functionName, func);
- }
+ public static final UDFFinder instance = new AnalysisToolPak();
+
+ private static final class NotImplemented implements FreeRefFunction {
+ private final String _functionName;
+
+ public NotImplemented(String functionName) {
+ _functionName = functionName;
+ }
+
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ throw new NotImplementedException(_functionName);
+ }
+ }
+
+ ;
+
+ private final Map _functionsByName = createFunctionsMap();
+
+
+ private AnalysisToolPak() {
+ // enforce singleton
+ }
+
+ public FreeRefFunction findFunction(String name) {
+ return _functionsByName.get(name);
+ }
+
+ private Map createFunctionsMap() {
+ Map m = new HashMap(108);
+
+ r(m, "ACCRINT", null);
+ r(m, "ACCRINTM", null);
+ r(m, "AMORDEGRC", null);
+ r(m, "AMORLINC", null);
+ r(m, "AVERAGEIF", null);
+ r(m, "AVERAGEIFS", null);
+ r(m, "BAHTTEXT", null);
+ r(m, "BESSELI", null);
+ r(m, "BESSELJ", null);
+ r(m, "BESSELK", null);
+ r(m, "BESSELY", null);
+ r(m, "BIN2DEC", null);
+ r(m, "BIN2HEX", null);
+ r(m, "BIN2OCT", null);
+ r(m, "COMPLEX", null);
+ r(m, "CONVERT", null);
+ r(m, "COUNTIFS", null);
+ r(m, "COUPDAYBS", null);
+ r(m, "COUPDAYS", null);
+ r(m, "COUPDAYSNC", null);
+ r(m, "COUPNCD", null);
+ r(m, "COUPNUM", null);
+ r(m, "COUPPCD", null);
+ r(m, "CUBEKPIMEMBER", null);
+ r(m, "CUBEMEMBER", null);
+ r(m, "CUBEMEMBERPROPERTY", null);
+ r(m, "CUBERANKEDMEMBER", null);
+ r(m, "CUBESET", null);
+ r(m, "CUBESETCOUNT", null);
+ r(m, "CUBEVALUE", null);
+ r(m, "CUMIPMT", null);
+ r(m, "CUMPRINC", null);
+ r(m, "DEC2BIN", null);
+ r(m, "DEC2HEX", null);
+ r(m, "DEC2OCT", null);
+ r(m, "DELTA", null);
+ r(m, "DISC", null);
+ r(m, "DOLLARDE", null);
+ r(m, "DOLLARFR", null);
+ r(m, "DURATION", null);
+ r(m, "EDATE", null);
+ r(m, "EFFECT", null);
+ r(m, "EOMONTH", null);
+ r(m, "ERF", null);
+ r(m, "ERFC", null);
+ r(m, "FACTDOUBLE", null);
+ r(m, "FVSCHEDULE", null);
+ r(m, "GCD", null);
+ r(m, "GESTEP", null);
+ r(m, "HEX2BIN", null);
+ r(m, "HEX2DEC", null);
+ r(m, "HEX2OCT", null);
+ r(m, "IFERROR", null);
+ r(m, "IMABS", null);
+ r(m, "IMAGINARY", null);
+ r(m, "IMARGUMENT", null);
+ r(m, "IMCONJUGATE", null);
+ r(m, "IMCOS", null);
+ r(m, "IMDIV", null);
+ r(m, "IMEXP", null);
+ r(m, "IMLN", null);
+ r(m, "IMLOG10", null);
+ r(m, "IMLOG2", null);
+ r(m, "IMPOWER", null);
+ r(m, "IMPRODUCT", null);
+ r(m, "IMREAL", null);
+ r(m, "IMSIN", null);
+ r(m, "IMSQRT", null);
+ r(m, "IMSUB", null);
+ r(m, "IMSUM", null);
+ r(m, "INTRATE", null);
+ r(m, "ISEVEN", ParityFunction.IS_EVEN);
+ r(m, "ISODD", ParityFunction.IS_ODD);
+ r(m, "JIS", null);
+ r(m, "LCM", null);
+ r(m, "MDURATION", null);
+ r(m, "MROUND", null);
+ r(m, "MULTINOMIAL", null);
+ r(m, "NETWORKDAYS", null);
+ r(m, "NOMINAL", null);
+ r(m, "OCT2BIN", null);
+ r(m, "OCT2DEC", null);
+ r(m, "OCT2HEX", null);
+ r(m, "ODDFPRICE", null);
+ r(m, "ODDFYIELD", null);
+ r(m, "ODDLPRICE", null);
+ r(m, "ODDLYIELD", null);
+ r(m, "PRICE", null);
+ r(m, "PRICEDISC", null);
+ r(m, "PRICEMAT", null);
+ r(m, "QUOTIENT", null);
+ r(m, "RANDBETWEEN", RandBetween.instance);
+ r(m, "RECEIVED", null);
+ r(m, "RTD", null);
+ r(m, "SERIESSUM", null);
+ r(m, "SQRTPI", null);
+ r(m, "SUMIFS", null);
+ r(m, "TBILLEQ", null);
+ r(m, "TBILLPRICE", null);
+ r(m, "TBILLYIELD", null);
+ r(m, "WEEKNUM", null);
+ r(m, "WORKDAY", null);
+ r(m, "XIRR", null);
+ r(m, "XNPV", null);
+ r(m, "YEARFRAC", YearFrac.instance);
+ r(m, "YIELD", null);
+ r(m, "YIELDDISC", null);
+ r(m, "YIELDMAT", null);
+
+ return m;
+ }
+
+ private static void r(Map m, String functionName, FreeRefFunction pFunc) {
+ FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc;
+ m.put(functionName, func);
+ }
}
diff --git a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java
index 74e4a182bb..28aacc215e 100644
--- a/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java
+++ b/src/java/org/apache/poi/ss/formula/eval/forked/ForkedEvaluationWorkbook.java
@@ -27,6 +27,7 @@ import org.apache.poi.ss.formula.EvaluationCell;
import org.apache.poi.ss.formula.EvaluationName;
import org.apache.poi.ss.formula.EvaluationSheet;
import org.apache.poi.ss.formula.EvaluationWorkbook;
+import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Workbook;
/**
@@ -102,6 +103,10 @@ final class ForkedEvaluationWorkbook implements EvaluationWorkbook {
return _masterBook.getName(namePtg);
}
+ public EvaluationName getName(String name, int sheetIndex){
+ return _masterBook.getName(name, sheetIndex);
+ }
+
public EvaluationSheet getSheet(int sheetIndex) {
return getSharedSheet(getSheetName(sheetIndex));
}
@@ -130,6 +135,10 @@ final class ForkedEvaluationWorkbook implements EvaluationWorkbook {
return _masterBook.resolveNameXText(ptg);
}
+ public UDFFinder getUDFFinder(){
+ return _masterBook.getUDFFinder();
+ }
+
private static final class OrderedSheet implements Comparable {
private final String _sheetName;
private final int _index;
diff --git a/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java b/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java
index bbe9ecfd80..91b1ac3308 100644
--- a/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java
+++ b/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java
@@ -41,7 +41,7 @@ public final class FunctionMetadataRegistry {
private final FunctionMetadata[] _functionDataByIndex;
private final Map _functionDataByName;
- private static FunctionMetadataRegistry getInstance() {
+ public static FunctionMetadataRegistry getInstance() {
if (_instance == null) {
_instance = FunctionMetadataReader.createRegistry();
}
diff --git a/src/java/org/apache/poi/ss/formula/udf/AggregatingUDFFinder.java b/src/java/org/apache/poi/ss/formula/udf/AggregatingUDFFinder.java
index 2be10e0ea8..7fdfdd16c1 100644
--- a/src/java/org/apache/poi/ss/formula/udf/AggregatingUDFFinder.java
+++ b/src/java/org/apache/poi/ss/formula/udf/AggregatingUDFFinder.java
@@ -19,17 +19,22 @@ package org.apache.poi.ss.formula.udf;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
/**
* Collects add-in libraries and VB macro functions together into one UDF finder
*
* @author PUdalau
*/
-public final class AggregatingUDFFinder implements UDFFinder {
+public class AggregatingUDFFinder implements UDFFinder {
- private final UDFFinder[] _usedToolPacks;
+ private final Collection _usedToolPacks;
public AggregatingUDFFinder(UDFFinder ... usedToolPacks) {
- _usedToolPacks = usedToolPacks.clone();
+ _usedToolPacks = new ArrayList(usedToolPacks.length);
+ _usedToolPacks.addAll(Arrays.asList(usedToolPacks));
}
/**
@@ -49,4 +54,13 @@ public final class AggregatingUDFFinder implements UDFFinder {
}
return null;
}
+
+ /**
+ * Add a new toolpack
+ *
+ * @param toolPack the UDF toolpack to add
+ */
+ public void add(UDFFinder toolPack){
+ _usedToolPacks.add(toolPack);
+ }
}
diff --git a/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java b/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java
index fd23cad977..b1ad37f8ae 100644
--- a/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java
+++ b/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java
@@ -38,12 +38,12 @@ public final class DefaultUDFFinder implements UDFFinder {
}
HashMap m = new HashMap(nFuncs * 3 / 2);
for (int i = 0; i < functionImpls.length; i++) {
- m.put(functionNames[i], functionImpls[i]);
+ m.put(functionNames[i].toUpperCase(), functionImpls[i]);
}
_functionsByName = m;
}
public FreeRefFunction findFunction(String name) {
- return _functionsByName.get(name);
+ return _functionsByName.get(name.toUpperCase());
}
}
diff --git a/src/java/org/apache/poi/ss/usermodel/Workbook.java b/src/java/org/apache/poi/ss/usermodel/Workbook.java
index 11425877bc..03a349d3fa 100644
--- a/src/java/org/apache/poi/ss/usermodel/Workbook.java
+++ b/src/java/org/apache/poi/ss/usermodel/Workbook.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
+import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
/**
@@ -496,4 +497,12 @@ public interface Workbook {
* @throws IllegalArgumentException if the supplied sheet index or state is invalid
*/
void setSheetHidden(int sheetIx, int hidden);
+
+ /**
+ * Register a new toolpack in this workbook.
+ *
+ * @param toopack the toolpack to register
+ */
+ void addToolPack(UDFFinder toopack);
+
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/model/IndexedUDFFinder.java b/src/ooxml/java/org/apache/poi/xssf/model/IndexedUDFFinder.java
new file mode 100644
index 0000000000..2814a89a84
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xssf/model/IndexedUDFFinder.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xssf.model;
+
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.util.Internal;
+
+import java.util.HashMap;
+
+/**
+ * A UDFFinder that can retrieve functions both by name and by fake index.
+ *
+ * @author Yegor Kozlov
+ */
+@Internal
+public final class IndexedUDFFinder extends AggregatingUDFFinder {
+ private final HashMap _funcMap;
+
+ public IndexedUDFFinder(UDFFinder... usedToolPacks) {
+ super(usedToolPacks);
+ _funcMap = new HashMap();
+ }
+
+ public FreeRefFunction findFunction(String name) {
+ FreeRefFunction func = super.findFunction(name);
+ if (func != null) {
+ int idx = getFunctionIndex(name);
+ _funcMap.put(idx, name);
+ }
+ return func;
+ }
+
+ public String getFunctionName(int idx) {
+ return _funcMap.get(idx);
+ }
+
+ public int getFunctionIndex(String name) {
+ return name.hashCode();
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
index 7813092a56..9535504506 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
@@ -17,6 +17,7 @@
package org.apache.poi.xssf.usermodel;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.ptg.NamePtg;
import org.apache.poi.ss.formula.ptg.NameXPtg;
import org.apache.poi.ss.formula.ptg.Ptg;
@@ -29,8 +30,12 @@ import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaParsingWorkbook;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
import org.apache.poi.ss.formula.FormulaType;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.xssf.model.IndexedUDFFinder;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
+import java.util.HashMap;
+
/**
* Internal POI use only
*
@@ -100,10 +105,18 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
}
public NameXPtg getNameXPtg(String name) {
- // may require to return null to make tests pass
- throw new RuntimeException("Not implemented yet");
+ IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
+ FreeRefFunction func = udfFinder.findFunction(name);
+ if(func == null) return null;
+ else return new NameXPtg(0, udfFinder.getFunctionIndex(name));
}
+ public String resolveNameXText(NameXPtg n) {
+ int idx = n.getNameIndex();
+ IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
+ return udfFinder.getFunctionName(idx);
+ }
+
public EvaluationSheet getSheet(int sheetIndex) {
return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
}
@@ -119,14 +132,6 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return _uBook.getSheetIndex(sheetName);
}
- /**
- * TODO - figure out what the hell this methods does in
- * HSSF...
- */
- public String resolveNameXText(NameXPtg n) {
- throw new RuntimeException("method not implemented yet");
- }
-
public String getSheetNameByExternSheet(int externSheetIndex) {
int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
return _uBook.getSheetName(sheetIndex);
@@ -145,6 +150,10 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet()));
}
+ public UDFFinder getUDFFinder(){
+ return _uBook.getUDFFinder();
+ }
+
private static final class Name implements EvaluationName {
private final XSSFName _nameRecord;
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
index a559919e7e..f7af84500c 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
@@ -46,6 +46,8 @@ import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
+import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
@@ -53,11 +55,7 @@ import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.util.*;
-import org.apache.poi.xssf.model.CalculationChain;
-import org.apache.poi.xssf.model.MapInfo;
-import org.apache.poi.xssf.model.SharedStringsTable;
-import org.apache.poi.xssf.model.StylesTable;
-import org.apache.poi.xssf.model.ThemesTable;
+import org.apache.poi.xssf.model.*;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
@@ -123,6 +121,12 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable
+ * The default instance extends the built-in functions with the Excel Analysis Tool Pack.
+ * To set / evaluate custom functions you need to register them as follows:
+ *
+ *
+ *
+ *
+ * @return wrapped instance of UDFFinder that allows seeking functions both by index and name
+ */
+ /*package*/ UDFFinder getUDFFinder() {
+ return _udfFinder;
+ }
+
+ /**
+ * Register a new toolpack in this workbook.
+ *
+ * @param toopack the toolpack to register
+ */
+ public void addToolPack(UDFFinder toopack){
+ _udfFinder.add(toopack);
+ }
+
+
}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFExternalFunctions.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFExternalFunctions.java
new file mode 100644
index 0000000000..70e4d91700
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFExternalFunctions.java
@@ -0,0 +1,35 @@
+/* ====================================================================
+ 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.xssf.usermodel;
+
+import org.apache.poi.ss.formula.BaseTestExternalFunctions;
+import org.apache.poi.xssf.XSSFITestDataProvider;
+
+/**
+ * Tests setting and evaluating user-defined functions in HSSF
+ */
+public final class TestXSSFExternalFunctions extends BaseTestExternalFunctions {
+
+ public TestXSSFExternalFunctions() {
+ super(XSSFITestDataProvider.instance);
+ }
+
+ public void testATP(){
+ baseTestInvokeATP("atp.xlsx");
+ }
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFExternalFunctions.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFExternalFunctions.java
new file mode 100644
index 0000000000..3a9ae1db94
--- /dev/null
+++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFExternalFunctions.java
@@ -0,0 +1,36 @@
+/* ====================================================================
+ 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.HSSFITestDataProvider;
+import org.apache.poi.ss.formula.BaseTestExternalFunctions;
+
+/**
+ * Tests setting and evaluating user-defined functions in HSSF
+ */
+public final class TestHSSFExternalFunctions extends BaseTestExternalFunctions {
+
+ public TestHSSFExternalFunctions() {
+ super(HSSFITestDataProvider.instance);
+ }
+
+ public void testATP(){
+ baseTestInvokeATP("atp.xls");
+ }
+
+}
diff --git a/src/testcases/org/apache/poi/ss/formula/BaseTestExternalFunctions.java b/src/testcases/org/apache/poi/ss/formula/BaseTestExternalFunctions.java
new file mode 100644
index 0000000000..87da6100db
--- /dev/null
+++ b/src/testcases/org/apache/poi/ss/formula/BaseTestExternalFunctions.java
@@ -0,0 +1,144 @@
+/* ====================================================================
+ 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 junit.framework.TestCase;
+import org.apache.poi.ss.ITestDataProvider;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+import org.apache.poi.ss.formula.udf.DefaultUDFFinder;
+import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+
+/**
+ * Test setting / evaluating of Analysis Toolpack and user-defined functions
+ *
+ * @author Yegor Kozlov
+ */
+public class BaseTestExternalFunctions extends TestCase {
+ // define two custom user-defined functions
+ private static class MyFunc implements FreeRefFunction {
+ public MyFunc() {
+ //
+ }
+
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ if (args.length != 1 || !(args[0] instanceof StringEval)) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ StringEval input = (StringEval) args[0];
+ return new StringEval(input.getStringValue() + "abc");
+ }
+ }
+
+ private static class MyFunc2 implements FreeRefFunction {
+ public MyFunc2() {
+ //
+ }
+
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ if (args.length != 1 || !(args[0] instanceof StringEval)) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ StringEval input = (StringEval) args[0];
+ return new StringEval(input.getStringValue() + "abc2");
+ }
+ }
+
+ /**
+ * register the two test UDFs in a UDF finder, to be passed to the workbook
+ */
+ private static UDFFinder customToolpack = new DefaultUDFFinder(
+ new String[] { "myFunc", "myFunc2"},
+ new FreeRefFunction[] { new MyFunc(), new MyFunc2()}
+ );
+
+
+ protected final ITestDataProvider _testDataProvider;
+
+ /**
+ * @param testDataProvider an object that provides test data in HSSF / XSSF specific way
+ */
+ protected BaseTestExternalFunctions(ITestDataProvider testDataProvider) {
+ _testDataProvider = testDataProvider;
+ }
+
+ public void testExternalFunctions() {
+ Workbook wb = _testDataProvider.createWorkbook();
+
+ Sheet sh = wb.createSheet();
+
+ Cell cell1 = sh.createRow(0).createCell(0);
+ cell1.setCellFormula("ISODD(1)+ISEVEN(2)"); // functions from the Excel Analysis Toolpack
+ assertEquals("ISODD(1)+ISEVEN(2)", cell1.getCellFormula());
+
+ Cell cell2 = sh.createRow(1).createCell(0);
+ try {
+ cell2.setCellFormula("MYFUNC(\"B1\")");
+ fail("Should fail because MYFUNC is an unknown function");
+ } catch (FormulaParseException e){
+ ; //expected
+ }
+
+ wb.addToolPack(customToolpack);
+
+ cell2.setCellFormula("MYFUNC(\"B1\")");
+ assertEquals("MYFUNC(\"B1\")", cell2.getCellFormula());
+
+ Cell cell3 = sh.createRow(2).createCell(0);
+ cell3.setCellFormula("MYFUNC2(\"C1\")&\"-\"&A2"); //where A2 is defined above
+ assertEquals("MYFUNC2(\"C1\")&\"-\"&A2", cell3.getCellFormula());
+
+ FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
+ assertEquals(2.0, evaluator.evaluate(cell1).getNumberValue());
+ assertEquals("B1abc", evaluator.evaluate(cell2).getStringValue());
+ assertEquals("C1abc2-B1abc", evaluator.evaluate(cell3).getStringValue());
+
+ }
+
+ /**
+ * test invoking saved ATP functions
+ *
+ * @param testFile either atp.xls or atp.xlsx
+ */
+ public void baseTestInvokeATP(String testFile){
+ Workbook wb = _testDataProvider.openSampleWorkbook(testFile);
+ FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
+
+ Sheet sh = wb.getSheetAt(0);
+ // these two are not imlemented in r
+ assertEquals("DELTA(1.3,1.5)", sh.getRow(0).getCell(1).getCellFormula());
+ assertEquals("COMPLEX(2,4)", sh.getRow(1).getCell(1).getCellFormula());
+
+ Cell cell2 = sh.getRow(2).getCell(1);
+ assertEquals("ISODD(2)", cell2.getCellFormula());
+ assertEquals(false, evaluator.evaluate(cell2).getBooleanValue());
+ assertEquals(Cell.CELL_TYPE_BOOLEAN, evaluator.evaluateFormulaCell(cell2));
+
+ Cell cell3 = sh.getRow(3).getCell(1);
+ assertEquals("ISEVEN(2)", cell3.getCellFormula());
+ assertEquals(true, evaluator.evaluate(cell3).getBooleanValue());
+ assertEquals(Cell.CELL_TYPE_BOOLEAN, evaluator.evaluateFormulaCell(cell3));
+
+ }
+
+}
diff --git a/test-data/spreadsheet/atp.xls b/test-data/spreadsheet/atp.xls
new file mode 100644
index 0000000000000000000000000000000000000000..c446457a5a2f21d40cf6fc8c569d6c0d2643787f
GIT binary patch
literal 14336
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
EKijo~UH||9
literal 0
HcmV?d00001
diff --git a/test-data/spreadsheet/atp.xlsx b/test-data/spreadsheet/atp.xlsx
new file mode 100644
index 0000000000000000000000000000000000000000..3371fb4c569d7cbf48b3dc61916c121bebb4907e
GIT binary patch
literal 38593
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
Tz%Ov&Huxt5xc4M^<=y`UP`tJj
literal 0
HcmV?d00001
--
2.39.5