From: Yegor Kozlov Date: Wed, 16 Sep 2009 19:16:39 +0000 (+0000) Subject: improved work with user-defined functions, see Bugzilla 47809 X-Git-Tag: REL_3_5-FINAL~7 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=759683dcc0084497ecccac8aa917f11e22e8aa85;p=poi.git improved work with user-defined functions, see Bugzilla 47809 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@815928 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 17ada799c7..c4c9288550 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,7 @@ + 47809 - Improved work with user-defined functions 47581 - fixed XSSFSheet.setColumnWidth to produce XML compatible with Mac Excel 2008 47734 - removed unnecessary svn:executable flag from files in SVN trunk 47543 - added javadoc how to avoid Excel crash when creating too many HSSFRichTextString cells diff --git a/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java b/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java index 24423d04a0..acfb306242 100644 --- a/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java +++ b/src/java/org/apache/poi/hssf/record/formula/atp/AnalysisToolPak.java @@ -22,10 +22,14 @@ import java.util.Map; import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; +import org.apache.poi.hssf.record.formula.toolpack.ToolPack; import org.apache.poi.ss.formula.OperationEvaluationContext; import org.apache.poi.ss.formula.eval.NotImplementedException; -public final class AnalysisToolPak { +/** + * Modified 09/07/09 by Petr Udalau - systematized work of ToolPacks. + */ +public final class AnalysisToolPak implements ToolPack { private static final class NotImplemented implements FreeRefFunction { private final String _functionName; @@ -38,18 +42,14 @@ public final class AnalysisToolPak { throw new NotImplementedException(_functionName); } }; + + private Map _functionsByName = createFunctionsMap(); - private static Map _functionsByName = createFunctionsMap(); - - private AnalysisToolPak() { - // no instances of this class - } - - public static FreeRefFunction findFunction(String name) { + public FreeRefFunction findFunction(String name) { return _functionsByName.get(name); } - - private static Map createFunctionsMap() { + + private Map createFunctionsMap() { Map m = new HashMap(100); r(m, "ACCRINT", null); @@ -153,4 +153,16 @@ public final class AnalysisToolPak { FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc; m.put(functionName, func); } + + public void addFunction(String name, FreeRefFunction evaluator) { + r(_functionsByName, name, evaluator); + } + + public boolean containsFunction(String name) { + return _functionsByName.containsKey(name); + } + + public void removeFunction(String name) { + _functionsByName.remove(name); + } } diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/UserDefinedFunction.java b/src/java/org/apache/poi/hssf/record/formula/eval/UserDefinedFunction.java index f12c96e59f..bf390141ee 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/UserDefinedFunction.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/UserDefinedFunction.java @@ -17,8 +17,8 @@ package org.apache.poi.hssf.record.formula.eval; -import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; +import org.apache.poi.hssf.record.formula.toolpack.MainToolPacksHandler; import org.apache.poi.ss.formula.EvaluationWorkbook; import org.apache.poi.ss.formula.OperationEvaluationContext; import org.apache.poi.ss.formula.eval.NotImplementedException; @@ -28,6 +28,8 @@ import org.apache.poi.ss.formula.eval.NotImplementedException; * AbstractFunctionPtg.field_2_fnc_index == 255) * * @author Josh Micich + * + * Modified 09/07/09 by Petr Udalau - Improved resolving of UDFs through the ToolPacks. */ final class UserDefinedFunction implements FreeRefFunction { @@ -38,7 +40,7 @@ final class UserDefinedFunction implements FreeRefFunction { } public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { - + EvaluationWorkbook workbook = ec.getWorkbook(); int nIncomingArgs = args.length; if(nIncomingArgs < 1) { throw new RuntimeException("function name argument missing"); @@ -47,9 +49,9 @@ final class UserDefinedFunction implements FreeRefFunction { ValueEval nameArg = args[0]; FreeRefFunction targetFunc; if (nameArg instanceof NameEval) { - targetFunc = findInternalUserDefinedFunction((NameEval) nameArg); + targetFunc = findInternalUserDefinedFunction(workbook, (NameEval) nameArg); } else if (nameArg instanceof NameXEval) { - targetFunc = findExternalUserDefinedFunction(ec.getWorkbook(), (NameXEval) nameArg); + targetFunc = findExternalUserDefinedFunction(workbook, (NameXEval) nameArg); } else { throw new RuntimeException("First argument should be a NameEval, but got (" + nameArg.getClass().getName() + ")"); @@ -67,21 +69,29 @@ final class UserDefinedFunction implements FreeRefFunction { if(false) { System.out.println("received call to external user defined function (" + functionName + ")"); } - // currently only looking for functions from the 'Analysis TookPak' e.g. "YEARFRAC" or "ISEVEN" + // currently only looking for functions from the 'Analysis TookPak'(contained in MainToolPacksHandler) e.g. "YEARFRAC" or "ISEVEN" // not sure how much this logic would need to change to support other or multiple add-ins. - FreeRefFunction result = AnalysisToolPak.findFunction(functionName); + FreeRefFunction result = MainToolPacksHandler.instance().findFunction(functionName); if (result != null) { return result; } throw new NotImplementedException(functionName); } - private static FreeRefFunction findInternalUserDefinedFunction(NameEval functionNameEval) { + private static FreeRefFunction findInternalUserDefinedFunction(EvaluationWorkbook workbook, + NameEval functionNameEval) { String functionName = functionNameEval.getFunctionName(); if(false) { System.out.println("received call to internal user defined function (" + functionName + ")"); } + FreeRefFunction functionEvaluator = workbook.findUserDefinedFunction(functionName); + if (functionEvaluator == null) { + functionEvaluator = MainToolPacksHandler.instance().findFunction(functionName); + } + if (functionEvaluator != null) { + return functionEvaluator; + } // TODO find the implementation for the user defined function throw new NotImplementedException(functionName); diff --git a/src/java/org/apache/poi/hssf/record/formula/toolpack/DefaultToolPack.java b/src/java/org/apache/poi/hssf/record/formula/toolpack/DefaultToolPack.java new file mode 100755 index 0000000000..39d2c47ca9 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/toolpack/DefaultToolPack.java @@ -0,0 +1,51 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.toolpack; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; + +/** + * Default tool pack. + * If you want to add some UDF, but you don't want to create new tool pack, use this. + * + * @author PUdalau + */ +public class DefaultToolPack implements ToolPack { + private Map functionsByName = new HashMap(); + + public void addFunction(String name, FreeRefFunction evaluator) { + if (evaluator != null){ + functionsByName.put(name, evaluator); + } + } + + public boolean containsFunction(String name) { + return functionsByName.containsKey(name); + } + + public FreeRefFunction findFunction(String name) { + return functionsByName.get(name); + } + + public void removeFunction(String name) { + functionsByName.remove(name); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/toolpack/MainToolPacksHandler.java b/src/java/org/apache/poi/hssf/record/formula/toolpack/MainToolPacksHandler.java new file mode 100755 index 0000000000..9bb0c07888 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/toolpack/MainToolPacksHandler.java @@ -0,0 +1,102 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.record.formula.toolpack; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; + +/** + * Contains all tool packs. Processing of UDF is through this class. + * + * @author PUdalau + */ +public class MainToolPacksHandler{ + + private DefaultToolPack defaultToolPack; + + private List usedToolPacks = new ArrayList(); + + private static MainToolPacksHandler instance; + + /** + * @return Unique instance of handler. + */ + public static MainToolPacksHandler instance() { + if (instance == null) { + instance = new MainToolPacksHandler(); + } + return instance; + } + + /** + * @return Default tool pack(which is obligatory exists in handler). + */ + public DefaultToolPack getDefaultToolPack() { + return defaultToolPack; + } + + private MainToolPacksHandler() { + defaultToolPack = new DefaultToolPack(); + usedToolPacks.add(defaultToolPack); + usedToolPacks.add(new AnalysisToolPak()); + } + + /** + * Checks if such function exists in any registered tool pack. + * @param name Name of function. + * @return true if some tool pack contains such function. + */ + public boolean containsFunction(String name) { + for (ToolPack pack : usedToolPacks) { + if (pack.containsFunction(name)) { + return true; + } + } + return false; + } + + /** + * Returns executor by specified name. Returns null if + * function isn't contained by any registered tool pack. + * + * @param name + * Name of function. + * @return Function executor. + */ + public FreeRefFunction findFunction(String name) { + FreeRefFunction evaluatorForFunction; + for (ToolPack pack : usedToolPacks) { + evaluatorForFunction = pack.findFunction(name); + if (evaluatorForFunction != null) { + return evaluatorForFunction; + } + } + return null; + } + + /** + * Registers new tool pack in handler. + * @param pack Tool pack to add. + */ + public void addToolPack(ToolPack pack) { + usedToolPacks.add(pack); + } +} diff --git a/src/java/org/apache/poi/hssf/record/formula/toolpack/ToolPack.java b/src/java/org/apache/poi/hssf/record/formula/toolpack/ToolPack.java new file mode 100755 index 0000000000..d2e58da9e3 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/toolpack/ToolPack.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.hssf.record.formula.toolpack; + +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; + +/** + * Common interface for any new tool pack with executors for functions. + * + * @author PUdalau + */ +public interface ToolPack { + /** + * Returns executor by specified name. Returns null if tool + * pack doesn't contains such function. + * + * @param name Name of function. + * @return Function executor. + */ + FreeRefFunction findFunction(String name); + + /** + * Add new function with executor. + * @param name Name of function. + * @param evaluator Function executor. + */ + void addFunction(String name, FreeRefFunction evaluator); + + /** + * Returns executor by specified name if it exists. + * @param name Name of function. + */ + void removeFunction(String name); + + /** + * Checks if such function exists in tool pack. + * @param name Name of function. + * @return true if tool pack contains such function. + */ + boolean containsFunction(String name); +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java index 968daef664..b0f18bcda9 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java @@ -24,6 +24,7 @@ import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.ss.formula.*; import org.apache.poi.ss.SpreadsheetVersion; @@ -31,6 +32,8 @@ import org.apache.poi.ss.SpreadsheetVersion; * Internal POI use only * * @author Josh Micich + * + * Modified 09/07/09 by Petr Udalau - added methods for searching for UDFs of this Workbook. */ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { @@ -159,4 +162,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E public SpreadsheetVersion getSpreadsheetVersion(){ return SpreadsheetVersion.EXCEL97; } + + public FreeRefFunction findUserDefinedFunction(String functionName) { + return _uBook.getUserDefinedFunction(functionName); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 0f98298a17..b9a732bc32 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -24,9 +24,11 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import org.apache.poi.POIDocument; @@ -62,15 +64,18 @@ import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.SheetNameFormatter; import org.apache.poi.hssf.record.formula.UnionPtg; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; 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.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Name; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; + /** * High level representation of a workbook. This is the first object most users * will construct whether they are reading or writing a workbook. It is also the @@ -82,6 +87,8 @@ import org.apache.poi.util.POILogger; * @author Glen Stampoultzis (glens at apache.org) * @author Shawn Laubach (slaubach at apache dot org) * + * + * Modified 09/07/09 by Petr Udalau - added methods for work with UDFs of this Workbook. */ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.usermodel.Workbook { private static final Pattern COMMA_PATTERN = Pattern.compile(","); @@ -160,8 +167,9 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm private static POILogger log = POILogFactory.getLogger(HSSFWorkbook.class); - - + + /** Map of user defined functions, key - function name, value - instance of FreeRefFunctions */ + private Map udfFunctions; /** * Creates new HSSFWorkbook from scratch (start here!) @@ -178,6 +186,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm workbook = book; _sheets = new ArrayList( INITIAL_CAPACITY ); names = new ArrayList( INITIAL_CAPACITY ); + udfFunctions = new HashMap(); } public HSSFWorkbook(POIFSFileSystem fs) throws IOException { @@ -269,6 +278,7 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm _sheets = new ArrayList(INITIAL_CAPACITY); names = new ArrayList(INITIAL_CAPACITY); + udfFunctions = new HashMap(); // Grab the data from the workbook stream, however // it happens to be spelled. @@ -1617,6 +1627,25 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm } } } + + public FreeRefFunction getUserDefinedFunction(String functionName) { + return udfFunctions.get(functionName); + } + + public void registerUserDefinedFunction(String functionName, FreeRefFunction freeRefFunction) { + Name udfDeclaration = getName(functionName); + if (udfDeclaration == null) { + udfDeclaration = createName(); + } + udfDeclaration.setNameName(functionName); + udfDeclaration.setFunction(true); + udfFunctions.put(functionName, freeRefFunction); + + } + + public List getUserDefinedFunctionNames() { + return new ArrayList(udfFunctions.keySet()); + } /** * Is the workbook protected with a password (not encrypted)? diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java index a3b2325719..0d65af92df 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.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; /** * Abstracts a workbook for the purpose of formula evaluation.
@@ -27,6 +28,8 @@ import org.apache.poi.hssf.record.formula.Ptg; * For POI internal use only * * @author Josh Micich + * + * Modified 09/07/09 by Petr Udalau - added methods for searching for UDFs of this Workbook. */ public interface EvaluationWorkbook { String getSheetName(int sheetIndex); @@ -51,6 +54,17 @@ public interface EvaluationWorkbook { String resolveNameXText(NameXPtg ptg); Ptg[] getFormulaTokens(EvaluationCell cell); + /** + * Find and return user defined function (UDF) contained by workbook with + * specified name. + * + * @param functionName UDF name + * @return instance of FreeRefFunction or null if no UDF with the specified + * name exists. + */ + FreeRefFunction findUserDefinedFunction(String functionName); + + class ExternalSheet { private final String _workbookName; private final String _sheetName; 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 08c7841197..1525708f31 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 @@ -23,6 +23,7 @@ import java.util.Map; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.ss.formula.EvaluationCell; import org.apache.poi.ss.formula.EvaluationName; import org.apache.poi.ss.formula.EvaluationSheet; @@ -35,6 +36,8 @@ import org.apache.poi.ss.usermodel.Workbook; * updated after a call to {@link #getOrCreateUpdatableCell(String, int, int)}. * * @author Josh Micich + * + * Modified 09/07/09 by Petr Udalau - added methods for searching for UDFs of this Workbook. */ final class ForkedEvaluationWorkbook implements EvaluationWorkbook { @@ -141,4 +144,8 @@ final class ForkedEvaluationWorkbook implements EvaluationWorkbook { return _index - o._index; } } + + public FreeRefFunction findUserDefinedFunction(String functionName) { + return _masterBook.findUserDefinedFunction(functionName); + } } diff --git a/src/java/org/apache/poi/ss/usermodel/Workbook.java b/src/java/org/apache/poi/ss/usermodel/Workbook.java index 6ebf1013f8..cbbc2a4268 100644 --- a/src/java/org/apache/poi/ss/usermodel/Workbook.java +++ b/src/java/org/apache/poi/ss/usermodel/Workbook.java @@ -21,12 +21,15 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; /** * High level representation of a Excel workbook. This is the first object most users * will construct whether they are reading or writing a workbook. It is also the * top level object for creating new sheets/etc. + * + * Modified 09/07/09 by Petr Udalau - added methods for work with UDFs of this Workbook. */ public interface Workbook { @@ -464,4 +467,29 @@ public interface Workbook { * @param hidden 0 for not hidden, 1 for hidden, 2 for very hidden */ void setSheetHidden(int sheetIx, int hidden); + + /** + * Find and return user defined function (UDF) with specified name. + * + * @param functionName + * UDF name + * @return instance of FreeRefFunction or null if no UDF with the specified + * name exists. + */ + FreeRefFunction getUserDefinedFunction(String functionName); + + /** + * Add user defined function (UDF) to workbook + * + * @param name + * @param function + */ + void registerUserDefinedFunction(String name, FreeRefFunction function); + + /** + * Returns user defined functions (UDF) names + * + * @return list of UDF names + */ + List getUserDefinedFunctionNames(); } 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 5264281f00..5e6fd93904 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java @@ -20,6 +20,7 @@ package org.apache.poi.xssf.usermodel; import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.ss.formula.*; import org.apache.poi.ss.SpreadsheetVersion; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName; @@ -28,6 +29,8 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName; * Internal POI use only * * @author Josh Micich + * + * Modified 09/07/09 by Petr Udalau - added methods for searching for UDFs of this Workbook. */ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook { @@ -177,4 +180,8 @@ public final class XSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E public SpreadsheetVersion getSpreadsheetVersion(){ return SpreadsheetVersion.EXCEL2007; } + + public FreeRefFunction findUserDefinedFunction(String functionName) { + return _uBook.getUserDefinedFunction(functionName); + } } 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 e761146d3e..ffd1736119 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -32,6 +32,7 @@ import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLException; import org.apache.poi.POIXMLProperties; import org.apache.poi.hssf.record.formula.SheetNameFormatter; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; @@ -40,6 +41,7 @@ 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.usermodel.Name; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; @@ -63,6 +65,8 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.*; * High level representation of a SpreadsheetML workbook. This is the first object most users * will construct whether they are reading or writing a workbook. It is also the * top level object for creating new sheets/etc. + * + * Modified 09/07/09 by Petr Udalau - added methods for work with UDFs of this Workbook. */ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable { private static final Pattern COMMA_PATTERN = Pattern.compile(","); @@ -129,6 +133,9 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable pictures; private static POILogger logger = POILogFactory.getLogger(XSSFWorkbook.class); + + /** Map of user defined functions, key - function name, value - instance of FreeRefFunctions */ + private Map udfFunctions = new HashMap(); /** * Create a new SpreadsheetML workbook. @@ -1346,4 +1353,22 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable getUserDefinedFunctionNames() { + return new ArrayList(udfFunctions.keySet()); + } + } diff --git a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java index ebd0afb552..b2937b0fcb 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/eval/TestExternalFunction.java @@ -22,61 +22,92 @@ import java.io.IOException; import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; +import org.apache.poi.hssf.record.formula.toolpack.DefaultToolPack; +import org.apache.poi.hssf.record.formula.toolpack.MainToolPacksHandler; +import org.apache.poi.hssf.record.formula.toolpack.ToolPack; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; -import org.apache.poi.hssf.usermodel.HSSFName; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.formula.eval.NotImplementedException; -import org.apache.poi.ss.usermodel.CellValue; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.usermodel.Workbook; + /** * * @author Josh Micich + * + * Modified 09/14/09 by Petr Udalau - Test of registering UDFs in workbook and + * using ToolPacks. */ public final class TestExternalFunction extends TestCase { - /** - * Checks that an external function can get invoked from the formula evaluator. - * @throws IOException - - */ - public void testInvoke() { - - HSSFWorkbook wb; - HSSFSheet sheet; - HSSFCell cell; - if (false) { - // TODO - this code won't work until we can create user-defined functions directly with POI - wb = new HSSFWorkbook(); - sheet = wb.createSheet(); - wb.setSheetName(0, "Sheet1"); - HSSFName hssfName = wb.createName(); - hssfName.setNameName("myFunc"); - - } else { - // This sample spreadsheet already has a VB function called 'myFunc' - wb = HSSFTestDataSamples.openSampleWorkbook("testNames.xls"); - sheet = wb.getSheetAt(0); - HSSFRow row = sheet.createRow(0); - cell = row.createCell(1); - } - - cell.setCellFormula("myFunc()"); - String actualFormula=cell.getCellFormula(); - assertEquals("myFunc()", actualFormula); - - HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); - // Check out what ExternalFunction.evaluate() does: - CellValue evalResult; - try { - evalResult = fe.evaluate(cell); - } catch (NotImplementedException e) { - assertEquals("Error evaluating cell Sheet1!B1", e.getMessage()); - assertEquals("myFunc", e.getCause().getMessage()); - return; - } - // TODO - make this test assert something more interesting as soon as ExternalFunction works a bit better - assertNotNull(evalResult); - } + private static class MyFunc implements FreeRefFunction { + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + if (args.length != 1 || !(args[0] instanceof StringEval)) { + return ErrorEval.VALUE_INVALID; + } else { + StringEval input = (StringEval) args[0]; + return new StringEval(input.getStringValue() + "abc"); + } + } + } + + private static class MyFunc2 implements FreeRefFunction { + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + if (args.length != 1 || !(args[0] instanceof StringEval)) { + return ErrorEval.VALUE_INVALID; + } else { + StringEval input = (StringEval) args[0]; + return new StringEval(input.getStringValue() + "abc2"); + } + } + } + + /** + * Creates and registers user-defined function "MyFunc()" directly with POI. + * This is VB function defined in "testNames.xls". In future there must be + * some parser of VBA scripts which will register UDFs. + */ + private void registerMyFunc(Workbook workbook) { + workbook.registerUserDefinedFunction("myFunc", new MyFunc()); + } + + /** + * Creates example ToolPack which contains function "MyFunc2()". + */ + private void createExampleToolPack() { + ToolPack exampleToolPack = new DefaultToolPack(); + exampleToolPack.addFunction("myFunc2", new MyFunc2()); + MainToolPacksHandler.instance().addToolPack(exampleToolPack); + } + + /** + * Checks that an external function can get invoked from the formula + * evaluator. + * + * @throws IOException + * + */ + public void testInvoke() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("testNames.xls"); + HSSFSheet sheet = wb.getSheetAt(0); + + registerMyFunc(wb); + createExampleToolPack(); + + HSSFRow row = sheet.getRow(0); + HSSFCell myFuncCell = row.getCell(1); //=myFunc("_") + + HSSFCell myFunc2Cell = row.getCell(2); //=myFunc2("_") + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + try { + assertEquals("_abc", fe.evaluate(myFuncCell).getStringValue()); + assertEquals("_abc2", fe.evaluate(myFunc2Cell).getStringValue()); + } catch (Exception e) { + assertFalse(true); + } + } } diff --git a/test-data/spreadsheet/testNames.xls b/test-data/spreadsheet/testNames.xls index c5d43cc038..ad1c2889c8 100644 Binary files a/test-data/spreadsheet/testNames.xls and b/test-data/spreadsheet/testNames.xls differ