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