From: Yegor Kozlov
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCache.java b/src/java/org/apache/poi/ss/formula/EvaluationCache.java
index e0f89c444e..6b9278e3c5 100644
--- a/src/java/org/apache/poi/ss/formula/EvaluationCache.java
+++ b/src/java/org/apache/poi/ss/formula/EvaluationCache.java
@@ -17,22 +17,21 @@
package org.apache.poi.ss.formula;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
-import org.apache.poi.hssf.record.formula.eval.BoolEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFCell;
-import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.FormulaCellCache.IEntryOperation;
import org.apache.poi.ss.formula.FormulaUsedBlankCellSet.BookSheetKey;
import org.apache.poi.ss.formula.PlainCellCache.Loc;
+import org.apache.poi.ss.usermodel.Cell;
/**
- * Performance optimisation for {@link HSSFFormulaEvaluator}. This class stores previously
- * calculated values of already visited cells, to avoid unnecessary re-calculation when the
- * same cells are referenced multiple times
+ * Performance optimisation for {@link org.apache.poi.ss.usermodel.FormulaEvaluator}.
+ * This class stores previously calculated values of already visited cells,
+ * to avoid unnecessary re-calculation when the same cells are referenced multiple times
*
* @author Josh Micich
*/
@@ -57,7 +56,7 @@ final class EvaluationCache {
Loc loc = new Loc(bookIndex, sheetIndex, rowIndex, columnIndex);
PlainValueCellCacheEntry pcce = _plainCellCache.get(loc);
- if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
+ if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
if (fcce == null) {
fcce = new FormulaCellCacheEntry();
if (pcce == null) {
@@ -198,7 +197,7 @@ final class EvaluationCache {
}
public void notifyDeleteCell(int bookIndex, int sheetIndex, EvaluationCell cell) {
- if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
+ if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
FormulaCellCacheEntry fcce = _formulaCellCache.remove(cell);
if (fcce == null) {
// formula cell has not been evaluated yet
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationCell.java b/src/java/org/apache/poi/ss/formula/EvaluationCell.java
index e225934061..eef4da18da 100644
--- a/src/java/org/apache/poi/ss/formula/EvaluationCell.java
+++ b/src/java/org/apache/poi/ss/formula/EvaluationCell.java
@@ -17,8 +17,6 @@
package org.apache.poi.ss.formula;
-import java.util.HashMap;
-
/**
* Abstracts a cell for the purpose of formula evaluation. This interface represents both formula
* and non-formula cells.
@@ -29,7 +27,8 @@ import java.util.HashMap;
*/
public interface EvaluationCell {
/**
- * @return an Object that identifies the underlying cell, suitable for use as a key in a {@link HashMap}
+ * @return an Object that identifies the underlying cell,
+ * suitable for use as a key in a {@link java.util.HashMap}
*/
Object getIdentityKey();
diff --git a/src/java/org/apache/poi/ss/formula/EvaluationTracker.java b/src/java/org/apache/poi/ss/formula/EvaluationTracker.java
index 00da33af9e..ef7cb1187b 100644
--- a/src/java/org/apache/poi/ss/formula/EvaluationTracker.java
+++ b/src/java/org/apache/poi/ss/formula/EvaluationTracker.java
@@ -22,14 +22,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
/**
* Instances of this class keep track of multiple dependent cell evaluations due
- * to recursive calls to {@link WorkbookEvaluator#evaluate(HSSFCell)}
+ * to recursive calls to {@link WorkbookEvaluator#evaluate(EvaluationCell)}}
* The main purpose of this class is to detect an attempt to evaluate a cell
* that is already being evaluated. In other words, it detects circular
* references in spreadsheet formulas.
diff --git a/src/java/org/apache/poi/ss/formula/Formula.java b/src/java/org/apache/poi/ss/formula/Formula.java
index 0eb8a7f6dc..da1ef1af71 100644
--- a/src/java/org/apache/poi/ss/formula/Formula.java
+++ b/src/java/org/apache/poi/ss/formula/Formula.java
@@ -19,9 +19,6 @@ package org.apache.poi.ss.formula;
import java.util.Arrays;
-import org.apache.poi.hssf.record.ArrayRecord;
-import org.apache.poi.hssf.record.SharedFormulaRecord;
-import org.apache.poi.hssf.record.TableRecord;
import org.apache.poi.hssf.record.formula.ExpPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.TblPtg;
@@ -165,8 +162,9 @@ public class Formula {
}
/**
- * Gets the locator for the corresponding {@link SharedFormulaRecord}, {@link ArrayRecord} or
- * {@link TableRecord} if this formula belongs to such a grouping. The {@link CellReference}
+ * Gets the locator for the corresponding {@link org.apache.poi.hssf.record.SharedFormulaRecord},
+ * {@link org.apache.poi.hssf.record.ArrayRecord} or {@link org.apache.poi.hssf.record.TableRecord}
+ * if this formula belongs to such a grouping. The {@link CellReference}
* returned by this method will match the top left corner of the range of that grouping.
* The return value is usually not the same as the location of the cell containing this formula.
*
diff --git a/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java b/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java
index a02b772116..2d399494ab 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaCellCacheEntry.java
@@ -21,7 +21,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.FormulaUsedBlankCellSet.BookSheetKey;
diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java
index db46b53e86..4399045832 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaParser.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java
@@ -23,9 +23,9 @@ import java.util.regex.Pattern;
import org.apache.poi.hssf.record.constant.ErrorConstant;
import org.apache.poi.hssf.record.formula.*;
-import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
-import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
-import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
+import org.apache.poi.ss.formula.function.FunctionMetadata;
+import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
+import org.apache.poi.ss.usermodel.ErrorConstants;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
@@ -1297,13 +1297,13 @@ public final class FormulaParser {
case 'V':
if(part1.equals("VALUE")) {
Match('!');
- return HSSFErrorConstants.ERROR_VALUE;
+ return ErrorConstants.ERROR_VALUE;
}
throw expected("#VALUE!");
case 'R':
if(part1.equals("REF")) {
Match('!');
- return HSSFErrorConstants.ERROR_REF;
+ return ErrorConstants.ERROR_REF;
}
throw expected("#REF!");
case 'D':
@@ -1311,21 +1311,21 @@ public final class FormulaParser {
Match('/');
Match('0');
Match('!');
- return HSSFErrorConstants.ERROR_DIV_0;
+ return ErrorConstants.ERROR_DIV_0;
}
throw expected("#DIV/0!");
case 'N':
if(part1.equals("NAME")) {
Match('?'); // only one that ends in '?'
- return HSSFErrorConstants.ERROR_NAME;
+ return ErrorConstants.ERROR_NAME;
}
if(part1.equals("NUM")) {
Match('!');
- return HSSFErrorConstants.ERROR_NUM;
+ return ErrorConstants.ERROR_NUM;
}
if(part1.equals("NULL")) {
Match('!');
- return HSSFErrorConstants.ERROR_NULL;
+ return ErrorConstants.ERROR_NULL;
}
if(part1.equals("N")) {
Match('/');
@@ -1334,7 +1334,7 @@ public final class FormulaParser {
}
Match(look);
// Note - no '!' or '?' suffix
- return HSSFErrorConstants.ERROR_NA;
+ return ErrorConstants.ERROR_NA;
}
throw expected("#NAME?, #NUM!, #NULL! or #N/A");
diff --git a/src/java/org/apache/poi/ss/formula/FormulaShifter.java b/src/java/org/apache/poi/ss/formula/FormulaShifter.java
new file mode 100644
index 0000000000..750eecc373
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/FormulaShifter.java
@@ -0,0 +1,296 @@
+/* ====================================================================
+ 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 org.apache.poi.hssf.record.formula.*;
+
+
+/**
+ * @author Josh Micich
+ */
+public final class FormulaShifter {
+
+ /**
+ * Extern sheet index of sheet where moving is occurring
+ */
+ private final int _externSheetIndex;
+ private final int _firstMovedIndex;
+ private final int _lastMovedIndex;
+ private final int _amountToMove;
+
+ private FormulaShifter(int externSheetIndex, int firstMovedIndex, int lastMovedIndex, int amountToMove) {
+ if (amountToMove == 0) {
+ throw new IllegalArgumentException("amountToMove must not be zero");
+ }
+ if (firstMovedIndex > lastMovedIndex) {
+ throw new IllegalArgumentException("firstMovedIndex, lastMovedIndex out of order");
+ }
+ _externSheetIndex = externSheetIndex;
+ _firstMovedIndex = firstMovedIndex;
+ _lastMovedIndex = lastMovedIndex;
+ _amountToMove = amountToMove;
+ }
+
+ public static FormulaShifter createForRowShift(int externSheetIndex, int firstMovedRowIndex, int lastMovedRowIndex, int numberOfRowsToMove) {
+ return new FormulaShifter(externSheetIndex, firstMovedRowIndex, lastMovedRowIndex, numberOfRowsToMove);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(getClass().getName());
+ sb.append(" [");
+ sb.append(_firstMovedIndex);
+ sb.append(_lastMovedIndex);
+ sb.append(_amountToMove);
+ return sb.toString();
+ }
+
+ /**
+ * @param ptgs - if necessary, will get modified by this method
+ * @param currentExternSheetIx - the extern sheet index of the sheet that contains the formula being adjusted
+ * @return true
if a change was made to the formula tokens
+ */
+ public boolean adjustFormula(Ptg[] ptgs, int currentExternSheetIx) {
+ boolean refsWereChanged = false;
+ for(int i=0; i
diff --git a/src/java/org/apache/poi/ss/formula/IStabilityClassifier.java b/src/java/org/apache/poi/ss/formula/IStabilityClassifier.java
index a7d2d3f478..826a61016c 100644
--- a/src/java/org/apache/poi/ss/formula/IStabilityClassifier.java
+++ b/src/java/org/apache/poi/ss/formula/IStabilityClassifier.java
@@ -17,8 +17,6 @@
package org.apache.poi.ss.formula;
-import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
-
/**
* Used to help optimise cell evaluation result caching by allowing applications to specify which
* parts of a workbook are final.
@@ -49,7 +47,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
* Ptg
+ * tokens into human readable text form. In formula expressions, a sheet name always has a
+ * trailing '!' so there is little chance for ambiguity. It doesn't matter too much what this
+ * method returns but it is worth noting the likely consumers of these formula text strings:
+ *
+ *
+ *
+ * At the time of writing, POI's formula parser tolerates cell-like sheet names in formulas
+ * with or without delimiters. The same goes for Excel(2007), both manual and automated entry.
+ *
+ * For better or worse this implementation attempts to replicate Excel's formula renderer.
+ * Excel uses range checking on the apparent 'row' and 'column' components. Note however that
+ * the maximum sheet size varies across versions.
+ * @see org.apache.poi.ss.util.CellReference
+ */
+ /* package */ static boolean cellReferenceIsWithinRange(String lettersPrefix, String numbersSuffix) {
+ return CellReference.cellReferenceIsWithinRange(lettersPrefix, numbersSuffix, SpreadsheetVersion.EXCEL97);
+ }
+
+ /**
+ * Note - this method assumes the specified rawSheetName has only letters and digits. It
+ * cannot be used to match absolute or range references (using the dollar or colon char).
+ *
+ * Some notable cases:
+ *
+ *
+ * @return
+ *
+ * Input Result Comments
+ * "A1" true
+ * "a111" true
+ * "AA" false
+ * "aa1" true
+ * "A1A" false
+ * "A1A1" false
+ * "A$1:$C$20" false Not a plain cell reference
+ * "SALES20080101" true
+ * Still needs delimiting even though well out of range true
if there is any possible ambiguity that the specified rawSheetName
+ * could be interpreted as a valid cell name.
+ */
+ /* package */ static boolean nameLooksLikePlainCellReference(String rawSheetName) {
+ Matcher matcher = CELL_REF_PATTERN.matcher(rawSheetName);
+ if(!matcher.matches()) {
+ return false;
+ }
+
+ // rawSheetName == "Sheet1" gets this far.
+ String lettersPrefix = matcher.group(1);
+ String numbersSuffix = matcher.group(2);
+ return cellReferenceIsWithinRange(lettersPrefix, numbersSuffix);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java b/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
index 97568cfec4..e6d15861ef 100644
--- a/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
+++ b/src/java/org/apache/poi/ss/formula/SheetRefEvaluator.java
@@ -17,7 +17,7 @@
package org.apache.poi.ss.formula;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
/**
*
*
diff --git a/src/java/org/apache/poi/ss/formula/TwoDEval.java b/src/java/org/apache/poi/ss/formula/TwoDEval.java
index fbb24e8dde..7b9411ae01 100644
--- a/src/java/org/apache/poi/ss/formula/TwoDEval.java
+++ b/src/java/org/apache/poi/ss/formula/TwoDEval.java
@@ -17,11 +17,11 @@
package org.apache.poi.ss.formula;
-import org.apache.poi.hssf.record.formula.eval.AreaEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.eval.AreaEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
/**
- * Common interface of {@link AreaEval} and {@link org.apache.poi.hssf.record.formula.eval.AreaEvalBase}
+ * Common interface of {@link AreaEval} and {@link org.apache.poi.ss.formula.eval.AreaEvalBase}
*
* @author Josh Micich
*/
diff --git a/src/java/org/apache/poi/ss/formula/UserDefinedFunction.java b/src/java/org/apache/poi/ss/formula/UserDefinedFunction.java
index 7bf612a47c..c1b22ba9ba 100644
--- a/src/java/org/apache/poi/ss/formula/UserDefinedFunction.java
+++ b/src/java/org/apache/poi/ss/formula/UserDefinedFunction.java
@@ -17,11 +17,10 @@
package org.apache.poi.ss.formula;
-import org.apache.poi.hssf.record.formula.eval.NameEval;
-import org.apache.poi.hssf.record.formula.eval.NameXEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.eval.NameEval;
+import org.apache.poi.ss.formula.eval.NameXEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.eval.NotImplementedException;
/**
*
diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
index 60d31a707d..5717be22b0 100644
--- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
+++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
@@ -48,23 +48,20 @@ import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.UnionPtg;
import org.apache.poi.hssf.record.formula.UnknownPtg;
-import org.apache.poi.hssf.record.formula.eval.AreaEval;
-import org.apache.poi.hssf.record.formula.eval.BlankEval;
-import org.apache.poi.hssf.record.formula.eval.BoolEval;
-import org.apache.poi.hssf.record.formula.eval.ErrorEval;
-import org.apache.poi.hssf.record.formula.eval.EvaluationException;
-import org.apache.poi.hssf.record.formula.eval.MissingArgEval;
-import org.apache.poi.hssf.record.formula.eval.NameEval;
-import org.apache.poi.hssf.record.formula.eval.NameXEval;
-import org.apache.poi.hssf.record.formula.eval.NumberEval;
-import org.apache.poi.hssf.record.formula.eval.OperandResolver;
-import org.apache.poi.hssf.record.formula.eval.RefEval;
-import org.apache.poi.hssf.record.formula.eval.StringEval;
-import org.apache.poi.hssf.record.formula.eval.ValueEval;
-import org.apache.poi.hssf.record.formula.functions.Choose;
-import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
-import org.apache.poi.hssf.record.formula.functions.IfFunc;
-import org.apache.poi.hssf.record.formula.udf.UDFFinder;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.MissingArgEval;
+import org.apache.poi.ss.formula.eval.NameEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.StringEval;
+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.UDFFinder;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
diff --git a/src/java/org/apache/poi/ss/formula/function/FunctionDataBuilder.java b/src/java/org/apache/poi/ss/formula/function/FunctionDataBuilder.java
new file mode 100644
index 0000000000..6109cb7b94
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/function/FunctionDataBuilder.java
@@ -0,0 +1,91 @@
+/* ====================================================================
+ 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.function;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Temporarily collects FunctionMetadata instances for creation of a
+ * FunctionMetadataRegistry.
+ *
+ * @author Josh Micich
+ */
+final class FunctionDataBuilder {
+ private int _maxFunctionIndex;
+ private final Map _functionDataByName;
+ private final Map _functionDataByIndex;
+ /** stores indexes of all functions with footnotes (i.e. whose definitions might change) */
+ private final Set _mutatingFunctionIndexes;
+
+ public FunctionDataBuilder(int sizeEstimate) {
+ _maxFunctionIndex = -1;
+ _functionDataByName = new HashMap(sizeEstimate * 3 / 2);
+ _functionDataByIndex = new HashMap(sizeEstimate * 3 / 2);
+ _mutatingFunctionIndexes = new HashSet();
+ }
+
+ public void add(int functionIndex, String functionName, int minParams, int maxParams,
+ byte returnClassCode, byte[] parameterClassCodes, boolean hasFootnote) {
+ FunctionMetadata fm = new FunctionMetadata(functionIndex, functionName, minParams, maxParams,
+ returnClassCode, parameterClassCodes);
+
+ Integer indexKey = Integer.valueOf(functionIndex);
+
+
+ if(functionIndex > _maxFunctionIndex) {
+ _maxFunctionIndex = functionIndex;
+ }
+ // allow function definitions to change only if both previous and the new items have footnotes
+ FunctionMetadata prevFM;
+ prevFM = (FunctionMetadata) _functionDataByName.get(functionName);
+ if(prevFM != null) {
+ if(!hasFootnote || !_mutatingFunctionIndexes.contains(indexKey)) {
+ throw new RuntimeException("Multiple entries for function name '" + functionName + "'");
+ }
+ _functionDataByIndex.remove(Integer.valueOf(prevFM.getIndex()));
+ }
+ prevFM = (FunctionMetadata) _functionDataByIndex.get(indexKey);
+ if(prevFM != null) {
+ if(!hasFootnote || !_mutatingFunctionIndexes.contains(indexKey)) {
+ throw new RuntimeException("Multiple entries for function index (" + functionIndex + ")");
+ }
+ _functionDataByName.remove(prevFM.getName());
+ }
+ if(hasFootnote) {
+ _mutatingFunctionIndexes.add(indexKey);
+ }
+ _functionDataByIndex.put(indexKey, fm);
+ _functionDataByName.put(functionName, fm);
+ }
+
+ public FunctionMetadataRegistry build() {
+
+ FunctionMetadata[] jumbledArray = new FunctionMetadata[_functionDataByName.size()];
+ _functionDataByName.values().toArray(jumbledArray);
+ FunctionMetadata[] fdIndexArray = new FunctionMetadata[_maxFunctionIndex+1];
+ for (int i = 0; i < jumbledArray.length; i++) {
+ FunctionMetadata fd = jumbledArray[i];
+ fdIndexArray[fd.getIndex()] = fd;
+ }
+
+ return new FunctionMetadataRegistry(fdIndexArray, _functionDataByName);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/function/FunctionMetadata.java b/src/java/org/apache/poi/ss/formula/function/FunctionMetadata.java
new file mode 100644
index 0000000000..0a72bba384
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/function/FunctionMetadata.java
@@ -0,0 +1,90 @@
+/* ====================================================================
+ 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.function;
+
+/**
+ * Holds information about Excel built-in functions.
+ *
+ * @author Josh Micich
+ */
+public final class FunctionMetadata {
+ /**
+ * maxParams=30 in functionMetadata.txt means the maximum number arguments supported
+ * by the given version of Excel. Validation routines should take the actual limit (Excel 97 or 2007)
+ * from the SpreadsheetVersion enum.
+ * Perhaps a value like 'M' should be used instead of '30' in functionMetadata.txt
+ * to make that file more version neutral.
+ * @see org.apache.poi.ss.formula.FormulaParser#validateNumArgs(int, FunctionMetadata)
+ */
+ private static final short FUNCTION_MAX_PARAMS = 30;
+
+ private final int _index;
+ private final String _name;
+ private final int _minParams;
+ private final int _maxParams;
+ private final byte _returnClassCode;
+ private final byte[] _parameterClassCodes;
+
+ /* package */ FunctionMetadata(int index, String name, int minParams, int maxParams,
+ byte returnClassCode, byte[] parameterClassCodes) {
+ _index = index;
+ _name = name;
+ _minParams = minParams;
+ _maxParams = maxParams;
+ _returnClassCode = returnClassCode;
+ _parameterClassCodes = parameterClassCodes;
+ }
+ public int getIndex() {
+ return _index;
+ }
+ public String getName() {
+ return _name;
+ }
+ public int getMinParams() {
+ return _minParams;
+ }
+ public int getMaxParams() {
+ return _maxParams;
+ }
+ public boolean hasFixedArgsLength() {
+ return _minParams == _maxParams;
+ }
+ public byte getReturnClassCode() {
+ return _returnClassCode;
+ }
+ public byte[] getParameterClassCodes() {
+ return _parameterClassCodes.clone();
+ }
+ /**
+ * Some varags functions (like VLOOKUP) have a specific limit to the number of arguments that
+ * can be passed. Other functions (like SUM) don't have such a limit. For those functions,
+ * the spreadsheet version determines the maximum number of arguments that can be passed.
+ * @return true
if this function can the maximum number of arguments allowable by
+ * the {@link org.apache.poi.ss.SpreadsheetVersion}
+ */
+ public boolean hasUnlimitedVarags() {
+ return FUNCTION_MAX_PARAMS == _maxParams;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_index).append(" ").append(_name);
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java b/src/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java
new file mode 100644
index 0000000000..53ba79c5da
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java
@@ -0,0 +1,195 @@
+/* ====================================================================
+ 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.function;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.apache.poi.hssf.record.formula.Ptg;
+
+/**
+ * Converts the text meta-data file into a FunctionMetadataRegistry
+ *
+ * @author Josh Micich
+ */
+final class FunctionMetadataReader {
+
+ private static final String METADATA_FILE_NAME = "functionMetadata.txt";
+
+ /** plain ASCII text metadata file uses three dots for ellipsis */
+ private static final String ELLIPSIS = "...";
+
+ private static final Pattern TAB_DELIM_PATTERN = Pattern.compile("\t");
+ private static final Pattern SPACE_DELIM_PATTERN = Pattern.compile(" ");
+ private static final byte[] EMPTY_BYTE_ARRAY = { };
+
+ private static final String[] DIGIT_ENDING_FUNCTION_NAMES = {
+ // Digits at the end of a function might be due to a left-over footnote marker.
+ // except in these cases
+ "LOG10", "ATAN2", "DAYS360", "SUMXMY2", "SUMX2MY2", "SUMX2PY2",
+ };
+ private static final Set DIGIT_ENDING_FUNCTION_NAMES_SET = new HashSet(Arrays.asList(DIGIT_ENDING_FUNCTION_NAMES));
+
+ public static FunctionMetadataRegistry createRegistry() {
+ InputStream is = FunctionMetadataReader.class.getResourceAsStream(METADATA_FILE_NAME);
+ if (is == null) {
+ throw new RuntimeException("resource '" + METADATA_FILE_NAME + "' not found");
+ }
+
+ BufferedReader br;
+ try {
+ br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
+ } catch(UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ FunctionDataBuilder fdb = new FunctionDataBuilder(400);
+
+ try {
+ while (true) {
+ String line = br.readLine();
+ if (line == null) {
+ break;
+ }
+ if (line.length() < 1 || line.charAt(0) == '#') {
+ continue;
+ }
+ String trimLine = line.trim();
+ if (trimLine.length() < 1) {
+ continue;
+ }
+ processLine(fdb, line);
+ }
+ br.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return fdb.build();
+ }
+
+ private static void processLine(FunctionDataBuilder fdb, String line) {
+
+ String[] parts = TAB_DELIM_PATTERN.split(line, -2);
+ if(parts.length != 8) {
+ throw new RuntimeException("Bad line format '" + line + "' - expected 8 data fields");
+ }
+ int functionIndex = parseInt(parts[0]);
+ String functionName = parts[1];
+ int minParams = parseInt(parts[2]);
+ int maxParams = parseInt(parts[3]);
+ byte returnClassCode = parseReturnTypeCode(parts[4]);
+ byte[] parameterClassCodes = parseOperandTypeCodes(parts[5]);
+ // 6 isVolatile
+ boolean hasNote = parts[7].length() > 0;
+
+ validateFunctionName(functionName);
+ // TODO - make POI use isVolatile
+ fdb.add(functionIndex, functionName, minParams, maxParams,
+ returnClassCode, parameterClassCodes, hasNote);
+ }
+
+
+ private static byte parseReturnTypeCode(String code) {
+ if(code.length() == 0) {
+ return Ptg.CLASS_REF; // happens for GETPIVOTDATA
+ }
+ return parseOperandTypeCode(code);
+ }
+
+ private static byte[] parseOperandTypeCodes(String codes) {
+ if(codes.length() < 1) {
+ return EMPTY_BYTE_ARRAY; // happens for GETPIVOTDATA
+ }
+ if(isDash(codes)) {
+ // '-' means empty:
+ return EMPTY_BYTE_ARRAY;
+ }
+ String[] array = SPACE_DELIM_PATTERN.split(codes);
+ int nItems = array.length;
+ if(ELLIPSIS.equals(array[nItems-1])) {
+ // final ellipsis is optional, and ignored
+ // (all unspecified params are assumed to be the same as the last)
+ nItems --;
+ }
+ byte[] result = new byte[nItems];
+ for (int i = 0; i < nItems; i++) {
+ result[i] = parseOperandTypeCode(array[i]);
+ }
+ return result;
+ }
+
+ private static boolean isDash(String codes) {
+ if(codes.length() == 1) {
+ switch (codes.charAt(0)) {
+ case '-':
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static byte parseOperandTypeCode(String code) {
+ if(code.length() != 1) {
+ throw new RuntimeException("Bad operand type code format '" + code + "' expected single char");
+ }
+ switch(code.charAt(0)) {
+ case 'V': return Ptg.CLASS_VALUE;
+ case 'R': return Ptg.CLASS_REF;
+ case 'A': return Ptg.CLASS_ARRAY;
+ }
+ throw new IllegalArgumentException("Unexpected operand type code '" + code + "' (" + (int)code.charAt(0) + ")");
+ }
+
+ /**
+ * Makes sure that footnote digits from the original OOO document have not been accidentally
+ * left behind
+ */
+ private static void validateFunctionName(String functionName) {
+ int len = functionName.length();
+ int ix = len - 1;
+ if (!Character.isDigit(functionName.charAt(ix))) {
+ return;
+ }
+ while(ix >= 0) {
+ if (!Character.isDigit(functionName.charAt(ix))) {
+ break;
+ }
+ ix--;
+ }
+ if(DIGIT_ENDING_FUNCTION_NAMES_SET.contains(functionName)) {
+ return;
+ }
+ throw new RuntimeException("Invalid function name '" + functionName
+ + "' (is footnote number incorrectly appended)");
+ }
+
+ private static int parseInt(String valStr) {
+ try {
+ return Integer.parseInt(valStr);
+ } catch (NumberFormatException e) {
+ throw new RuntimeException("Value '" + valStr + "' could not be parsed as an integer");
+ }
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java b/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java
new file mode 100644
index 0000000000..bbe9ecfd80
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/function/FunctionMetadataRegistry.java
@@ -0,0 +1,90 @@
+/* ====================================================================
+ 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.function;
+
+import java.util.Map;
+import java.util.Set;
+/**
+ * Allows clients to get {@link FunctionMetadata} instances for any built-in function of Excel.
+ *
+ * @author Josh Micich
+ */
+public final class FunctionMetadataRegistry {
+ /**
+ * The name of the IF function (i.e. "IF"). Extracted as a constant for clarity.
+ */
+ public static final String FUNCTION_NAME_IF = "IF";
+
+ public static final int FUNCTION_INDEX_IF = 1;
+ public static final short FUNCTION_INDEX_SUM = 4;
+ public static final int FUNCTION_INDEX_CHOOSE = 100;
+ public static final short FUNCTION_INDEX_INDIRECT = 148;
+ public static final short FUNCTION_INDEX_EXTERNAL = 255;
+
+ private static FunctionMetadataRegistry _instance;
+
+ private final FunctionMetadata[] _functionDataByIndex;
+ private final Map
+ *
+ *
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public abstract class BooleanFunction implements Function {
+
+ public final ValueEval evaluate(ValueEval[] args, int srcRow, int srcCol) {
+ if (args.length < 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ boolean boolResult;
+ try {
+ boolResult = calculate(args);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ return BoolEval.valueOf(boolResult);
+ }
+
+ private boolean calculate(ValueEval[] args) throws EvaluationException {
+
+ boolean result = getInitialResultValue();
+ boolean atleastOneNonBlank = false;
+
+ /*
+ * Note: no short-circuit boolean loop exit because any ErrorEvals will override the result
+ */
+ for (int i=0, iSize=args.length; i
+ *
+ *
+ * range is the range of cells to count blanks
+ * Syntax: COUNTIF ( range, criteria ) + *
range | is the range of cells to be counted based on the criteria |
---|---|
criteria | is used to determine which cells to count |
null
if the specified value contains no special wildcard characters.
+ */
+ private static Pattern getWildCardPattern(String value) {
+ int len = value.length();
+ StringBuffer sb = new StringBuffer(len);
+ boolean hasWildCard = false;
+ for(int i=0; i
+ * Syntax:
+ * ERROR.TYPE(errorValue)
+ * Returns a number corresponding to the error type of the supplied argument.
+ *+ *
errorValue | Return Value |
#NULL! | 1 |
#DIV/0! | 2 |
#VALUE! | 3 |
#REF! | 4 |
#NAME? | 5 |
#NUM! | 6 |
#N/A! | 7 |
everything else | #N/A! |
+ * p(1+r)^n + y(1+rt)((1+r)^n-1)/r + f=0 ...{when r!=0} + * ny + p + f=0 ...{when r=0} + *+ */ +final class FinanceLib { + + private FinanceLib() { + // no instances of this class + } + + /** + * Future value of an amount given the number of payments, rate, amount + * of individual payment, present value and boolean value indicating whether + * payments are due at the beginning of period + * (false => payments are due at end of period) + * @param r rate + * @param n num of periods + * @param y pmt per period + * @param p future value + * @param t type (true=pmt at end of period, false=pmt at begining of period) + */ + public static double fv(double r, double n, double y, double p, boolean t) { + double retval = 0; + if (r == 0) { + retval = -1*(p+(n*y)); + } + else { + double r1 = r + 1; + retval =((1-Math.pow(r1, n)) * (t ? r1 : 1) * y ) / r + - + p*Math.pow(r1, n); + } + return retval; + } + + /** + * Present value of an amount given the number of future payments, rate, amount + * of individual payment, future value and boolean value indicating whether + * payments are due at the beginning of period + * (false => payments are due at end of period) + * @param r + * @param n + * @param y + * @param f + * @param t + */ + public static double pv(double r, double n, double y, double f, boolean t) { + double retval = 0; + if (r == 0) { + retval = -1*((n*y)+f); + } + else { + double r1 = r + 1; + retval =(( ( 1 - Math.pow(r1, n) ) / r ) * (t ? r1 : 1) * y - f) + / + Math.pow(r1, n); + } + return retval; + } + + /** + * calculates the Net Present Value of a principal amount + * given the discount rate and a sequence of cash flows + * (supplied as an array). If the amounts are income the value should + * be positive, else if they are payments and not income, the + * value should be negative. + * @param r + * @param cfs cashflow amounts + */ + public static double npv(double r, double[] cfs) { + double npv = 0; + double r1 = r + 1; + double trate = r1; + for (int i=0, iSize=cfs.length; i
null
,
+ * nor are any of its elements.
+ * @param ec primarily used to identify the source cell containing the formula being evaluated.
+ * may also be used to dynamically create reference evals.
+ * @return never null
. Possibly an instance of ErrorEval in the case of
+ * a specified Excel error (Exceptions are never thrown to represent Excel errors).
+ */
+ ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Function.java b/src/java/org/apache/poi/ss/formula/functions/Function.java
new file mode 100644
index 0000000000..5f97b81a24
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Function.java
@@ -0,0 +1,43 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.functions;
+
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.MissingArgEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Common interface for all implementations of Excel built-in functions.
+ *
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ */
+public interface Function {
+
+ /**
+ * @param args the evaluated function arguments. Empty values are represented with
+ * {@link BlankEval} or {@link MissingArgEval}, never null
.
+ * @param srcRowIndex row index of the cell containing the formula under evaluation
+ * @param srcColumnIndex column index of the cell containing the formula under evaluation
+ * @return The evaluated result, possibly an {@link ErrorEval}, never null
.
+ * Note - Excel uses the error code #NUM! instead of IEEE NaN, so when
+ * numeric functions evaluate to {@link Double#NaN} be sure to translate the result to {@link
+ * ErrorEval#NUM_ERROR}.
+ */
+ ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Function0Arg.java b/src/java/org/apache/poi/ss/formula/functions/Function0Arg.java
new file mode 100644
index 0000000000..749053ea15
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Function0Arg.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implemented by all functions that can be called with zero arguments
+ *
+ * @author Josh Micich
+ */
+public interface Function0Arg extends Function {
+ /**
+ * see {@link Function#evaluate(ValueEval[], int, int)}
+ */
+ ValueEval evaluate(int srcRowIndex, int srcColumnIndex);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Function1Arg.java b/src/java/org/apache/poi/ss/formula/functions/Function1Arg.java
new file mode 100644
index 0000000000..ec214f4f09
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Function1Arg.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implemented by all functions that can be called with one argument
+ *
+ * @author Josh Micich
+ */
+public interface Function1Arg extends Function {
+ /**
+ * see {@link Function#evaluate(ValueEval[], int, int)}
+ */
+ ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Function2Arg.java b/src/java/org/apache/poi/ss/formula/functions/Function2Arg.java
new file mode 100644
index 0000000000..bd395cc51e
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Function2Arg.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implemented by all functions that can be called with two arguments
+ *
+ * @author Josh Micich
+ */
+public interface Function2Arg extends Function {
+ /**
+ * see {@link Function#evaluate(ValueEval[], int, int)}
+ */
+ ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Function3Arg.java b/src/java/org/apache/poi/ss/formula/functions/Function3Arg.java
new file mode 100644
index 0000000000..ad5eee1586
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Function3Arg.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implemented by all functions that can be called with three arguments
+ *
+ * @author Josh Micich
+ */
+public interface Function3Arg extends Function {
+ /**
+ * see {@link Function#evaluate(ValueEval[], int, int)}
+ */
+ ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Function4Arg.java b/src/java/org/apache/poi/ss/formula/functions/Function4Arg.java
new file mode 100644
index 0000000000..f0eee52241
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Function4Arg.java
@@ -0,0 +1,32 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implemented by all functions that can be called with four arguments
+ *
+ * @author Josh Micich
+ */
+public interface Function4Arg extends Function {
+ /**
+ * see {@link Function#evaluate(ValueEval[], int, int)}
+ */
+ ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2, ValueEval arg3);
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Hlookup.java b/src/java/org/apache/poi/ss/formula/functions/Hlookup.java
new file mode 100644
index 0000000000..c6190dcbac
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Hlookup.java
@@ -0,0 +1,80 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.LookupUtils.ValueVector;
+import org.apache.poi.ss.formula.TwoDEval;
+/**
+ * Implementation of the HLOOKUP() function.
+ *
+ * HLOOKUP finds a column in a lookup table by the first row value and returns the value from another row.
+ *
+ * Syntax :
+ * INDEX ( reference, row_num[, column_num [, area_num]])
+ * INDEX ( array, row_num[, column_num])
+ *
reference | typically an area reference, possibly a union of areas |
---|---|
array | a literal array value (currently not supported) |
row_num | selects the row within the array or area reference |
column_num | selects column within the array or area reference. default is 1 |
area_num | used when reference is a union of areas |
null
if text cannot be parsed.
+ */
+ private static String[] parseWorkbookAndSheetName(CharSequence text) {
+ int lastIx = text.length() - 1;
+ if (lastIx < 0) {
+ return null;
+ }
+ if (canTrim(text)) {
+ return null;
+ }
+ char firstChar = text.charAt(0);
+ if (Character.isWhitespace(firstChar)) {
+ return null;
+ }
+ if (firstChar == '\'') {
+ // workbookName or sheetName needs quoting
+ // quotes go around both
+ if (text.charAt(lastIx) != '\'') {
+ return null;
+ }
+ firstChar = text.charAt(1);
+ if (Character.isWhitespace(firstChar)) {
+ return null;
+ }
+ String wbName;
+ int sheetStartPos;
+ if (firstChar == '[') {
+ int rbPos = text.toString().lastIndexOf(']');
+ if (rbPos < 0) {
+ return null;
+ }
+ wbName = unescapeString(text.subSequence(2, rbPos));
+ if (wbName == null || canTrim(wbName)) {
+ return null;
+ }
+ sheetStartPos = rbPos + 1;
+ } else {
+ wbName = null;
+ sheetStartPos = 1;
+ }
+
+ // else - just sheet name
+ String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
+ if (sheetName == null) { // note - when quoted, sheetName can
+ // start/end with whitespace
+ return null;
+ }
+ return new String[] { wbName, sheetName, };
+ }
+
+ if (firstChar == '[') {
+ int rbPos = text.toString().lastIndexOf(']');
+ if (rbPos < 0) {
+ return null;
+ }
+ CharSequence wbName = text.subSequence(1, rbPos);
+ if (canTrim(wbName)) {
+ return null;
+ }
+ CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
+ if (canTrim(sheetName)) {
+ return null;
+ }
+ return new String[] { wbName.toString(), sheetName.toString(), };
+ }
+ // else - just sheet name
+ return new String[] { null, text.toString(), };
+ }
+
+ /**
+ * @return null
if there is a syntax error in any escape sequence
+ * (the typical syntax error is a single quote character not followed by another).
+ */
+ private static String unescapeString(CharSequence text) {
+ int len = text.length();
+ StringBuilder sb = new StringBuilder(len);
+ int i = 0;
+ while (i < len) {
+ char ch = text.charAt(i);
+ if (ch == '\'') {
+ // every quote must be followed by another
+ i++;
+ if (i >= len) {
+ return null;
+ }
+ ch = text.charAt(i);
+ if (ch != '\'') {
+ return null;
+ }
+ }
+ sb.append(ch);
+ i++;
+ }
+ return sb.toString();
+ }
+
+ private static boolean canTrim(CharSequence text) {
+ int lastIx = text.length() - 1;
+ if (lastIx < 0) {
+ return false;
+ }
+ if (Character.isWhitespace(text.charAt(0))) {
+ return true;
+ }
+ if (Character.isWhitespace(text.charAt(lastIx))) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/LogicalFunction.java b/src/java/org/apache/poi/ss/formula/functions/LogicalFunction.java
new file mode 100644
index 0000000000..ef18dab645
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/LogicalFunction.java
@@ -0,0 +1,119 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.AreaEval;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.RefEval;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ * @author Josh Micich
+ */
+public abstract class LogicalFunction extends Fixed1ArgFunction {
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+ ValueEval ve;
+ try {
+ ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
+ } catch (EvaluationException e) {
+ if (false) {
+ // Note - it is more usual to propagate error codes straight to the result like this:
+ return e.getErrorEval();
+ // but logical functions behave a little differently
+ }
+ // this will usually cause a 'FALSE' result except for ISNONTEXT()
+ ve = e.getErrorEval();
+ }
+ return BoolEval.valueOf(evaluate(ve));
+
+ }
+ /**
+ * @param arg any {@link ValueEval}, potentially {@link BlankEval} or {@link ErrorEval}.
+ */
+ protected abstract boolean evaluate(ValueEval arg);
+
+ public static final Function ISLOGICAL = new LogicalFunction() {
+ protected boolean evaluate(ValueEval arg) {
+ return arg instanceof BoolEval;
+ }
+ };
+ public static final Function ISNONTEXT = new LogicalFunction() {
+ protected boolean evaluate(ValueEval arg) {
+ return !(arg instanceof StringEval);
+ }
+ };
+ public static final Function ISNUMBER = new LogicalFunction() {
+ protected boolean evaluate(ValueEval arg) {
+ return arg instanceof NumberEval;
+ }
+ };
+ public static final Function ISTEXT = new LogicalFunction() {
+ protected boolean evaluate(ValueEval arg) {
+ return arg instanceof StringEval;
+ }
+ };
+
+ public static final Function ISBLANK = new LogicalFunction() {
+
+ protected boolean evaluate(ValueEval arg) {
+ return arg instanceof BlankEval;
+ }
+ };
+
+ public static final Function ISERROR = new LogicalFunction() {
+
+ protected boolean evaluate(ValueEval arg) {
+ return arg instanceof ErrorEval;
+ }
+ };
+
+ /**
+ * Implementation for Excel ISNA() function.
+ *
+ * Syntax:null
if the supplied area is neither a single row nor a single colum
+ */
+ public static ValueVector createVector(TwoDEval ae) {
+ if (ae.isColumn()) {
+ return createColumnVector(ae, 0);
+ }
+ if (ae.isRow()) {
+ return createRowVector(ae, 0);
+ }
+ return null;
+ }
+
+ /**
+ * Enumeration to support 4 valued comparison results.
+ * Excel lookup functions have complex behaviour in the case where the lookup array has mixed
+ * types, and/or is unordered. Contrary to suggestions in some Excel documentation, there
+ * does not appear to be a universal ordering across types. The binary search algorithm used
+ * changes behaviour when the evaluated 'mid' value has a different type to the lookup value.
+ *
+ * A simple int might have done the same job, but there is risk in confusion with the well
+ * known Comparable.compareTo() and Comparator.compare() which both use
+ * a ubiquitous 3 value result encoding.
+ */
+ public static final class CompareResult {
+ private final boolean _isTypeMismatch;
+ private final boolean _isLessThan;
+ private final boolean _isEqual;
+ private final boolean _isGreaterThan;
+
+ private CompareResult(boolean isTypeMismatch, int simpleCompareResult) {
+ if(isTypeMismatch) {
+ _isTypeMismatch = true;
+ _isLessThan = false;
+ _isEqual = false;
+ _isGreaterThan = false;
+ } else {
+ _isTypeMismatch = false;
+ _isLessThan = simpleCompareResult < 0;
+ _isEqual = simpleCompareResult == 0;
+ _isGreaterThan = simpleCompareResult > 0;
+ }
+ }
+ public static final CompareResult TYPE_MISMATCH = new CompareResult(true, 0);
+ public static final CompareResult LESS_THAN = new CompareResult(false, -1);
+ public static final CompareResult EQUAL = new CompareResult(false, 0);
+ public static final CompareResult GREATER_THAN = new CompareResult(false, +1);
+
+ public static final CompareResult valueOf(int simpleCompareResult) {
+ if(simpleCompareResult < 0) {
+ return LESS_THAN;
+ }
+ if(simpleCompareResult > 0) {
+ return GREATER_THAN;
+ }
+ return EQUAL;
+ }
+
+ public boolean isTypeMismatch() {
+ return _isTypeMismatch;
+ }
+ public boolean isLessThan() {
+ return _isLessThan;
+ }
+ public boolean isEqual() {
+ return _isEqual;
+ }
+ public boolean isGreaterThan() {
+ return _isGreaterThan;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(formatAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private String formatAsString() {
+ if(_isTypeMismatch) {
+ return "TYPE_MISMATCH";
+ }
+ if(_isLessThan) {
+ return "LESS_THAN";
+ }
+ if(_isEqual) {
+ return "EQUAL";
+ }
+ if(_isGreaterThan) {
+ return "GREATER_THAN";
+ }
+ // toString must be reliable
+ return "??error??";
+ }
+ }
+
+ public interface LookupValueComparer {
+ /**
+ * @return one of 4 instances or CompareResult: LESS_THAN, EQUAL,
+ * GREATER_THAN or TYPE_MISMATCH
+ */
+ CompareResult compareTo(ValueEval other);
+ }
+
+ private static abstract class LookupValueComparerBase implements LookupValueComparer {
+
+ private final Class extends ValueEval> _targetClass;
+ protected LookupValueComparerBase(ValueEval targetValue) {
+ if(targetValue == null) {
+ throw new RuntimeException("targetValue cannot be null");
+ }
+ _targetClass = targetValue.getClass();
+ }
+ public final CompareResult compareTo(ValueEval other) {
+ if (other == null) {
+ throw new RuntimeException("compare to value cannot be null");
+ }
+ if (_targetClass != other.getClass()) {
+ return CompareResult.TYPE_MISMATCH;
+ }
+ return compareSameType(other);
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(getValueAsString());
+ sb.append("]");
+ return sb.toString();
+ }
+ protected abstract CompareResult compareSameType(ValueEval other);
+ /** used only for debug purposes */
+ protected abstract String getValueAsString();
+ }
+
+ private static final class StringLookupComparer extends LookupValueComparerBase {
+ private String _value;
+
+ protected StringLookupComparer(StringEval se) {
+ super(se);
+ _value = se.getStringValue();
+ }
+ protected CompareResult compareSameType(ValueEval other) {
+ StringEval se = (StringEval) other;
+ return CompareResult.valueOf(_value.compareToIgnoreCase(se.getStringValue()));
+ }
+ protected String getValueAsString() {
+ return _value;
+ }
+ }
+ private static final class NumberLookupComparer extends LookupValueComparerBase {
+ private double _value;
+
+ protected NumberLookupComparer(NumberEval ne) {
+ super(ne);
+ _value = ne.getNumberValue();
+ }
+ protected CompareResult compareSameType(ValueEval other) {
+ NumberEval ne = (NumberEval) other;
+ return CompareResult.valueOf(Double.compare(_value, ne.getNumberValue()));
+ }
+ protected String getValueAsString() {
+ return String.valueOf(_value);
+ }
+ }
+ private static final class BooleanLookupComparer extends LookupValueComparerBase {
+ private boolean _value;
+
+ protected BooleanLookupComparer(BoolEval be) {
+ super(be);
+ _value = be.getBooleanValue();
+ }
+ protected CompareResult compareSameType(ValueEval other) {
+ BoolEval be = (BoolEval) other;
+ boolean otherVal = be.getBooleanValue();
+ if(_value == otherVal) {
+ return CompareResult.EQUAL;
+ }
+ // TRUE > FALSE
+ if(_value) {
+ return CompareResult.GREATER_THAN;
+ }
+ return CompareResult.LESS_THAN;
+ }
+ protected String getValueAsString() {
+ return String.valueOf(_value);
+ }
+ }
+
+ /**
+ * Processes the third argument to VLOOKUP, or HLOOKUP (col_index_num
+ * or row_index_num respectively).Input Return | Value | Thrown Error |
---|---|---|
5 | 4 | |
2.9 | 2 | |
"5" | 4 | |
"2.18e1" | 21 | |
"-$2" | -3 | * |
FALSE | -1 | * |
TRUE | 0 | |
"TRUE" | #REF! | |
"abc" | #REF! | |
"" | #REF! | |
<blank> | #VALUE! |
null
+ */
+ public static boolean resolveRangeLookupArg(ValueEval rangeLookupArg, int srcCellRow, int srcCellCol) throws EvaluationException {
+
+ ValueEval valEval = OperandResolver.getSingleValue(rangeLookupArg, srcCellRow, srcCellCol);
+ if(valEval instanceof BlankEval) {
+ // Tricky:
+ // fourth arg supplied but evaluates to blank
+ // this does not get the default value
+ return false;
+ }
+ if(valEval instanceof BoolEval) {
+ // Happy day flow
+ BoolEval boolEval = (BoolEval) valEval;
+ return boolEval.getBooleanValue();
+ }
+
+ if (valEval instanceof StringEval) {
+ String stringValue = ((StringEval) valEval).getStringValue();
+ if(stringValue.length() < 1) {
+ // More trickiness:
+ // Empty string is not the same as BlankEval. It causes #VALUE! error
+ throw EvaluationException.invalidValue();
+ }
+ // TODO move parseBoolean to OperandResolver
+ Boolean b = Countif.parseBoolean(stringValue);
+ if(b != null) {
+ // string converted to boolean OK
+ return b.booleanValue();
+ }
+ // Even more trickiness:
+ // Note - even if the StringEval represents a number value (for example "1"),
+ // Excel does not resolve it to a boolean.
+ throw EvaluationException.invalidValue();
+ // This is in contrast to the code below,, where NumberEvals values (for
+ // example 0.01) *do* resolve to equivalent boolean values.
+ }
+ if (valEval instanceof NumericValueEval) {
+ NumericValueEval nve = (NumericValueEval) valEval;
+ // zero is FALSE, everything else is TRUE
+ return 0.0 != nve.getNumberValue();
+ }
+ throw new RuntimeException("Unexpected eval type (" + valEval.getClass().getName() + ")");
+ }
+
+ public static int lookupIndexOfValue(ValueEval lookupValue, ValueVector vector, boolean isRangeLookup) throws EvaluationException {
+ LookupValueComparer lookupComparer = createLookupComparer(lookupValue);
+ int result;
+ if(isRangeLookup) {
+ result = performBinarySearch(vector, lookupComparer);
+ } else {
+ result = lookupIndexOfExactValue(lookupComparer, vector);
+ }
+ if(result < 0) {
+ throw new EvaluationException(ErrorEval.NA);
+ }
+ return result;
+ }
+
+
+ /**
+ * Finds first (lowest index) exact occurrence of specified value.
+ * @param lookupValue the value to be found in column or row vector
+ * @param vector the values to be searched. For VLOOKUP this is the first column of the
+ * tableArray. For HLOOKUP this is the first row of the tableArray.
+ * @return zero based index into the vector, -1 if value cannot be found
+ */
+ private static int lookupIndexOfExactValue(LookupValueComparer lookupComparer, ValueVector vector) {
+
+ // find first occurrence of lookup value
+ int size = vector.getSize();
+ for (int i = 0; i < size; i++) {
+ if(lookupComparer.compareTo(vector.getItem(i)).isEqual()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Encapsulates some standard binary search functionality so the unusual Excel behaviour can
+ * be clearly distinguished.
+ */
+ private static final class BinarySearchIndexes {
+
+ private int _lowIx;
+ private int _highIx;
+
+ public BinarySearchIndexes(int highIx) {
+ _lowIx = -1;
+ _highIx = highIx;
+ }
+
+ /**
+ * @return -1 if the search range is empty
+ */
+ public int getMidIx() {
+ int ixDiff = _highIx - _lowIx;
+ if(ixDiff < 2) {
+ return -1;
+ }
+ return _lowIx + (ixDiff / 2);
+ }
+
+ public int getLowIx() {
+ return _lowIx;
+ }
+ public int getHighIx() {
+ return _highIx;
+ }
+ public void narrowSearch(int midIx, boolean isLessThan) {
+ if(isLessThan) {
+ _highIx = midIx;
+ } else {
+ _lowIx = midIx;
+ }
+ }
+ }
+ /**
+ * Excel has funny behaviour when the some elements in the search vector are the wrong type.
+ *
+ */
+ private static int performBinarySearch(ValueVector vector, LookupValueComparer lookupComparer) {
+ // both low and high indexes point to values assumed too low and too high.
+ BinarySearchIndexes bsi = new BinarySearchIndexes(vector.getSize());
+
+ while(true) {
+ int midIx = bsi.getMidIx();
+
+ if(midIx < 0) {
+ return bsi.getLowIx();
+ }
+ CompareResult cr = lookupComparer.compareTo(vector.getItem(midIx));
+ if(cr.isTypeMismatch()) {
+ int newMidIx = handleMidValueTypeMismatch(lookupComparer, vector, bsi, midIx);
+ if(newMidIx < 0) {
+ continue;
+ }
+ midIx = newMidIx;
+ cr = lookupComparer.compareTo(vector.getItem(midIx));
+ }
+ if(cr.isEqual()) {
+ return findLastIndexInRunOfEqualValues(lookupComparer, vector, midIx, bsi.getHighIx());
+ }
+ bsi.narrowSearch(midIx, cr.isLessThan());
+ }
+ }
+ /**
+ * Excel seems to handle mismatched types initially by just stepping 'mid' ix forward to the
+ * first compatible value.
+ * @param midIx 'mid' index (value which has the wrong type)
+ * @return usually -1, signifying that the BinarySearchIndex has been narrowed to the new mid
+ * index. Zero or greater signifies that an exact match for the lookup value was found
+ */
+ private static int handleMidValueTypeMismatch(LookupValueComparer lookupComparer, ValueVector vector,
+ BinarySearchIndexes bsi, int midIx) {
+ int newMid = midIx;
+ int highIx = bsi.getHighIx();
+
+ while(true) {
+ newMid++;
+ if(newMid == highIx) {
+ // every element from midIx to highIx was the wrong type
+ // move highIx down to the low end of the mid values
+ bsi.narrowSearch(midIx, true);
+ return -1;
+ }
+ CompareResult cr = lookupComparer.compareTo(vector.getItem(newMid));
+ if(cr.isLessThan() && newMid == highIx-1) {
+ // move highIx down to the low end of the mid values
+ bsi.narrowSearch(midIx, true);
+ return -1;
+ // but only when "newMid == highIx-1"? slightly weird.
+ // It would seem more efficient to always do this.
+ }
+ if(cr.isTypeMismatch()) {
+ // keep stepping over values until the right type is found
+ continue;
+ }
+ if(cr.isEqual()) {
+ return newMid;
+ }
+ // Note - if moving highIx down (due to lookupValue | Matching Behaviour |
---|---|
1 | (default) find the largest value that is less than or equal to lookup_value. + * The lookup_array must be in ascending order*. |
0 | find the first value that is exactly equal to lookup_value. + * The lookup_array can be in any order. |
-1 | find the smallest value that is greater than or equal to lookup_value. + * The lookup_array must be in descending order*. |
If n is negative, the resulting value is obtained + * as the round value of absolute value of n multiplied + * by the sign value of n (@see MathX.sign(double d)). + * Thus, -0.6666666 rounded to p=0 will give -1 not 0. + *
If n is NaN, returned value is NaN. + * @param n + * @param p + */ + public static double round(double n, int p) { + double retval; + + if (Double.isNaN(n) || Double.isInfinite(n)) { + retval = Double.NaN; + } + else { + if (p != 0) { + double temp = Math.pow(10, p); + retval = Math.round(n*temp)/temp; + } + else { + retval = Math.round(n); + } + } + + return retval; + } + + /** + * Returns a value rounded-up to p digits after decimal. + * If p is negative, then the number is rounded to + * places to the left of the decimal point. eg. + * 10.23 rounded to -1 will give: 20. If p is zero, + * the returned value is rounded to the nearest integral + * value. + *
If n is negative, the resulting value is obtained + * as the round-up value of absolute value of n multiplied + * by the sign value of n (@see MathX.sign(double d)). + * Thus, -0.2 rounded-up to p=0 will give -1 not 0. + *
If n is NaN, returned value is NaN. + * @param n + * @param p + */ + public static double roundUp(double n, int p) { + double retval; + + if (Double.isNaN(n) || Double.isInfinite(n)) { + retval = Double.NaN; + } + else { + if (p != 0) { + double temp = Math.pow(10, p); + double nat = Math.abs(n*temp); + + retval = sign(n) * + ((nat == (long) nat) + ? nat / temp + : Math.round(nat + 0.5) / temp); + } + else { + double na = Math.abs(n); + retval = sign(n) * + ((na == (long) na) + ? na + : (long) na + 1); + } + } + + return retval; + } + + /** + * Returns a value rounded to p digits after decimal. + * If p is negative, then the number is rounded to + * places to the left of the decimal point. eg. + * 10.23 rounded to -1 will give: 10. If p is zero, + * the returned value is rounded to the nearest integral + * value. + *
If n is negative, the resulting value is obtained + * as the round-up value of absolute value of n multiplied + * by the sign value of n (@see MathX.sign(double d)). + * Thus, -0.8 rounded-down to p=0 will give 0 not -1. + *
If n is NaN, returned value is NaN.
+ * @param n
+ * @param p
+ */
+ public static double roundDown(double n, int p) {
+ double retval;
+
+ if (Double.isNaN(n) || Double.isInfinite(n)) {
+ retval = Double.NaN;
+ }
+ else {
+ if (p != 0) {
+ double temp = Math.pow(10, p);
+ retval = sign(n) * Math.round((Math.abs(n)*temp) - 0.5)/temp;
+ }
+ else {
+ retval = (long) n;
+ }
+ }
+
+ return retval;
+ }
+
+
+ /**
+ * If d < 0, returns short -1
+ *
+ * If d > 0, returns short 1
+ *
+ * If d == 0, returns short 0
+ *
If d is NaN, then 1 will be returned. It is the responsibility
+ * of caller to check for d isNaN if some other value is desired.
+ * @param d
+ */
+ public static short sign(double d) {
+ return (short) ((d == 0)
+ ? 0
+ : (d < 0)
+ ? -1
+ : 1);
+ }
+
+ /**
+ * average of all values
+ * @param values
+ */
+ public static double average(double[] values) {
+ double ave = 0;
+ double sum = 0;
+ for (int i=0, iSize=values.length; i
+ * When n and s are "valid" arguments, the returned value is: Math.ceiling(n/s) * s;
+ *
+ *
+ * Syntax :
+ * n and s are invalid if any of following conditions are true:
+ *
+ *
+ * In all such cases, Double.NaN is returned.
+ * @param n
+ * @param s
+ */
+ public static double floor(double n, double s) {
+ double f;
+
+ if ((n<0 && s>0) || (n>0 && s<0) || (s==0 && n!=0)) {
+ f = Double.NaN;
+ }
+ else {
+ f = (n==0 || s==0) ? 0 : Math.floor(n/s) * s;
+ }
+
+ return f;
+ }
+
+ /**
+ * Note: this function is different from java.lang.Math.ceil(..).
+ *
+ * n and s are invalid if any of following conditions are true:
+ *
+ *
+ * In all such cases, Double.NaN is returned.
+ * @param n
+ * @param s
+ */
+ public static double ceiling(double n, double s) {
+ double c;
+
+ if ((n<0 && s>0) || (n>0 && s<0)) {
+ c = Double.NaN;
+ }
+ else {
+ c = (n == 0 || s == 0) ? 0 : Math.ceil(n/s) * s;
+ }
+
+ return c;
+ }
+
+ /**
+ *
for all n >= 1; factorial n = n * (n-1) * (n-2) * ... * 1
+ *
else if n == 0; factorial n = 1
+ *
else if n < 0; factorial n = Double.NaN
+ *
Loss of precision can occur if n is large enough.
+ * If n is large so that the resulting value would be greater
+ * than Double.MAX_VALUE; Double.POSITIVE_INFINITY is returned.
+ * If n < 0, Double.NaN is returned.
+ * @param n
+ */
+ public static double factorial(int n) {
+ double d = 1;
+
+ if (n >= 0) {
+ if (n <= 170) {
+ for (int i=1; i<=n; i++) {
+ d *= i;
+ }
+ }
+ else {
+ d = Double.POSITIVE_INFINITY;
+ }
+ }
+ else {
+ d = Double.NaN;
+ }
+ return d;
+ }
+
+
+ /**
+ * returns the remainder resulting from operation:
+ * n / d.
+ *
The result has the sign of the divisor.
+ *
Examples:
+ *
+ *
+ * If d == 0, result is NaN
+ * @param n
+ * @param d
+ */
+ public static double mod(double n, double d) {
+ double result = 0;
+
+ if (d == 0) {
+ result = Double.NaN;
+ }
+ else if (sign(n) == sign(d)) {
+ result = n % d;
+ }
+ else {
+ result = ((n % d) + d) % d;
+ }
+
+ return result;
+ }
+
+ /**
+ * inverse hyperbolic cosine
+ * @param d
+ */
+ public static double acosh(double d) {
+ return Math.log(Math.sqrt(Math.pow(d, 2) - 1) + d);
+ }
+
+ /**
+ * inverse hyperbolic sine
+ * @param d
+ */
+ public static double asinh(double d) {
+ return Math.log(Math.sqrt(d*d + 1) + d);
+ }
+
+ /**
+ * inverse hyperbolic tangent
+ * @param d
+ */
+ public static double atanh(double d) {
+ return Math.log((1 + d)/(1 - d)) / 2;
+ }
+
+ /**
+ * hyperbolic cosine
+ * @param d
+ */
+ public static double cosh(double d) {
+ double ePowX = Math.pow(Math.E, d);
+ double ePowNegX = Math.pow(Math.E, -d);
+ return (ePowX + ePowNegX) / 2;
+ }
+
+ /**
+ * hyperbolic sine
+ * @param d
+ */
+ public static double sinh(double d) {
+ double ePowX = Math.pow(Math.E, d);
+ double ePowNegX = Math.pow(Math.E, -d);
+ return (ePowX - ePowNegX) / 2;
+ }
+
+ /**
+ * hyperbolic tangent
+ * @param d
+ */
+ public static double tanh(double d) {
+ double ePowX = Math.pow(Math.E, d);
+ double ePowNegX = Math.pow(Math.E, -d);
+ return (ePowX - ePowNegX) / (ePowX + ePowNegX);
+ }
+
+
+ /**
+ * returns the total number of combinations possible when
+ * k items are chosen out of total of n items. If the number
+ * is too large, loss of precision may occur (since returned
+ * value is double). If the returned value is larger than
+ * Double.MAX_VALUE, Double.POSITIVE_INFINITY is returned.
+ * If either of the parameters is negative, Double.NaN is returned.
+ * @param n
+ * @param k
+ */
+ public static double nChooseK(int n, int k) {
+ double d = 1;
+ if (n<0 || k<0 || nnull
.
+ *
+ * @return never null
+ */
+ protected final double[] getNumberArray(ValueEval[] operands) throws EvaluationException {
+ if (operands.length > getMaxNumOperands()) {
+ throw EvaluationException.invalidValue();
+ }
+ DoubleList retval = new DoubleList();
+
+ for (int i=0, iSize=operands.length; i
+ * OFFSET(reference, rows, cols, height, width)
+ * reference is the base reference.
+ * rows is the number of rows up or down from the base reference.
+ * cols is the number of columns left or right from the base reference.
+ * height (default same height as base reference) is the row count for the returned area reference.
+ * width (default same width as base reference) is the column count for the returned area reference.
+ *
+ * @author Josh Micich
+ */
+public final class Offset implements Function {
+ // These values are specific to BIFF8
+ private static final int LAST_VALID_ROW_INDEX = 0xFFFF;
+ private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
+
+
+ /**
+ * A one dimensional base + offset. Represents either a row range or a column range.
+ * Two instances of this class together specify an area range.
+ */
+ /* package */ static final class LinearOffsetRange {
+
+ private final int _offset;
+ private final int _length;
+
+ public LinearOffsetRange(int offset, int length) {
+ if(length == 0) {
+ // handled that condition much earlier
+ throw new RuntimeException("length may not be zero");
+ }
+ _offset = offset;
+ _length = length;
+ }
+
+ public short getFirstIndex() {
+ return (short) _offset;
+ }
+ public short getLastIndex() {
+ return (short) (_offset + _length - 1);
+ }
+ /**
+ * Moves the range by the specified translation amount.
+ *
+ * This method also 'normalises' the range: Excel specifies that the width and height
+ * parameters (length field here) cannot be negative. However, OFFSET() does produce
+ * sensible results in these cases. That behavior is replicated here.
+ *
+ * @param translationAmount may be zero negative or positive
+ *
+ * @return the equivalent LinearOffsetRange with a positive length, moved by the
+ * specified translationAmount.
+ */
+ public LinearOffsetRange normaliseAndTranslate(int translationAmount) {
+ if (_length > 0) {
+ if(translationAmount == 0) {
+ return this;
+ }
+ return new LinearOffsetRange(translationAmount + _offset, _length);
+ }
+ return new LinearOffsetRange(translationAmount + _offset + _length + 1, -_length);
+ }
+
+ public boolean isOutOfBounds(int lowValidIx, int highValidIx) {
+ if(_offset < lowValidIx) {
+ return true;
+ }
+ if(getLastIndex() > highValidIx) {
+ return true;
+ }
+ return false;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_offset).append("...").append(getLastIndex());
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Encapsulates either an area or cell reference which may be 2d or 3d.
+ */
+ private static final class BaseRef {
+ private final int _firstRowIndex;
+ private final int _firstColumnIndex;
+ private final int _width;
+ private final int _height;
+ private final RefEval _refEval;
+ private final AreaEval _areaEval;
+
+ public BaseRef(RefEval re) {
+ _refEval = re;
+ _areaEval = null;
+ _firstRowIndex = re.getRow();
+ _firstColumnIndex = re.getColumn();
+ _height = 1;
+ _width = 1;
+ }
+
+ public BaseRef(AreaEval ae) {
+ _refEval = null;
+ _areaEval = ae;
+ _firstRowIndex = ae.getFirstRow();
+ _firstColumnIndex = ae.getFirstColumn();
+ _height = ae.getLastRow() - ae.getFirstRow() + 1;
+ _width = ae.getLastColumn() - ae.getFirstColumn() + 1;
+ }
+
+ public int getWidth() {
+ return _width;
+ }
+ public int getHeight() {
+ return _height;
+ }
+ public int getFirstRowIndex() {
+ return _firstRowIndex;
+ }
+ public int getFirstColumnIndex() {
+ return _firstColumnIndex;
+ }
+
+ public AreaEval offset(int relFirstRowIx, int relLastRowIx,
+ int relFirstColIx, int relLastColIx) {
+ if (_refEval == null) {
+ return _areaEval.offset(relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
+ }
+ return _refEval.offset(relFirstRowIx, relLastRowIx, relFirstColIx, relLastColIx);
+ }
+ }
+
+ public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) {
+ if(args.length < 3 || args.length > 5) {
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ try {
+ BaseRef baseRef = evaluateBaseRef(args[0]);
+ int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
+ int columnOffset = evaluateIntArg(args[2], srcCellRow, srcCellCol);
+ int height = baseRef.getHeight();
+ int width = baseRef.getWidth();
+ switch(args.length) {
+ case 5:
+ width = evaluateIntArg(args[4], srcCellRow, srcCellCol);
+ case 4:
+ height = evaluateIntArg(args[3], srcCellRow, srcCellCol);
+ }
+ // Zero height or width raises #REF! error
+ if(height == 0 || width == 0) {
+ return ErrorEval.REF_INVALID;
+ }
+ LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
+ LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
+ return createOffset(baseRef, rowOffsetRange, colOffsetRange);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ }
+
+ private static AreaEval createOffset(BaseRef baseRef,
+ LinearOffsetRange orRow, LinearOffsetRange orCol) throws EvaluationException {
+ LinearOffsetRange absRows = orRow.normaliseAndTranslate(baseRef.getFirstRowIndex());
+ LinearOffsetRange absCols = orCol.normaliseAndTranslate(baseRef.getFirstColumnIndex());
+
+ if(absRows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
+ throw new EvaluationException(ErrorEval.REF_INVALID);
+ }
+ if(absCols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
+ throw new EvaluationException(ErrorEval.REF_INVALID);
+ }
+ return baseRef.offset(orRow.getFirstIndex(), orRow.getLastIndex(), orCol.getFirstIndex(), orCol.getLastIndex());
+ }
+
+ private static BaseRef evaluateBaseRef(ValueEval eval) throws EvaluationException {
+
+ if(eval instanceof RefEval) {
+ return new BaseRef((RefEval)eval);
+ }
+ if(eval instanceof AreaEval) {
+ return new BaseRef((AreaEval)eval);
+ }
+ if (eval instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval) eval);
+ }
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+
+ /**
+ * OFFSET's numeric arguments (2..5) have similar processing rules
+ */
+ static int evaluateIntArg(ValueEval eval, int srcCellRow, int srcCellCol) throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol);
+ return OperandResolver.coerceValueToInt(ve);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Replace.java b/src/java/org/apache/poi/ss/formula/functions/Replace.java
new file mode 100644
index 0000000000..dd366dbd25
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Replace.java
@@ -0,0 +1,74 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * An implementation of the Excel REPLACE() function:
+ * Replaces part of a text string based on the number of characters
+ * you specify, with another text string.
+ *
+ * Syntax:
+ * REPLACE(oldText, startNum, numChars, newText)
+ *
+ * oldText The text string containing characters to replace
+ * startNum The position of the first character to replace (1-based)
+ * numChars The number of characters to replace
+ * newText The new text value to replace the removed section
+ *
+ * @author Manda Wilson < wilson at c bio dot msk cc dot org >
+ */
+public final class Replace extends Fixed4ArgFunction {
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
+ ValueEval arg2, ValueEval arg3) {
+
+ String oldStr;
+ int startNum;
+ int numChars;
+ String newStr;
+ try {
+ oldStr = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex);
+ startNum = TextFunction.evaluateIntArg(arg1, srcRowIndex, srcColumnIndex);
+ numChars = TextFunction.evaluateIntArg(arg2, srcRowIndex, srcColumnIndex);
+ newStr = TextFunction.evaluateStringArg(arg3, srcRowIndex, srcColumnIndex);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+
+ if (startNum < 1 || numChars < 0) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ StringBuffer strBuff = new StringBuffer(oldStr);
+ // remove any characters that should be replaced
+ if (startNum <= oldStr.length() && numChars != 0) {
+ strBuff.delete(startNum - 1, startNum - 1 + numChars);
+ }
+ // now insert (or append) newStr
+ if (startNum > strBuff.length()) {
+ strBuff.append(newStr);
+ } else {
+ strBuff.insert(startNum - 1, newStr);
+ }
+ return new StringEval(strBuff.toString());
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/RowFunc.java b/src/java/org/apache/poi/ss/formula/functions/RowFunc.java
new file mode 100644
index 0000000000..f0d468fb67
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/RowFunc.java
@@ -0,0 +1,59 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.AreaEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.RefEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Implementation for the Excel function ROW
+ *
+ * @author Josh Micich
+ */
+public final class RowFunc implements Function0Arg, Function1Arg {
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) {
+ return new NumberEval(srcRowIndex+1);
+ }
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+ int rnum;
+
+ if (arg0 instanceof AreaEval) {
+ rnum = ((AreaEval) arg0).getFirstRow();
+ } else if (arg0 instanceof RefEval) {
+ rnum = ((RefEval) arg0).getRow();
+ } else {
+ // anything else is not valid argument
+ return ErrorEval.VALUE_INVALID;
+ }
+
+ return new NumberEval(rnum + 1);
+ }
+ public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+ switch (args.length) {
+ case 1:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0]);
+ case 0:
+ return new NumberEval(srcRowIndex+1);
+ }
+ return ErrorEval.VALUE_INVALID;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Rows.java b/src/java/org/apache/poi/ss/formula/functions/Rows.java
new file mode 100644
index 0000000000..1e363222ab
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Rows.java
@@ -0,0 +1,45 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.RefEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.TwoDEval;
+
+/**
+ * Implementation for Excel ROWS function.
+ *
+ * @author Josh Micich
+ */
+public final class Rows extends Fixed1ArgFunction {
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
+
+ int result;
+ if (arg0 instanceof TwoDEval) {
+ result = ((TwoDEval) arg0).getHeight();
+ } else if (arg0 instanceof RefEval) {
+ result = 1;
+ } else { // anything else is not valid argument
+ return ErrorEval.VALUE_INVALID;
+ }
+ return new NumberEval(result);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/StatsLib.java b/src/java/org/apache/poi/ss/formula/functions/StatsLib.java
new file mode 100644
index 0000000000..645f7e4384
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/StatsLib.java
@@ -0,0 +1,137 @@
+/* ====================================================================
+ 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.functions;
+
+import java.util.Arrays;
+
+/**
+ * @author Amol S. Deshmukh < amolweb at ya hoo dot com >
+ *
+ * Library for common statistics functions
+ */
+final class StatsLib {
+
+ private StatsLib() {
+ // no instances of this class
+ }
+
+
+ /**
+ * returns the mean of deviations from mean.
+ * @param v
+ */
+ public static double avedev(double[] v) {
+ double r = 0;
+ double m = 0;
+ double s = 0;
+ for (int i=0, iSize=v.length; i
+ * k <= 0 & k >= v.length or null array or empty array
+ * will result in return value Double.NaN
+ * @param v
+ * @param k
+ */
+ public static double kthSmallest(double[] v, int k) {
+ double r = Double.NaN;
+ int index = k-1; // since arrays are 0-based
+ if (v!=null && v.length > index && index >= 0) {
+ Arrays.sort(v);
+ r = v[index];
+ }
+ return r;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Substitute.java b/src/java/org/apache/poi/ss/formula/functions/Substitute.java
new file mode 100644
index 0000000000..23954b1f0a
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Substitute.java
@@ -0,0 +1,108 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * An implementation of the SUBSTITUTE function:
+ * Substitutes text in a text string with new text, some number of times.
+ * @author Manda Wilson < wilson at c bio dot msk cc dot org >
+ */
+public final class Substitute extends Var3or4ArgFunction {
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
+ ValueEval arg2) {
+ String result;
+ try {
+ String oldStr = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex);
+ String searchStr = TextFunction.evaluateStringArg(arg1, srcRowIndex, srcColumnIndex);
+ String newStr = TextFunction.evaluateStringArg(arg2, srcRowIndex, srcColumnIndex);
+
+ result = replaceAllOccurrences(oldStr, searchStr, newStr);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ return new StringEval(result);
+ }
+
+ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1,
+ ValueEval arg2, ValueEval arg3) {
+ String result;
+ try {
+ String oldStr = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex);
+ String searchStr = TextFunction.evaluateStringArg(arg1, srcRowIndex, srcColumnIndex);
+ String newStr = TextFunction.evaluateStringArg(arg2, srcRowIndex, srcColumnIndex);
+
+ int instanceNumber = TextFunction.evaluateIntArg(arg3, srcRowIndex, srcColumnIndex);
+ if (instanceNumber < 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
+ result = replaceOneOccurrence(oldStr, searchStr, newStr, instanceNumber);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ return new StringEval(result);
+ }
+
+ private static String replaceAllOccurrences(String oldStr, String searchStr, String newStr) {
+ StringBuffer sb = new StringBuffer();
+ int startIndex = 0;
+ int nextMatch = -1;
+ while (true) {
+ nextMatch = oldStr.indexOf(searchStr, startIndex);
+ if (nextMatch < 0) {
+ // store everything from end of last match to end of string
+ sb.append(oldStr.substring(startIndex));
+ return sb.toString();
+ }
+ // store everything from end of last match to start of this match
+ sb.append(oldStr.substring(startIndex, nextMatch));
+ sb.append(newStr);
+ startIndex = nextMatch + searchStr.length();
+ }
+ }
+
+ private static String replaceOneOccurrence(String oldStr, String searchStr, String newStr, int instanceNumber) {
+ if (searchStr.length() < 1) {
+ return oldStr;
+ }
+ int startIndex = 0;
+ int nextMatch = -1;
+ int count=0;
+ while (true) {
+ nextMatch = oldStr.indexOf(searchStr, startIndex);
+ if (nextMatch < 0) {
+ // not enough occurrences found - leave unchanged
+ return oldStr;
+ }
+ count++;
+ if (count == instanceNumber) {
+ StringBuffer sb = new StringBuffer(oldStr.length() + newStr.length());
+ sb.append(oldStr.substring(0, nextMatch));
+ sb.append(newStr);
+ sb.append(oldStr.substring(nextMatch + searchStr.length()));
+ return sb.toString();
+ }
+ startIndex = nextMatch + searchStr.length();
+ }
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Subtotal.java b/src/java/org/apache/poi/ss/formula/functions/Subtotal.java
new file mode 100644
index 0000000000..8bbbfeea78
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Subtotal.java
@@ -0,0 +1,100 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.eval.NotImplementedException;
+
+/**
+ * Implementation for the Excel function SUBTOTAL
+ * SUBTOTAL ( functionCode, ref1, ref2 ... )
+ *
+ *
+ * functionCode (1-11) Selects the underlying aggregate function to be used (see table below)
+ * ref1, ref2 ... Arguments to be passed to the underlying aggregate function
+ *
functionCode | Aggregate Function |
---|---|
1 | AVERAGE |
2 | COUNT |
3 | COUNTA |
4 | MAX |
5 | MIN |
6 | PRODUCT |
7 | STDEV |
8 | STDEVP * |
9 | AVERAGE |
10 | VAR * |
11 | VARP * |
101-111 | * |
+ *
+ * Syntax :
+ * SUMIF ( range, criteria, sum_range )
+ *
range | The range over which criteria is applied. Also used for addend values when the third parameter is not present |
---|---|
criteria | The value or expression used to filter rows from range |
sum_range | Locates the top-left corner of the corresponding range of addends - values to be added (after being selected by the criteria) |
array1, ... arrayN | typically area references, + * possibly cell references or scalar values |
---|
ValueEval
.
+ * @param isScalarProduct false
for SUMPRODUCTs over area refs.
+ * @throws EvaluationException if ve
represents an error value.
+ *
+ * Note - string values and empty cells are interpreted differently depending on
+ * isScalarProduct
. For scalar products, if any term is blank or a string, the
+ * error (#VALUE!) is raised. For area (sum)products, if any term is blank or a string, the
+ * result is zero.
+ */
+ private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvaluationException {
+
+ if(ve instanceof BlankEval || ve == null) {
+ // TODO - shouldn't BlankEval.INSTANCE be used always instead of null?
+ // null seems to occur when the blank cell is part of an area ref (but not reliably)
+ if(isScalarProduct) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ return 0;
+ }
+
+ if(ve instanceof ErrorEval) {
+ throw new EvaluationException((ErrorEval)ve);
+ }
+ if(ve instanceof StringEval) {
+ if(isScalarProduct) {
+ throw new EvaluationException(ErrorEval.VALUE_INVALID);
+ }
+ // Note for area SUMPRODUCTs, string values are interpreted as zero
+ // even if they would parse as valid numeric values
+ return 0;
+ }
+ if(ve instanceof NumericValueEval) {
+ NumericValueEval nve = (NumericValueEval) ve;
+ return nve.getNumberValue();
+ }
+ throw new RuntimeException("Unexpected value eval class ("
+ + ve.getClass().getName() + ")");
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Sumx2my2.java b/src/java/org/apache/poi/ss/formula/functions/Sumx2my2.java
new file mode 100644
index 0000000000..9c8c386687
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Sumx2my2.java
@@ -0,0 +1,43 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss.formula.functions;
+
+
+/**
+ * Implementation of Excel function SUMX2MY2()
+ *
+ * Calculates the sum of differences of squares in two arrays of the same size.null
if there is any problem converting the text
+ */
+ private static Double convertTextToNumber(String strText) {
+ boolean foundCurrency = false;
+ boolean foundUnaryPlus = false;
+ boolean foundUnaryMinus = false;
+
+ int len = strText.length();
+ int i;
+ for (i = 0; i < len; i++) {
+ char ch = strText.charAt(i);
+ if (Character.isDigit(ch) || ch == '.') {
+ break;
+ }
+ switch (ch) {
+ case ' ':
+ // intervening spaces between '$', '-', '+' are OK
+ continue;
+ case '$':
+ if (foundCurrency) {
+ // only one currency symbols is allowed
+ return null;
+ }
+ foundCurrency = true;
+ continue;
+ case '+':
+ if (foundUnaryMinus || foundUnaryPlus) {
+ return null;
+ }
+ foundUnaryPlus = true;
+ continue;
+ case '-':
+ if (foundUnaryMinus || foundUnaryPlus) {
+ return null;
+ }
+ foundUnaryMinus = true;
+ continue;
+ default:
+ // all other characters are illegal
+ return null;
+ }
+ }
+ if (i >= len) {
+ // didn't find digits or '.'
+ if (foundCurrency || foundUnaryMinus || foundUnaryPlus) {
+ return null;
+ }
+ return ZERO;
+ }
+
+ // remove thousands separators
+
+ boolean foundDecimalPoint = false;
+ int lastThousandsSeparatorIndex = Short.MIN_VALUE;
+
+ StringBuffer sb = new StringBuffer(len);
+ for (; i < len; i++) {
+ char ch = strText.charAt(i);
+ if (Character.isDigit(ch)) {
+ sb.append(ch);
+ continue;
+ }
+ switch (ch) {
+ case ' ':
+ String remainingText = strText.substring(i);
+ if (remainingText.trim().length() > 0) {
+ // intervening spaces not allowed once the digits start
+ return null;
+ }
+ break;
+ case '.':
+ if (foundDecimalPoint) {
+ return null;
+ }
+ if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+ return null;
+ }
+ foundDecimalPoint = true;
+ sb.append('.');
+ continue;
+ case ',':
+ if (foundDecimalPoint) {
+ // thousands separators not allowed after '.' or 'E'
+ return null;
+ }
+ int distanceBetweenThousandsSeparators = i - lastThousandsSeparatorIndex;
+ // as long as there are 3 or more digits between
+ if (distanceBetweenThousandsSeparators < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+ return null;
+ }
+ lastThousandsSeparatorIndex = i;
+ // don't append ','
+ continue;
+
+ case 'E':
+ case 'e':
+ if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+ return null;
+ }
+ // append rest of strText and skip to end of loop
+ sb.append(strText.substring(i));
+ i = len;
+ break;
+ default:
+ // all other characters are illegal
+ return null;
+ }
+ }
+ if (!foundDecimalPoint) {
+ if (i - lastThousandsSeparatorIndex < MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR) {
+ return null;
+ }
+ }
+ double d;
+ try {
+ d = Double.parseDouble(sb.toString());
+ } catch (NumberFormatException e) {
+ // still a problem parsing the number - probably out of range
+ return null;
+ }
+ return new Double(foundUnaryMinus ? -d : d);
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Var1or2ArgFunction.java b/src/java/org/apache/poi/ss/formula/functions/Var1or2ArgFunction.java
new file mode 100644
index 0000000000..1938b79d3b
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Var1or2ArgFunction.java
@@ -0,0 +1,40 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Convenience base class for any function which must take two or three
+ * arguments
+ *
+ * @author Josh Micich
+ */
+abstract class Var1or2ArgFunction implements Function1Arg, Function2Arg {
+
+ public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+ switch (args.length) {
+ case 1:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0]);
+ case 2:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]);
+ }
+ return ErrorEval.VALUE_INVALID;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Var2or3ArgFunction.java b/src/java/org/apache/poi/ss/formula/functions/Var2or3ArgFunction.java
new file mode 100644
index 0000000000..cf3f350d6e
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Var2or3ArgFunction.java
@@ -0,0 +1,40 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Convenience base class for any function which must take two or three
+ * arguments
+ *
+ * @author Josh Micich
+ */
+abstract class Var2or3ArgFunction implements Function2Arg, Function3Arg {
+
+ public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+ switch (args.length) {
+ case 2:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]);
+ case 3:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2]);
+ }
+ return ErrorEval.VALUE_INVALID;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Var3or4ArgFunction.java b/src/java/org/apache/poi/ss/formula/functions/Var3or4ArgFunction.java
new file mode 100644
index 0000000000..e701b2d9a2
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Var3or4ArgFunction.java
@@ -0,0 +1,40 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
+
+/**
+ * Convenience base class for any function which must take three or four
+ * arguments
+ *
+ * @author Josh Micich
+ */
+abstract class Var3or4ArgFunction implements Function3Arg, Function4Arg {
+
+ public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
+ switch (args.length) {
+ case 3:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2]);
+ case 4:
+ return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], args[3]);
+ }
+ return ErrorEval.VALUE_INVALID;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/functions/Vlookup.java b/src/java/org/apache/poi/ss/formula/functions/Vlookup.java
new file mode 100644
index 0000000000..539be08163
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/functions/Vlookup.java
@@ -0,0 +1,81 @@
+/* ====================================================================
+ 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.functions;
+
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.ValueEval;
+import org.apache.poi.ss.formula.functions.LookupUtils.ValueVector;
+import org.apache.poi.ss.formula.TwoDEval;
+/**
+ * Implementation of the VLOOKUP() function.
+ *
+ * VLOOKUP finds a row in a lookup table by the first column value and returns the value from another column.null
if
+ * function isn't contained by any registered tool pack.
+ *
+ * @param name Name of function.
+ * @return Function executor. null
if not found
+ */
+ public FreeRefFunction findFunction(String name) {
+ FreeRefFunction evaluatorForFunction;
+ for (UDFFinder pack : _usedToolPacks) {
+ evaluatorForFunction = pack.findFunction(name);
+ if (evaluatorForFunction != null) {
+ return evaluatorForFunction;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java b/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java
new file mode 100644
index 0000000000..fd23cad977
--- /dev/null
+++ b/src/java/org/apache/poi/ss/formula/udf/DefaultUDFFinder.java
@@ -0,0 +1,49 @@
+/* ====================================================================
+ 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.udf;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.ss.formula.functions.FreeRefFunction;
+
+/**
+ * Default UDF finder - for adding your own user defined functions.
+ *
+ * @author PUdalau
+ */
+public final class DefaultUDFFinder implements UDFFinder {
+ private final Mapnull
if the function name is unknown.
+ *
+ * @param name Name of function.
+ * @return Function executor.
+ */
+ FreeRefFunction findFunction(String name);
+}
diff --git a/src/java/org/apache/poi/ss/util/CellReference.java b/src/java/org/apache/poi/ss/util/CellReference.java
index 5acebcef16..67f8fa0b15 100644
--- a/src/java/org/apache/poi/ss/util/CellReference.java
+++ b/src/java/org/apache/poi/ss/util/CellReference.java
@@ -20,7 +20,7 @@ package org.apache.poi.ss.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.apache.poi.hssf.record.formula.SheetNameFormatter;
+import org.apache.poi.ss.formula.SheetNameFormatter;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
diff --git a/src/java/org/apache/poi/ss/util/WorkbookUtil.java b/src/java/org/apache/poi/ss/util/WorkbookUtil.java
index b8c60da6ed..8ab44b9ce4 100644
--- a/src/java/org/apache/poi/ss/util/WorkbookUtil.java
+++ b/src/java/org/apache/poi/ss/util/WorkbookUtil.java
@@ -28,7 +28,7 @@ public class WorkbookUtil {
/**
* Creates a valid sheet name, which is conform to the rules.
* In any case, the result safely can be used for
- * {@link org.apache.poi.hssf.usermodel.HSSFWorkbook#setSheetName(int, String)}.
+ * {@link org.apache.poi.ss.usermodel.Workbook#setSheetName(int, String)}.
*