diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java index ac57ba7da..0d130452b 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 3504a6b05..a26d65aa1 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 0a40bf0ca..49f4598f5 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 cd635898f..a1393dc48 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 58871a64b..dca3774f5 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(); + public static final UDFFinder instance = new AnalysisToolPak(); - private static final class NotImplemented implements FreeRefFunction { - private final String _functionName; + private static final class NotImplemented implements FreeRefFunction { + private final String _functionName; - public NotImplemented(String functionName) { - _functionName = functionName; - } + public NotImplemented(String functionName) { + _functionName = functionName; + } - public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { - throw new NotImplementedException(_functionName); - } - }; + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + throw new NotImplementedException(_functionName); + } + } - private final Map _functionsByName = createFunctionsMap(); + ; + + private final Map _functionsByName = createFunctionsMap(); - private AnalysisToolPak() { - // enforce singleton - } + private AnalysisToolPak() { + // enforce singleton + } - public FreeRefFunction findFunction(String name) { - return _functionsByName.get(name); - } + public FreeRefFunction findFunction(String name) { + return _functionsByName.get(name); + } - private Map createFunctionsMap() { - Map m = new HashMap(100); + 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, "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); + 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; - } + return m; + } - private static void r(Map m, String functionName, FreeRefFunction pFunc) { - FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc; - m.put(functionName, func); - } + 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 74e4a182b..28aacc215 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 bbe9ecfd8..91b1ac330 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 2be10e0ea..7fdfdd16c 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 fd23cad97..b1ad37f8a 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 11425877b..03a349d3f 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 000000000..2814a89a8 --- /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 7813092a5..953550450 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 a559919e7..f7af84500 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 000000000..70e4d9170 --- /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 000000000..3a9ae1db9 --- /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 000000000..87da6100d --- /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 000000000..c446457a5 Binary files /dev/null and b/test-data/spreadsheet/atp.xls differ diff --git a/test-data/spreadsheet/atp.xlsx b/test-data/spreadsheet/atp.xlsx new file mode 100644 index 000000000..3371fb4c5 Binary files /dev/null and b/test-data/spreadsheet/atp.xlsx differ