git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1037439 13f79535-47bb-0310-9956-ffa450edef68pull/1/head
@@ -17,12 +17,12 @@ | |||
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.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.IEvaluationListener.ICacheEntry; | |||
/** |
@@ -20,7 +20,7 @@ package org.apache.poi.ss.formula; | |||
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; | |||
/** | |||
* Stores details about the current evaluation of a cell.<br/> |
@@ -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 |
@@ -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.<br/> | |||
@@ -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(); | |||
@@ -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. |
@@ -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. | |||
* |
@@ -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; | |||
@@ -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"); | |||
@@ -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 <code>true</code> if a change was made to the formula tokens | |||
*/ | |||
public boolean adjustFormula(Ptg[] ptgs, int currentExternSheetIx) { | |||
boolean refsWereChanged = false; | |||
for(int i=0; i<ptgs.length; i++) { | |||
Ptg newPtg = adjustPtg(ptgs[i], currentExternSheetIx); | |||
if (newPtg != null) { | |||
refsWereChanged = true; | |||
ptgs[i] = newPtg; | |||
} | |||
} | |||
return refsWereChanged; | |||
} | |||
private Ptg adjustPtg(Ptg ptg, int currentExternSheetIx) { | |||
return adjustPtgDueToRowMove(ptg, currentExternSheetIx); | |||
} | |||
/** | |||
* @return <code>true</code> if this Ptg needed to be changed | |||
*/ | |||
private Ptg adjustPtgDueToRowMove(Ptg ptg, int currentExternSheetIx) { | |||
if(ptg instanceof RefPtg) { | |||
if (currentExternSheetIx != _externSheetIndex) { | |||
// local refs on other sheets are unaffected | |||
return null; | |||
} | |||
RefPtg rptg = (RefPtg)ptg; | |||
return rowMoveRefPtg(rptg); | |||
} | |||
if(ptg instanceof Ref3DPtg) { | |||
Ref3DPtg rptg = (Ref3DPtg)ptg; | |||
if (_externSheetIndex != rptg.getExternSheetIndex()) { | |||
// only move 3D refs that refer to the sheet with cells being moved | |||
// (currentExternSheetIx is irrelevant) | |||
return null; | |||
} | |||
return rowMoveRefPtg(rptg); | |||
} | |||
if(ptg instanceof Area2DPtgBase) { | |||
if (currentExternSheetIx != _externSheetIndex) { | |||
// local refs on other sheets are unaffected | |||
return ptg; | |||
} | |||
return rowMoveAreaPtg((Area2DPtgBase)ptg); | |||
} | |||
if(ptg instanceof Area3DPtg) { | |||
Area3DPtg aptg = (Area3DPtg)ptg; | |||
if (_externSheetIndex != aptg.getExternSheetIndex()) { | |||
// only move 3D refs that refer to the sheet with cells being moved | |||
// (currentExternSheetIx is irrelevant) | |||
return null; | |||
} | |||
return rowMoveAreaPtg(aptg); | |||
} | |||
return null; | |||
} | |||
private Ptg rowMoveRefPtg(RefPtgBase rptg) { | |||
int refRow = rptg.getRow(); | |||
if (_firstMovedIndex <= refRow && refRow <= _lastMovedIndex) { | |||
// Rows being moved completely enclose the ref. | |||
// - move the area ref along with the rows regardless of destination | |||
rptg.setRow(refRow + _amountToMove); | |||
return rptg; | |||
} | |||
// else rules for adjusting area may also depend on the destination of the moved rows | |||
int destFirstRowIndex = _firstMovedIndex + _amountToMove; | |||
int destLastRowIndex = _lastMovedIndex + _amountToMove; | |||
// ref is outside source rows | |||
// check for clashes with destination | |||
if (destLastRowIndex < refRow || refRow < destFirstRowIndex) { | |||
// destination rows are completely outside ref | |||
return null; | |||
} | |||
if (destFirstRowIndex <= refRow && refRow <= destLastRowIndex) { | |||
// destination rows enclose the area (possibly exactly) | |||
return createDeletedRef(rptg); | |||
} | |||
throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " + | |||
_lastMovedIndex + ", " + _amountToMove + ", " + refRow + ", " + refRow + ")"); | |||
} | |||
private Ptg rowMoveAreaPtg(AreaPtgBase aptg) { | |||
int aFirstRow = aptg.getFirstRow(); | |||
int aLastRow = aptg.getLastRow(); | |||
if (_firstMovedIndex <= aFirstRow && aLastRow <= _lastMovedIndex) { | |||
// Rows being moved completely enclose the area ref. | |||
// - move the area ref along with the rows regardless of destination | |||
aptg.setFirstRow(aFirstRow + _amountToMove); | |||
aptg.setLastRow(aLastRow + _amountToMove); | |||
return aptg; | |||
} | |||
// else rules for adjusting area may also depend on the destination of the moved rows | |||
int destFirstRowIndex = _firstMovedIndex + _amountToMove; | |||
int destLastRowIndex = _lastMovedIndex + _amountToMove; | |||
if (aFirstRow < _firstMovedIndex && _lastMovedIndex < aLastRow) { | |||
// Rows moved were originally *completely* within the area ref | |||
// If the destination of the rows overlaps either the top | |||
// or bottom of the area ref there will be a change | |||
if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) { | |||
// truncate the top of the area by the moved rows | |||
aptg.setFirstRow(destLastRowIndex+1); | |||
return aptg; | |||
} else if (destFirstRowIndex <= aLastRow && aLastRow < destLastRowIndex) { | |||
// truncate the bottom of the area by the moved rows | |||
aptg.setLastRow(destFirstRowIndex-1); | |||
return aptg; | |||
} | |||
// else - rows have moved completely outside the area ref, | |||
// or still remain completely within the area ref | |||
return null; // - no change to the area | |||
} | |||
if (_firstMovedIndex <= aFirstRow && aFirstRow <= _lastMovedIndex) { | |||
// Rows moved include the first row of the area ref, but not the last row | |||
// btw: (aLastRow > _lastMovedIndex) | |||
if (_amountToMove < 0) { | |||
// simple case - expand area by shifting top upward | |||
aptg.setFirstRow(aFirstRow + _amountToMove); | |||
return aptg; | |||
} | |||
if (destFirstRowIndex > aLastRow) { | |||
// in this case, excel ignores the row move | |||
return null; | |||
} | |||
int newFirstRowIx = aFirstRow + _amountToMove; | |||
if (destLastRowIndex < aLastRow) { | |||
// end of area is preserved (will remain exact same row) | |||
// the top area row is moved simply | |||
aptg.setFirstRow(newFirstRowIx); | |||
return aptg; | |||
} | |||
// else - bottom area row has been replaced - both area top and bottom may move now | |||
int areaRemainingTopRowIx = _lastMovedIndex + 1; | |||
if (destFirstRowIndex > areaRemainingTopRowIx) { | |||
// old top row of area has moved deep within the area, and exposed a new top row | |||
newFirstRowIx = areaRemainingTopRowIx; | |||
} | |||
aptg.setFirstRow(newFirstRowIx); | |||
aptg.setLastRow(Math.max(aLastRow, destLastRowIndex)); | |||
return aptg; | |||
} | |||
if (_firstMovedIndex <= aLastRow && aLastRow <= _lastMovedIndex) { | |||
// Rows moved include the last row of the area ref, but not the first | |||
// btw: (aFirstRow < _firstMovedIndex) | |||
if (_amountToMove > 0) { | |||
// simple case - expand area by shifting bottom downward | |||
aptg.setLastRow(aLastRow + _amountToMove); | |||
return aptg; | |||
} | |||
if (destLastRowIndex < aFirstRow) { | |||
// in this case, excel ignores the row move | |||
return null; | |||
} | |||
int newLastRowIx = aLastRow + _amountToMove; | |||
if (destFirstRowIndex > aFirstRow) { | |||
// top of area is preserved (will remain exact same row) | |||
// the bottom area row is moved simply | |||
aptg.setLastRow(newLastRowIx); | |||
return aptg; | |||
} | |||
// else - top area row has been replaced - both area top and bottom may move now | |||
int areaRemainingBottomRowIx = _firstMovedIndex - 1; | |||
if (destLastRowIndex < areaRemainingBottomRowIx) { | |||
// old bottom row of area has moved up deep within the area, and exposed a new bottom row | |||
newLastRowIx = areaRemainingBottomRowIx; | |||
} | |||
aptg.setFirstRow(Math.min(aFirstRow, destFirstRowIndex)); | |||
aptg.setLastRow(newLastRowIx); | |||
return aptg; | |||
} | |||
// else source rows include none of the rows of the area ref | |||
// check for clashes with destination | |||
if (destLastRowIndex < aFirstRow || aLastRow < destFirstRowIndex) { | |||
// destination rows are completely outside area ref | |||
return null; | |||
} | |||
if (destFirstRowIndex <= aFirstRow && aLastRow <= destLastRowIndex) { | |||
// destination rows enclose the area (possibly exactly) | |||
return createDeletedRef(aptg); | |||
} | |||
if (aFirstRow <= destFirstRowIndex && destLastRowIndex <= aLastRow) { | |||
// destination rows are within area ref (possibly exact on top or bottom, but not both) | |||
return null; // - no change to area | |||
} | |||
if (destFirstRowIndex < aFirstRow && aFirstRow <= destLastRowIndex) { | |||
// dest rows overlap top of area | |||
// - truncate the top | |||
aptg.setFirstRow(destLastRowIndex+1); | |||
return aptg; | |||
} | |||
if (destFirstRowIndex < aLastRow && aLastRow <= destLastRowIndex) { | |||
// dest rows overlap bottom of area | |||
// - truncate the bottom | |||
aptg.setLastRow(destFirstRowIndex-1); | |||
return aptg; | |||
} | |||
throw new IllegalStateException("Situation not covered: (" + _firstMovedIndex + ", " + | |||
_lastMovedIndex + ", " + _amountToMove + ", " + aFirstRow + ", " + aLastRow + ")"); | |||
} | |||
private static Ptg createDeletedRef(Ptg ptg) { | |||
if (ptg instanceof RefPtg) { | |||
return new RefErrorPtg(); | |||
} | |||
if (ptg instanceof Ref3DPtg) { | |||
Ref3DPtg rptg = (Ref3DPtg) ptg; | |||
return new DeletedRef3DPtg(rptg.getExternSheetIndex()); | |||
} | |||
if (ptg instanceof AreaPtg) { | |||
return new AreaErrPtg(); | |||
} | |||
if (ptg instanceof Area3DPtg) { | |||
Area3DPtg area3DPtg = (Area3DPtg) ptg; | |||
return new DeletedArea3DPtg(area3DPtg.getExternSheetIndex()); | |||
} | |||
throw new IllegalArgumentException("Unexpected ref ptg class (" + ptg.getClass().getName() + ")"); | |||
} | |||
} |
@@ -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; | |||
/** | |||
* Tests can implement this class to track the internal working of the {@link WorkbookEvaluator}.<br/> |
@@ -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 <em>final</em>.<br/> | |||
@@ -49,7 +47,7 @@ import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; | |||
* <li>To retain freedom to change any cell definition at any time, an application may classify all | |||
* cells as 'not final'. This freedom comes at the expense of greater memory consumption.</li> | |||
* <li>For the purpose of these classifications, setting the cached formula result of a cell (for | |||
* example in {@link HSSFFormulaEvaluator#evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)}) | |||
* example in {@link org.apache.poi.ss.usermodel.FormulaEvaluator#evaluateFormulaCell(org.apache.poi.ss.usermodel.Cell)}) | |||
* does not constitute changing the definition of the cell.</li> | |||
* <li>Updating cells which have been classified as 'final' will cause the evaluator to behave | |||
* unpredictably (typically ignoring the update).</li> |
@@ -19,9 +19,9 @@ package org.apache.poi.ss.formula; | |||
import org.apache.poi.hssf.record.formula.AreaI; | |||
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; | |||
import org.apache.poi.hssf.record.formula.eval.AreaEval; | |||
import org.apache.poi.hssf.record.formula.eval.AreaEvalBase; | |||
import org.apache.poi.hssf.record.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.eval.AreaEval; | |||
import org.apache.poi.ss.formula.eval.AreaEvalBase; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.hssf.util.CellReference; | |||
/** |
@@ -19,9 +19,9 @@ package org.apache.poi.ss.formula; | |||
import org.apache.poi.hssf.record.formula.AreaI; | |||
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; | |||
import org.apache.poi.hssf.record.formula.eval.AreaEval; | |||
import org.apache.poi.hssf.record.formula.eval.RefEvalBase; | |||
import org.apache.poi.hssf.record.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.eval.AreaEval; | |||
import org.apache.poi.ss.formula.eval.RefEvalBase; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.hssf.util.CellReference; | |||
/** |
@@ -21,8 +21,8 @@ import org.apache.poi.hssf.record.formula.Area3DPtg; | |||
import org.apache.poi.hssf.record.formula.NameXPtg; | |||
import org.apache.poi.hssf.record.formula.Ptg; | |||
import org.apache.poi.hssf.record.formula.Ref3DPtg; | |||
import org.apache.poi.hssf.record.formula.eval.*; | |||
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; | |||
import org.apache.poi.ss.formula.eval.*; | |||
import org.apache.poi.ss.formula.functions.FreeRefFunction; | |||
import org.apache.poi.ss.SpreadsheetVersion; | |||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; | |||
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName; |
@@ -41,19 +41,19 @@ import org.apache.poi.hssf.record.formula.RangePtg; | |||
import org.apache.poi.hssf.record.formula.SubtractPtg; | |||
import org.apache.poi.hssf.record.formula.UnaryMinusPtg; | |||
import org.apache.poi.hssf.record.formula.UnaryPlusPtg; | |||
import org.apache.poi.hssf.record.formula.eval.ConcatEval; | |||
import org.apache.poi.hssf.record.formula.eval.FunctionEval; | |||
import org.apache.poi.hssf.record.formula.eval.IntersectionEval; | |||
import org.apache.poi.hssf.record.formula.eval.PercentEval; | |||
import org.apache.poi.hssf.record.formula.eval.RangeEval; | |||
import org.apache.poi.hssf.record.formula.eval.RelationalOperationEval; | |||
import org.apache.poi.hssf.record.formula.eval.TwoOperandNumericOperation; | |||
import org.apache.poi.hssf.record.formula.eval.UnaryMinusEval; | |||
import org.apache.poi.hssf.record.formula.eval.UnaryPlusEval; | |||
import org.apache.poi.hssf.record.formula.eval.ValueEval; | |||
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; | |||
import org.apache.poi.hssf.record.formula.functions.Function; | |||
import org.apache.poi.hssf.record.formula.functions.Indirect; | |||
import org.apache.poi.ss.formula.eval.ConcatEval; | |||
import org.apache.poi.ss.formula.eval.FunctionEval; | |||
import org.apache.poi.ss.formula.eval.IntersectionEval; | |||
import org.apache.poi.ss.formula.eval.PercentEval; | |||
import org.apache.poi.ss.formula.eval.RangeEval; | |||
import org.apache.poi.ss.formula.eval.RelationalOperationEval; | |||
import org.apache.poi.ss.formula.eval.TwoOperandNumericOperation; | |||
import org.apache.poi.ss.formula.eval.UnaryMinusEval; | |||
import org.apache.poi.ss.formula.eval.UnaryPlusEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; | |||
import org.apache.poi.ss.formula.functions.Function; | |||
import org.apache.poi.ss.formula.functions.Indirect; | |||
/** | |||
* This class creates <tt>OperationEval</tt> instances to help evaluate <tt>OperationPtg</tt> |
@@ -23,7 +23,7 @@ import org.apache.poi.hssf.record.formula.FuncVarPtg; | |||
import org.apache.poi.hssf.record.formula.MemAreaPtg; | |||
import org.apache.poi.hssf.record.formula.MemFuncPtg; | |||
import org.apache.poi.hssf.record.formula.Ptg; | |||
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; | |||
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; | |||
/** | |||
* Represents a syntactic element from a formula by encapsulating the corresponding <tt>Ptg</tt> | |||
* token. Each <tt>ParseNode</tt> may have child <tt>ParseNode</tt>s in the case when the wrapped |
@@ -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; | |||
/** | |||
* Used for non-formula cells, primarily to keep track of the referencing (formula) cells. |
@@ -0,0 +1,98 @@ | |||
/* ==================================================================== | |||
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.*; | |||
import org.apache.poi.ss.SpreadsheetVersion; | |||
/** | |||
* Encapsulates logic to convert shared formulaa into non shared equivalent | |||
*/ | |||
public class SharedFormula { | |||
private final int _columnWrappingMask; | |||
private final int _rowWrappingMask; | |||
public SharedFormula(SpreadsheetVersion ssVersion){ | |||
_columnWrappingMask = ssVersion.getLastColumnIndex(); //"IV" for .xls and "XFD" for .xlsx | |||
_rowWrappingMask = ssVersion.getLastRowIndex(); | |||
} | |||
/** | |||
* Creates a non shared formula from the shared formula counterpart, i.e. | |||
* Converts the shared formula into the equivalent {@link org.apache.poi.hssf.record.formula.Ptg} array that it would have, | |||
* were it not shared. | |||
* | |||
* @param ptgs parsed tokens of the shared formula | |||
* @param formulaRow | |||
* @param formulaColumn | |||
*/ | |||
public Ptg[] convertSharedFormulas(Ptg[] ptgs, int formulaRow, int formulaColumn) { | |||
Ptg[] newPtgStack = new Ptg[ptgs.length]; | |||
for (int k = 0; k < ptgs.length; k++) { | |||
Ptg ptg = ptgs[k]; | |||
byte originalOperandClass = -1; | |||
if (!ptg.isBaseToken()) { | |||
originalOperandClass = ptg.getPtgClass(); | |||
} | |||
if (ptg instanceof RefPtgBase) { | |||
RefPtgBase refNPtg = (RefPtgBase)ptg; | |||
ptg = new RefPtg(fixupRelativeRow(formulaRow,refNPtg.getRow(),refNPtg.isRowRelative()), | |||
fixupRelativeColumn(formulaColumn,refNPtg.getColumn(),refNPtg.isColRelative()), | |||
refNPtg.isRowRelative(), | |||
refNPtg.isColRelative()); | |||
ptg.setClass(originalOperandClass); | |||
} else if (ptg instanceof AreaPtgBase) { | |||
AreaPtgBase areaNPtg = (AreaPtgBase)ptg; | |||
ptg = new AreaPtg(fixupRelativeRow(formulaRow,areaNPtg.getFirstRow(),areaNPtg.isFirstRowRelative()), | |||
fixupRelativeRow(formulaRow,areaNPtg.getLastRow(),areaNPtg.isLastRowRelative()), | |||
fixupRelativeColumn(formulaColumn,areaNPtg.getFirstColumn(),areaNPtg.isFirstColRelative()), | |||
fixupRelativeColumn(formulaColumn,areaNPtg.getLastColumn(),areaNPtg.isLastColRelative()), | |||
areaNPtg.isFirstRowRelative(), | |||
areaNPtg.isLastRowRelative(), | |||
areaNPtg.isFirstColRelative(), | |||
areaNPtg.isLastColRelative()); | |||
ptg.setClass(originalOperandClass); | |||
} else if (ptg instanceof OperandPtg) { | |||
// Any subclass of OperandPtg is mutable, so it's safest to not share these instances. | |||
ptg = ((OperandPtg) ptg).copy(); | |||
} else { | |||
// all other Ptgs are immutable and can be shared | |||
} | |||
newPtgStack[k] = ptg; | |||
} | |||
return newPtgStack; | |||
} | |||
private int fixupRelativeColumn(int currentcolumn, int column, boolean relative) { | |||
if(relative) { | |||
// mask out upper bits to produce 'wrapping' at the maximum column ("IV" for .xls and "XFD" for .xlsx) | |||
return (column + currentcolumn) & _columnWrappingMask; | |||
} | |||
return column; | |||
} | |||
private int fixupRelativeRow(int currentrow, int row, boolean relative) { | |||
if(relative) { | |||
return (row+currentrow) & _rowWrappingMask; | |||
} | |||
return row; | |||
} | |||
} |
@@ -0,0 +1,223 @@ | |||
/* ==================================================================== | |||
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 java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import org.apache.poi.ss.util.CellReference; | |||
import org.apache.poi.ss.SpreadsheetVersion; | |||
/** | |||
* Formats sheet names for use in formula expressions. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class SheetNameFormatter { | |||
private static final char DELIMITER = '\''; | |||
/** | |||
* Matches a single cell ref with no absolute ('$') markers | |||
*/ | |||
private static final Pattern CELL_REF_PATTERN = Pattern.compile("([A-Za-z]+)([0-9]+)"); | |||
private SheetNameFormatter() { | |||
// no instances of this class | |||
} | |||
/** | |||
* Used to format sheet names as they would appear in cell formula expressions. | |||
* @return the sheet name unchanged if there is no need for delimiting. Otherwise the sheet | |||
* name is enclosed in single quotes ('). Any single quotes which were already present in the | |||
* sheet name will be converted to double single quotes (''). | |||
*/ | |||
public static String format(String rawSheetName) { | |||
StringBuffer sb = new StringBuffer(rawSheetName.length() + 2); | |||
appendFormat(sb, rawSheetName); | |||
return sb.toString(); | |||
} | |||
/** | |||
* Convenience method for ({@link #format(String)}) when a StringBuffer is already available. | |||
* | |||
* @param out - sheet name will be appended here possibly with delimiting quotes | |||
*/ | |||
public static void appendFormat(StringBuffer out, String rawSheetName) { | |||
boolean needsQuotes = needsDelimiting(rawSheetName); | |||
if(needsQuotes) { | |||
out.append(DELIMITER); | |||
appendAndEscape(out, rawSheetName); | |||
out.append(DELIMITER); | |||
} else { | |||
out.append(rawSheetName); | |||
} | |||
} | |||
public static void appendFormat(StringBuffer out, String workbookName, String rawSheetName) { | |||
boolean needsQuotes = needsDelimiting(workbookName) || needsDelimiting(rawSheetName); | |||
if(needsQuotes) { | |||
out.append(DELIMITER); | |||
out.append('['); | |||
appendAndEscape(out, workbookName.replace('[', '(').replace(']', ')')); | |||
out.append(']'); | |||
appendAndEscape(out, rawSheetName); | |||
out.append(DELIMITER); | |||
} else { | |||
out.append('['); | |||
out.append(workbookName); | |||
out.append(']'); | |||
out.append(rawSheetName); | |||
} | |||
} | |||
private static void appendAndEscape(StringBuffer sb, String rawSheetName) { | |||
int len = rawSheetName.length(); | |||
for(int i=0; i<len; i++) { | |||
char ch = rawSheetName.charAt(i); | |||
if(ch == DELIMITER) { | |||
// single quotes (') are encoded as ('') | |||
sb.append(DELIMITER); | |||
} | |||
sb.append(ch); | |||
} | |||
} | |||
private static boolean needsDelimiting(String rawSheetName) { | |||
int len = rawSheetName.length(); | |||
if(len < 1) { | |||
throw new RuntimeException("Zero length string is an invalid sheet name"); | |||
} | |||
if(Character.isDigit(rawSheetName.charAt(0))) { | |||
// sheet name with digit in the first position always requires delimiting | |||
return true; | |||
} | |||
for(int i=0; i<len; i++) { | |||
char ch = rawSheetName.charAt(i); | |||
if(isSpecialChar(ch)) { | |||
return true; | |||
} | |||
} | |||
if(Character.isLetter(rawSheetName.charAt(0)) | |||
&& Character.isDigit(rawSheetName.charAt(len-1))) { | |||
// note - values like "A$1:$C$20" don't get this far | |||
if(nameLooksLikePlainCellReference(rawSheetName)) { | |||
return true; | |||
} | |||
} | |||
if (nameLooksLikeBooleanLiteral(rawSheetName)) { | |||
return true; | |||
} | |||
// Error constant literals all contain '#' and other special characters | |||
// so they don't get this far | |||
return false; | |||
} | |||
private static boolean nameLooksLikeBooleanLiteral(String rawSheetName) { | |||
switch(rawSheetName.charAt(0)) { | |||
case 'T': case 't': | |||
return "TRUE".equalsIgnoreCase(rawSheetName); | |||
case 'F': case 'f': | |||
return "FALSE".equalsIgnoreCase(rawSheetName); | |||
} | |||
return false; | |||
} | |||
/** | |||
* @return <code>true</code> if the presence of the specified character in a sheet name would | |||
* require the sheet name to be delimited in formulas. This includes every non-alphanumeric | |||
* character besides underscore '_' and dot '.'. | |||
*/ | |||
/* package */ static boolean isSpecialChar(char ch) { | |||
// note - Character.isJavaIdentifierPart() would allow dollars '$' | |||
if(Character.isLetterOrDigit(ch)) { | |||
return false; | |||
} | |||
switch(ch) { | |||
case '.': // dot is OK | |||
case '_': // underscore is OK | |||
return false; | |||
case '\n': | |||
case '\r': | |||
case '\t': | |||
throw new RuntimeException("Illegal character (0x" | |||
+ Integer.toHexString(ch) + ") found in sheet name"); | |||
} | |||
return true; | |||
} | |||
/** | |||
* Used to decide whether sheet names like 'AB123' need delimiting due to the fact that they | |||
* look like cell references. | |||
* <p/> | |||
* This code is currently being used for translating formulas represented with <code>Ptg</code> | |||
* 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: | |||
* <ol> | |||
* <li>POI's own formula parser</li> | |||
* <li>Visual reading by human</li> | |||
* <li>VBA automation entry into Excel cell contents e.g. ActiveCell.Formula = "=c64!A1"</li> | |||
* <li>Manual entry into Excel cell contents</li> | |||
* <li>Some third party formula parser</li> | |||
* </ol> | |||
* | |||
* 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. | |||
* <p/> | |||
* 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). | |||
* <p/> | |||
* Some notable cases: | |||
* <blockquote><table border="0" cellpadding="1" cellspacing="0" | |||
* summary="Notable cases."> | |||
* <tr><th>Input </th><th>Result </th><th>Comments</th></tr> | |||
* <tr><td>"A1" </td><td>true</td><td> </td></tr> | |||
* <tr><td>"a111" </td><td>true</td><td> </td></tr> | |||
* <tr><td>"AA" </td><td>false</td><td> </td></tr> | |||
* <tr><td>"aa1" </td><td>true</td><td> </td></tr> | |||
* <tr><td>"A1A" </td><td>false</td><td> </td></tr> | |||
* <tr><td>"A1A1" </td><td>false</td><td> </td></tr> | |||
* <tr><td>"A$1:$C$20" </td><td>false</td><td>Not a plain cell reference</td></tr> | |||
* <tr><td>"SALES20080101" </td><td>true</td> | |||
* <td>Still needs delimiting even though well out of range</td></tr> | |||
* </table></blockquote> | |||
* | |||
* @return <code>true</code> 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); | |||
} | |||
} |
@@ -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; | |||
/** | |||
* | |||
* |
@@ -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 | |||
*/ |
@@ -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; | |||
/** | |||
* |
@@ -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; |
@@ -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 <tt>FunctionMetadata</tt> instances for creation of a | |||
* <tt>FunctionMetadataRegistry</tt>. | |||
* | |||
* @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); | |||
} | |||
} |
@@ -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 <code>true</code> 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(); | |||
} | |||
} |
@@ -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 <tt>FunctionMetadataRegistry</tt> | |||
* | |||
* @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"); | |||
} | |||
} | |||
} |
@@ -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<String, FunctionMetadata> _functionDataByName; | |||
private static FunctionMetadataRegistry getInstance() { | |||
if (_instance == null) { | |||
_instance = FunctionMetadataReader.createRegistry(); | |||
} | |||
return _instance; | |||
} | |||
/* package */ FunctionMetadataRegistry(FunctionMetadata[] functionDataByIndex, Map<String, FunctionMetadata> functionDataByName) { | |||
_functionDataByIndex = functionDataByIndex; | |||
_functionDataByName = functionDataByName; | |||
} | |||
/* package */ Set<String> getAllFunctionNames() { | |||
return _functionDataByName.keySet(); | |||
} | |||
public static FunctionMetadata getFunctionByIndex(int index) { | |||
return getInstance().getFunctionByIndexInternal(index); | |||
} | |||
private FunctionMetadata getFunctionByIndexInternal(int index) { | |||
return _functionDataByIndex[index]; | |||
} | |||
/** | |||
* Resolves a built-in function index. | |||
* @param name uppercase function name | |||
* @return a negative value if the function name is not found. | |||
* This typically occurs for external functions. | |||
*/ | |||
public static short lookupIndexByName(String name) { | |||
FunctionMetadata fd = getInstance().getFunctionByNameInternal(name); | |||
if (fd == null) { | |||
return -1; | |||
} | |||
return (short) fd.getIndex(); | |||
} | |||
private FunctionMetadata getFunctionByNameInternal(String name) { | |||
return _functionDataByName.get(name); | |||
} | |||
public static FunctionMetadata getFunctionByName(String name) { | |||
return getInstance().getFunctionByNameInternal(name); | |||
} | |||
} |
@@ -0,0 +1,145 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public abstract class AggregateFunction extends MultiOperandNumericFunction { | |||
private static final class LargeSmall extends Fixed2ArgFunction { | |||
private final boolean _isLarge; | |||
protected LargeSmall(boolean isLarge) { | |||
_isLarge = isLarge; | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, | |||
ValueEval arg1) { | |||
double dn; | |||
try { | |||
ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex); | |||
dn = OperandResolver.coerceValueToDouble(ve1); | |||
} catch (EvaluationException e1) { | |||
// all errors in the second arg translate to #VALUE! | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
// weird Excel behaviour on second arg | |||
if (dn < 1.0) { | |||
// values between 0.0 and 1.0 result in #NUM! | |||
return ErrorEval.NUM_ERROR; | |||
} | |||
// all other values are rounded up to the next integer | |||
int k = (int) Math.ceil(dn); | |||
double result; | |||
try { | |||
double[] ds = ValueCollector.collectValues(arg0); | |||
if (k > ds.length) { | |||
return ErrorEval.NUM_ERROR; | |||
} | |||
result = _isLarge ? StatsLib.kthLargest(ds, k) : StatsLib.kthSmallest(ds, k); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
} | |||
private static final class ValueCollector extends MultiOperandNumericFunction { | |||
private static final ValueCollector instance = new ValueCollector(); | |||
public ValueCollector() { | |||
super(false, false); | |||
} | |||
public static double[] collectValues(ValueEval...operands) throws EvaluationException { | |||
return instance.getNumberArray(operands); | |||
} | |||
protected double evaluate(double[] values) { | |||
throw new IllegalStateException("should not be called"); | |||
} | |||
} | |||
protected AggregateFunction() { | |||
super(false, false); | |||
} | |||
public static final Function AVEDEV = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return StatsLib.avedev(values); | |||
} | |||
}; | |||
public static final Function AVERAGE = new AggregateFunction() { | |||
protected double evaluate(double[] values) throws EvaluationException { | |||
if (values.length < 1) { | |||
throw new EvaluationException(ErrorEval.DIV_ZERO); | |||
} | |||
return MathX.average(values); | |||
} | |||
}; | |||
public static final Function DEVSQ = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return StatsLib.devsq(values); | |||
} | |||
}; | |||
public static final Function LARGE = new LargeSmall(true); | |||
public static final Function MAX = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return values.length > 0 ? MathX.max(values) : 0; | |||
} | |||
}; | |||
public static final Function MEDIAN = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return StatsLib.median(values); | |||
} | |||
}; | |||
public static final Function MIN = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return values.length > 0 ? MathX.min(values) : 0; | |||
} | |||
}; | |||
public static final Function PRODUCT = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return MathX.product(values); | |||
} | |||
}; | |||
public static final Function SMALL = new LargeSmall(false); | |||
public static final Function STDEV = new AggregateFunction() { | |||
protected double evaluate(double[] values) throws EvaluationException { | |||
if (values.length < 1) { | |||
throw new EvaluationException(ErrorEval.DIV_ZERO); | |||
} | |||
return StatsLib.stdev(values); | |||
} | |||
}; | |||
public static final Function SUM = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return MathX.sum(values); | |||
} | |||
}; | |||
public static final Function SUMSQ = new AggregateFunction() { | |||
protected double evaluate(double[] values) { | |||
return MathX.sumsq(values); | |||
} | |||
}; | |||
} |
@@ -0,0 +1,146 @@ | |||
/* ==================================================================== | |||
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.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.RefEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* Here are the general rules concerning Boolean functions: | |||
* <ol> | |||
* <li> Blanks are ignored (not either true or false) </li> | |||
* <li> Strings are ignored if part of an area ref or cell ref, otherwise they must be 'true' or 'false'</li> | |||
* <li> Numbers: 0 is false. Any other number is TRUE </li> | |||
* <li> Areas: *all* cells in area are evaluated according to the above rules</li> | |||
* </ol> | |||
* | |||
* @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<iSize; i++) { | |||
ValueEval arg = args[i]; | |||
if (arg instanceof TwoDEval) { | |||
TwoDEval ae = (TwoDEval) arg; | |||
int height = ae.getHeight(); | |||
int width = ae.getWidth(); | |||
for (int rrIx=0; rrIx<height; rrIx++) { | |||
for (int rcIx=0; rcIx<width; rcIx++) { | |||
ValueEval ve = ae.getValue(rrIx, rcIx); | |||
Boolean tempVe = OperandResolver.coerceValueToBoolean(ve, true); | |||
if (tempVe != null) { | |||
result = partialEvaluate(result, tempVe.booleanValue()); | |||
atleastOneNonBlank = true; | |||
} | |||
} | |||
} | |||
continue; | |||
} | |||
Boolean tempVe; | |||
if (arg instanceof RefEval) { | |||
ValueEval ve = ((RefEval) arg).getInnerValueEval(); | |||
tempVe = OperandResolver.coerceValueToBoolean(ve, true); | |||
} else { | |||
tempVe = OperandResolver.coerceValueToBoolean(arg, false); | |||
} | |||
if (tempVe != null) { | |||
result = partialEvaluate(result, tempVe.booleanValue()); | |||
atleastOneNonBlank = true; | |||
} | |||
} | |||
if (!atleastOneNonBlank) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
return result; | |||
} | |||
protected abstract boolean getInitialResultValue(); | |||
protected abstract boolean partialEvaluate(boolean cumulativeResult, boolean currentValue); | |||
public static final Function AND = new BooleanFunction() { | |||
protected boolean getInitialResultValue() { | |||
return true; | |||
} | |||
protected boolean partialEvaluate(boolean cumulativeResult, boolean currentValue) { | |||
return cumulativeResult && currentValue; | |||
} | |||
}; | |||
public static final Function OR = new BooleanFunction() { | |||
protected boolean getInitialResultValue() { | |||
return false; | |||
} | |||
protected boolean partialEvaluate(boolean cumulativeResult, boolean currentValue) { | |||
return cumulativeResult || currentValue; | |||
} | |||
}; | |||
public static final Function FALSE = new Fixed0ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
return BoolEval.FALSE; | |||
} | |||
}; | |||
public static final Function TRUE = new Fixed0ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
return BoolEval.TRUE; | |||
} | |||
}; | |||
public static final Function NOT = new Fixed1ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
boolean boolArgVal; | |||
try { | |||
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
Boolean b = OperandResolver.coerceValueToBoolean(ve, false); | |||
boolArgVal = b == null ? false : b.booleanValue(); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return BoolEval.valueOf(!boolArgVal); | |||
} | |||
}; | |||
} |
@@ -0,0 +1,87 @@ | |||
/* ==================================================================== | |||
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.Calendar; | |||
import java.util.Date; | |||
import java.util.GregorianCalendar; | |||
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.ValueEval; | |||
import org.apache.poi.ss.usermodel.DateUtil; | |||
/** | |||
* Implementation of Excel functions DAY, MONTH and YEAR | |||
* | |||
* | |||
* @author Guenter Kickinger g.kickinger@gmx.net | |||
*/ | |||
public final class CalendarFieldFunction extends Fixed1ArgFunction { | |||
public static final Function YEAR = new CalendarFieldFunction(Calendar.YEAR, false); | |||
public static final Function MONTH = new CalendarFieldFunction(Calendar.MONTH, true); | |||
public static final Function DAY = new CalendarFieldFunction(Calendar.DAY_OF_MONTH, false); | |||
private final int _dateFieldId; | |||
private final boolean _needsOneBaseAdjustment; | |||
private CalendarFieldFunction(int dateFieldId, boolean needsOneBaseAdjustment) { | |||
_dateFieldId = dateFieldId; | |||
_needsOneBaseAdjustment = needsOneBaseAdjustment; | |||
} | |||
public final ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
int val; | |||
try { | |||
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
val = OperandResolver.coerceValueToInt(ve); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (val < 0) { | |||
return ErrorEval.NUM_ERROR; | |||
} | |||
return new NumberEval(getCalField(val)); | |||
} | |||
private int getCalField(int serialDay) { | |||
if (serialDay == 0) { | |||
// Special weird case | |||
// day zero should be 31-Dec-1899, but Excel seems to think it is 0-Jan-1900 | |||
switch (_dateFieldId) { | |||
case Calendar.YEAR: return 1900; | |||
case Calendar.MONTH: return 1; | |||
case Calendar.DAY_OF_MONTH: return 0; | |||
} | |||
throw new IllegalStateException("bad date field " + _dateFieldId); | |||
} | |||
Date d = DateUtil.getJavaDate(serialDay, false); // TODO fix 1900/1904 problem | |||
Calendar c = new GregorianCalendar(); | |||
c.setTime(d); | |||
int result = c.get(_dateFieldId); | |||
if (_needsOneBaseAdjustment) { | |||
result++; | |||
} | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
/* ==================================================================== | |||
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.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* @author Josh Micich | |||
*/ | |||
public final class Choose implements Function { | |||
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
if (args.length < 2) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
try { | |||
int ix = evaluateFirstArg(args[0], srcRowIndex, srcColumnIndex); | |||
if (ix < 1 || ix >= args.length) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
ValueEval result = OperandResolver.getSingleValue(args[ix], srcRowIndex, srcColumnIndex); | |||
if (result == MissingArgEval.instance) { | |||
return BlankEval.instance; | |||
} | |||
return result; | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
public static int evaluateFirstArg(ValueEval arg0, int srcRowIndex, int srcColumnIndex) | |||
throws EvaluationException { | |||
ValueEval ev = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
return OperandResolver.coerceValueToInt(ev); | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* ==================================================================== | |||
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; | |||
public final class Column implements Function0Arg, Function1Arg { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
return new NumberEval(srcColumnIndex+1); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
int rnum; | |||
if (arg0 instanceof AreaEval) { | |||
rnum = ((AreaEval) arg0).getFirstColumn(); | |||
} else if (arg0 instanceof RefEval) { | |||
rnum = ((RefEval) arg0).getColumn(); | |||
} 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(srcColumnIndex+1); | |||
} | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
} |
@@ -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 COLUMNS function. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Columns extends Fixed1ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
int result; | |||
if (arg0 instanceof TwoDEval) { | |||
result = ((TwoDEval) arg0).getWidth(); | |||
} else if (arg0 instanceof RefEval) { | |||
result = 1; | |||
} else { // anything else is not valid argument | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return new NumberEval(result); | |||
} | |||
} |
@@ -0,0 +1,77 @@ | |||
/* ==================================================================== | |||
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.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.functions.CountUtils.I_MatchPredicate; | |||
/** | |||
* Counts the number of cells that contain numeric data within | |||
* the list of arguments. | |||
* | |||
* Excel Syntax | |||
* COUNT(value1,value2,...) | |||
* Value1, value2, ... are 1 to 30 arguments representing the values or ranges to be counted. | |||
* | |||
* TODO: Check this properly matches excel on edge cases | |||
* like formula cells, error cells etc | |||
*/ | |||
public final class Count implements Function { | |||
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
int nArgs = args.length; | |||
if (nArgs < 1) { | |||
// too few arguments | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
if (nArgs > 30) { | |||
// too many arguments | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
int temp = 0; | |||
for(int i=0; i<nArgs; i++) { | |||
temp += CountUtils.countArg(args[i], predicate); | |||
} | |||
return new NumberEval(temp); | |||
} | |||
private static final I_MatchPredicate predicate = new I_MatchPredicate() { | |||
public boolean matches(ValueEval valueEval) { | |||
if(valueEval instanceof NumberEval) { | |||
// only numbers are counted | |||
return true; | |||
} | |||
if(valueEval == MissingArgEval.instance) { | |||
// oh yeah, and missing arguments | |||
return true; | |||
} | |||
// error values and string values not counted | |||
return false; | |||
} | |||
}; | |||
} |
@@ -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.RefEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* Common logic for COUNT, COUNTA and COUNTIF | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class CountUtils { | |||
private CountUtils() { | |||
// no instances of this class | |||
} | |||
/** | |||
* Common interface for the matching criteria. | |||
*/ | |||
public interface I_MatchPredicate { | |||
boolean matches(ValueEval x); | |||
} | |||
/** | |||
* @return the number of evaluated cells in the range that match the specified criteria | |||
*/ | |||
public static int countMatchingCellsInArea(TwoDEval areaEval, I_MatchPredicate criteriaPredicate) { | |||
int result = 0; | |||
int height = areaEval.getHeight(); | |||
int width = areaEval.getWidth(); | |||
for (int rrIx=0; rrIx<height; rrIx++) { | |||
for (int rcIx=0; rcIx<width; rcIx++) { | |||
ValueEval ve = areaEval.getValue(rrIx, rcIx); | |||
if(criteriaPredicate.matches(ve)) { | |||
result++; | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
/** | |||
* @return 1 if the evaluated cell matches the specified criteria | |||
*/ | |||
public static int countMatchingCell(RefEval refEval, I_MatchPredicate criteriaPredicate) { | |||
if(criteriaPredicate.matches(refEval.getInnerValueEval())) { | |||
return 1; | |||
} | |||
return 0; | |||
} | |||
public static int countArg(ValueEval eval, I_MatchPredicate criteriaPredicate) { | |||
if (eval == null) { | |||
throw new IllegalArgumentException("eval must not be null"); | |||
} | |||
if (eval instanceof TwoDEval) { | |||
return countMatchingCellsInArea((TwoDEval) eval, criteriaPredicate); | |||
} | |||
if (eval instanceof RefEval) { | |||
return CountUtils.countMatchingCell((RefEval) eval, criteriaPredicate); | |||
} | |||
return criteriaPredicate.matches(eval) ? 1 : 0; | |||
} | |||
} |
@@ -0,0 +1,72 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.functions.CountUtils.I_MatchPredicate; | |||
/** | |||
* Counts the number of cells that contain data within the list of arguments. | |||
* | |||
* Excel Syntax | |||
* COUNTA(value1,value2,...) | |||
* Value1, value2, ... are 1 to 30 arguments representing the values or ranges to be counted. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Counta implements Function { | |||
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
int nArgs = args.length; | |||
if (nArgs < 1) { | |||
// too few arguments | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
if (nArgs > 30) { | |||
// too many arguments | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
int temp = 0; | |||
for(int i=0; i<nArgs; i++) { | |||
temp += CountUtils.countArg(args[i], predicate); | |||
} | |||
return new NumberEval(temp); | |||
} | |||
private static final I_MatchPredicate predicate = new I_MatchPredicate() { | |||
public boolean matches(ValueEval valueEval) { | |||
// Note - observed behavior of Excel: | |||
// Error values like #VALUE!, #REF!, #DIV/0!, #NAME? etc don't cause this COUNTA to return an error | |||
// in fact, they seem to get counted | |||
if(valueEval == BlankEval.instance) { | |||
return false; | |||
} | |||
// Note - everything but BlankEval counts | |||
return true; | |||
} | |||
}; | |||
} |
@@ -0,0 +1,60 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.RefEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.functions.CountUtils.I_MatchPredicate; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* Implementation for the function COUNTBLANK | |||
* <p> | |||
* Syntax: COUNTBLANK ( range ) | |||
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><th>range </th><td>is the range of cells to count blanks</td></tr> | |||
* </table> | |||
* </p> | |||
* | |||
* @author Mads Mohr Christensen | |||
*/ | |||
public final class Countblank extends Fixed1ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
double result; | |||
if (arg0 instanceof RefEval) { | |||
result = CountUtils.countMatchingCell((RefEval) arg0, predicate); | |||
} else if (arg0 instanceof TwoDEval) { | |||
result = CountUtils.countMatchingCellsInArea((TwoDEval) arg0, predicate); | |||
} else { | |||
throw new IllegalArgumentException("Bad range arg type (" + arg0.getClass().getName() + ")"); | |||
} | |||
return new NumberEval(result); | |||
} | |||
private static final I_MatchPredicate predicate = new I_MatchPredicate() { | |||
public boolean matches(ValueEval valueEval) { | |||
// Note - only BlankEval counts | |||
return valueEval == BlankEval.instance; | |||
} | |||
}; | |||
} |
@@ -0,0 +1,528 @@ | |||
/* ==================================================================== | |||
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.regex.Pattern; | |||
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; | |||
import org.apache.poi.ss.formula.functions.CountUtils.I_MatchPredicate; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
import org.apache.poi.ss.usermodel.ErrorConstants; | |||
/** | |||
* Implementation for the function COUNTIF | |||
* <p> | |||
* Syntax: COUNTIF ( range, criteria ) | |||
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><th>range </th><td>is the range of cells to be counted based on the criteria</td></tr> | |||
* <tr><th>criteria</th><td>is used to determine which cells to count</td></tr> | |||
* </table> | |||
* </p> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Countif extends Fixed2ArgFunction { | |||
private static final class CmpOp { | |||
public static final int NONE = 0; | |||
public static final int EQ = 1; | |||
public static final int NE = 2; | |||
public static final int LE = 3; | |||
public static final int LT = 4; | |||
public static final int GT = 5; | |||
public static final int GE = 6; | |||
public static final CmpOp OP_NONE = op("", NONE); | |||
public static final CmpOp OP_EQ = op("=", EQ); | |||
public static final CmpOp OP_NE = op("<>", NE); | |||
public static final CmpOp OP_LE = op("<=", LE); | |||
public static final CmpOp OP_LT = op("<", LT); | |||
public static final CmpOp OP_GT = op(">", GT); | |||
public static final CmpOp OP_GE = op(">=", GE); | |||
private final String _representation; | |||
private final int _code; | |||
private static CmpOp op(String rep, int code) { | |||
return new CmpOp(rep, code); | |||
} | |||
private CmpOp(String representation, int code) { | |||
_representation = representation; | |||
_code = code; | |||
} | |||
/** | |||
* @return number of characters used to represent this operator | |||
*/ | |||
public int getLength() { | |||
return _representation.length(); | |||
} | |||
public int getCode() { | |||
return _code; | |||
} | |||
public static CmpOp getOperator(String value) { | |||
int len = value.length(); | |||
if (len < 1) { | |||
return OP_NONE; | |||
} | |||
char firstChar = value.charAt(0); | |||
switch(firstChar) { | |||
case '=': | |||
return OP_EQ; | |||
case '>': | |||
if (len > 1) { | |||
switch(value.charAt(1)) { | |||
case '=': | |||
return OP_GE; | |||
} | |||
} | |||
return OP_GT; | |||
case '<': | |||
if (len > 1) { | |||
switch(value.charAt(1)) { | |||
case '=': | |||
return OP_LE; | |||
case '>': | |||
return OP_NE; | |||
} | |||
} | |||
return OP_LT; | |||
} | |||
return OP_NONE; | |||
} | |||
public boolean evaluate(boolean cmpResult) { | |||
switch (_code) { | |||
case NONE: | |||
case EQ: | |||
return cmpResult; | |||
case NE: | |||
return !cmpResult; | |||
} | |||
throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '" | |||
+ _representation + "'"); | |||
} | |||
public boolean evaluate(int cmpResult) { | |||
switch (_code) { | |||
case NONE: | |||
case EQ: | |||
return cmpResult == 0; | |||
case NE: return cmpResult != 0; | |||
case LT: return cmpResult < 0; | |||
case LE: return cmpResult <= 0; | |||
case GT: return cmpResult > 0; | |||
case GE: return cmpResult <= 0; | |||
} | |||
throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '" | |||
+ _representation + "'"); | |||
} | |||
public String toString() { | |||
StringBuffer sb = new StringBuffer(64); | |||
sb.append(getClass().getName()); | |||
sb.append(" [").append(_representation).append("]"); | |||
return sb.toString(); | |||
} | |||
public String getRepresentation() { | |||
return _representation; | |||
} | |||
} | |||
private static abstract class MatcherBase implements I_MatchPredicate { | |||
private final CmpOp _operator; | |||
MatcherBase(CmpOp operator) { | |||
_operator = operator; | |||
} | |||
protected final int getCode() { | |||
return _operator.getCode(); | |||
} | |||
protected final boolean evaluate(int cmpResult) { | |||
return _operator.evaluate(cmpResult); | |||
} | |||
protected final boolean evaluate(boolean cmpResult) { | |||
return _operator.evaluate(cmpResult); | |||
} | |||
@Override | |||
public final String toString() { | |||
StringBuffer sb = new StringBuffer(64); | |||
sb.append(getClass().getName()).append(" ["); | |||
sb.append(_operator.getRepresentation()); | |||
sb.append(getValueText()); | |||
sb.append("]"); | |||
return sb.toString(); | |||
} | |||
protected abstract String getValueText(); | |||
} | |||
private static final class NumberMatcher extends MatcherBase { | |||
private final double _value; | |||
public NumberMatcher(double value, CmpOp operator) { | |||
super(operator); | |||
_value = value; | |||
} | |||
@Override | |||
protected String getValueText() { | |||
return String.valueOf(_value); | |||
} | |||
public boolean matches(ValueEval x) { | |||
double testValue; | |||
if(x instanceof StringEval) { | |||
// if the target(x) is a string, but parses as a number | |||
// it may still count as a match, only for the equality operator | |||
switch (getCode()) { | |||
case CmpOp.EQ: | |||
case CmpOp.NONE: | |||
break; | |||
case CmpOp.NE: | |||
// Always matches (inconsistent with above two cases). | |||
// for example '<>123' matches '123', '4', 'abc', etc | |||
return true; | |||
default: | |||
// never matches (also inconsistent with above three cases). | |||
// for example '>5' does not match '6', | |||
return false; | |||
} | |||
StringEval se = (StringEval)x; | |||
Double val = OperandResolver.parseDouble(se.getStringValue()); | |||
if(val == null) { | |||
// x is text that is not a number | |||
return false; | |||
} | |||
return _value == val.doubleValue(); | |||
} else if((x instanceof NumberEval)) { | |||
NumberEval ne = (NumberEval) x; | |||
testValue = ne.getNumberValue(); | |||
} else { | |||
return false; | |||
} | |||
return evaluate(Double.compare(testValue, _value)); | |||
} | |||
} | |||
private static final class BooleanMatcher extends MatcherBase { | |||
private final int _value; | |||
public BooleanMatcher(boolean value, CmpOp operator) { | |||
super(operator); | |||
_value = boolToInt(value); | |||
} | |||
@Override | |||
protected String getValueText() { | |||
return _value == 1 ? "TRUE" : "FALSE"; | |||
} | |||
private static int boolToInt(boolean value) { | |||
return value ? 1 : 0; | |||
} | |||
public boolean matches(ValueEval x) { | |||
int testValue; | |||
if(x instanceof StringEval) { | |||
if (true) { // change to false to observe more intuitive behaviour | |||
// Note - Unlike with numbers, it seems that COUNTIF never matches | |||
// boolean values when the target(x) is a string | |||
return false; | |||
} | |||
StringEval se = (StringEval)x; | |||
Boolean val = parseBoolean(se.getStringValue()); | |||
if(val == null) { | |||
// x is text that is not a boolean | |||
return false; | |||
} | |||
testValue = boolToInt(val.booleanValue()); | |||
} else if((x instanceof BoolEval)) { | |||
BoolEval be = (BoolEval) x; | |||
testValue = boolToInt(be.getBooleanValue()); | |||
} else { | |||
return false; | |||
} | |||
return evaluate(testValue - _value); | |||
} | |||
} | |||
private static final class ErrorMatcher extends MatcherBase { | |||
private final int _value; | |||
public ErrorMatcher(int errorCode, CmpOp operator) { | |||
super(operator); | |||
_value = errorCode; | |||
} | |||
@Override | |||
protected String getValueText() { | |||
return ErrorConstants.getText(_value); | |||
} | |||
public boolean matches(ValueEval x) { | |||
if(x instanceof ErrorEval) { | |||
int testValue = ((ErrorEval)x).getErrorCode(); | |||
return evaluate(testValue - _value); | |||
} | |||
return false; | |||
} | |||
} | |||
private static final class StringMatcher extends MatcherBase { | |||
private final String _value; | |||
private final Pattern _pattern; | |||
public StringMatcher(String value, CmpOp operator) { | |||
super(operator); | |||
_value = value; | |||
switch(operator.getCode()) { | |||
case CmpOp.NONE: | |||
case CmpOp.EQ: | |||
case CmpOp.NE: | |||
_pattern = getWildCardPattern(value); | |||
break; | |||
default: | |||
// pattern matching is never used for < > <= => | |||
_pattern = null; | |||
} | |||
} | |||
@Override | |||
protected String getValueText() { | |||
if (_pattern == null) { | |||
return _value; | |||
} | |||
return _pattern.pattern(); | |||
} | |||
public boolean matches(ValueEval x) { | |||
if (x instanceof BlankEval) { | |||
switch(getCode()) { | |||
case CmpOp.NONE: | |||
case CmpOp.EQ: | |||
return _value.length() == 0; | |||
} | |||
// no other criteria matches a blank cell | |||
return false; | |||
} | |||
if(!(x instanceof StringEval)) { | |||
// must always be string | |||
// even if match str is wild, but contains only digits | |||
// e.g. '4*7', NumberEval(4567) does not match | |||
return false; | |||
} | |||
String testedValue = ((StringEval) x).getStringValue(); | |||
if (testedValue.length() < 1 && _value.length() < 1) { | |||
// odd case: criteria '=' behaves differently to criteria '' | |||
switch(getCode()) { | |||
case CmpOp.NONE: return true; | |||
case CmpOp.EQ: return false; | |||
case CmpOp.NE: return true; | |||
} | |||
return false; | |||
} | |||
if (_pattern != null) { | |||
return evaluate(_pattern.matcher(testedValue).matches()); | |||
} | |||
return evaluate(testedValue.compareTo(_value)); | |||
} | |||
/** | |||
* Translates Excel countif wildcard strings into java regex strings | |||
* @return <code>null</code> 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<len; i++) { | |||
char ch = value.charAt(i); | |||
switch(ch) { | |||
case '?': | |||
hasWildCard = true; | |||
// match exactly one character | |||
sb.append('.'); | |||
continue; | |||
case '*': | |||
hasWildCard = true; | |||
// match one or more occurrences of any character | |||
sb.append(".*"); | |||
continue; | |||
case '~': | |||
if (i+1<len) { | |||
ch = value.charAt(i+1); | |||
switch (ch) { | |||
case '?': | |||
case '*': | |||
hasWildCard = true; | |||
sb.append('[').append(ch).append(']'); | |||
i++; // Note - incrementing loop variable here | |||
continue; | |||
} | |||
} | |||
// else not '~?' or '~*' | |||
sb.append('~'); // just plain '~' | |||
continue; | |||
case '.': | |||
case '$': | |||
case '^': | |||
case '[': | |||
case ']': | |||
case '(': | |||
case ')': | |||
// escape literal characters that would have special meaning in regex | |||
sb.append("\\").append(ch); | |||
continue; | |||
} | |||
sb.append(ch); | |||
} | |||
if (hasWildCard) { | |||
return Pattern.compile(sb.toString()); | |||
} | |||
return null; | |||
} | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
I_MatchPredicate mp = createCriteriaPredicate(arg1, srcRowIndex, srcColumnIndex); | |||
if(mp == null) { | |||
// If the criteria arg is a reference to a blank cell, countif always returns zero. | |||
return NumberEval.ZERO; | |||
} | |||
double result = countMatchingCellsInArea(arg0, mp); | |||
return new NumberEval(result); | |||
} | |||
/** | |||
* @return the number of evaluated cells in the range that match the specified criteria | |||
*/ | |||
private double countMatchingCellsInArea(ValueEval rangeArg, I_MatchPredicate criteriaPredicate) { | |||
if (rangeArg instanceof RefEval) { | |||
return CountUtils.countMatchingCell((RefEval) rangeArg, criteriaPredicate); | |||
} else if (rangeArg instanceof TwoDEval) { | |||
return CountUtils.countMatchingCellsInArea((TwoDEval) rangeArg, criteriaPredicate); | |||
} else { | |||
throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")"); | |||
} | |||
} | |||
/** | |||
* Creates a criteria predicate object for the supplied criteria arg | |||
* @return <code>null</code> if the arg evaluates to blank. | |||
*/ | |||
/* package */ static I_MatchPredicate createCriteriaPredicate(ValueEval arg, int srcRowIndex, int srcColumnIndex) { | |||
ValueEval evaluatedCriteriaArg = evaluateCriteriaArg(arg, srcRowIndex, srcColumnIndex); | |||
if(evaluatedCriteriaArg instanceof NumberEval) { | |||
return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue(), CmpOp.OP_NONE); | |||
} | |||
if(evaluatedCriteriaArg instanceof BoolEval) { | |||
return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue(), CmpOp.OP_NONE); | |||
} | |||
if(evaluatedCriteriaArg instanceof StringEval) { | |||
return createGeneralMatchPredicate((StringEval)evaluatedCriteriaArg); | |||
} | |||
if(evaluatedCriteriaArg instanceof ErrorEval) { | |||
return new ErrorMatcher(((ErrorEval)evaluatedCriteriaArg).getErrorCode(), CmpOp.OP_NONE); | |||
} | |||
if(evaluatedCriteriaArg == BlankEval.instance) { | |||
return null; | |||
} | |||
throw new RuntimeException("Unexpected type for criteria (" | |||
+ evaluatedCriteriaArg.getClass().getName() + ")"); | |||
} | |||
/** | |||
* | |||
* @return the de-referenced criteria arg (possibly {@link ErrorEval}) | |||
*/ | |||
private static ValueEval evaluateCriteriaArg(ValueEval arg, int srcRowIndex, int srcColumnIndex) { | |||
try { | |||
return OperandResolver.getSingleValue(arg, srcRowIndex, (short)srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
/** | |||
* When the second argument is a string, many things are possible | |||
*/ | |||
private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) { | |||
String value = stringEval.getStringValue(); | |||
CmpOp operator = CmpOp.getOperator(value); | |||
value = value.substring(operator.getLength()); | |||
Boolean booleanVal = parseBoolean(value); | |||
if(booleanVal != null) { | |||
return new BooleanMatcher(booleanVal.booleanValue(), operator); | |||
} | |||
Double doubleVal = OperandResolver.parseDouble(value); | |||
if(doubleVal != null) { | |||
return new NumberMatcher(doubleVal.doubleValue(), operator); | |||
} | |||
ErrorEval ee = parseError(value); | |||
if (ee != null) { | |||
return new ErrorMatcher(ee.getErrorCode(), operator); | |||
} | |||
//else - just a plain string with no interpretation. | |||
return new StringMatcher(value, operator); | |||
} | |||
private static ErrorEval parseError(String value) { | |||
if (value.length() < 4 || value.charAt(0) != '#') { | |||
return null; | |||
} | |||
if (value.equals("#NULL!")) return ErrorEval.NULL_INTERSECTION; | |||
if (value.equals("#DIV/0!")) return ErrorEval.DIV_ZERO; | |||
if (value.equals("#VALUE!")) return ErrorEval.VALUE_INVALID; | |||
if (value.equals("#REF!")) return ErrorEval.REF_INVALID; | |||
if (value.equals("#NAME?")) return ErrorEval.NAME_INVALID; | |||
if (value.equals("#NUM!")) return ErrorEval.NUM_ERROR; | |||
if (value.equals("#N/A")) return ErrorEval.NA; | |||
return null; | |||
} | |||
/** | |||
* Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers. | |||
*/ | |||
/* package */ static Boolean parseBoolean(String strRep) { | |||
if (strRep.length() < 1) { | |||
return null; | |||
} | |||
switch(strRep.charAt(0)) { | |||
case 't': | |||
case 'T': | |||
if("TRUE".equalsIgnoreCase(strRep)) { | |||
return Boolean.TRUE; | |||
} | |||
break; | |||
case 'f': | |||
case 'F': | |||
if("FALSE".equalsIgnoreCase(strRep)) { | |||
return Boolean.FALSE; | |||
} | |||
break; | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,92 @@ | |||
/* ==================================================================== | |||
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.Calendar; | |||
import java.util.GregorianCalendar; | |||
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.ValueEval; | |||
import org.apache.poi.ss.usermodel.DateUtil; | |||
/** | |||
* Implementation for the Excel function DATE | |||
* | |||
* @author Pavel Krupets (pkrupets at palmtreebusiness dot com) | |||
*/ | |||
public final class DateFunc extends Fixed3ArgFunction { | |||
public static final Function instance = new DateFunc(); | |||
private DateFunc() { | |||
// no fields to initialise | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
double result; | |||
try { | |||
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
double d2 = NumericFunction.singleOperandEvaluate(arg2, srcRowIndex, srcColumnIndex); | |||
result = evaluate(getYear(d0), (int) (d1 - 1), (int) d2); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
private static double evaluate(int year, int month, int pDay) throws EvaluationException { | |||
if (year < 0 || month < 0 || pDay < 0) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
if (year == 1900 && month == Calendar.FEBRUARY && pDay == 29) { | |||
return 60.0; | |||
} | |||
int day = pDay; | |||
if (year == 1900) { | |||
if ((month == Calendar.JANUARY && day >= 60) || | |||
(month == Calendar.FEBRUARY && day >= 30)) { | |||
day--; | |||
} | |||
} | |||
Calendar c = new GregorianCalendar(); | |||
c.set(year, month, day, 0, 0, 0); | |||
c.set(Calendar.MILLISECOND, 0); | |||
return DateUtil.getExcelDate(c.getTime(), false); // TODO - fix 1900/1904 problem | |||
} | |||
private static int getYear(double d) { | |||
int year = (int)d; | |||
if (year < 0) { | |||
return -1; | |||
} | |||
return year < 1900 ? 1900 + year : year; | |||
} | |||
} |
@@ -0,0 +1,118 @@ | |||
/* ==================================================================== | |||
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.Calendar; | |||
import java.util.GregorianCalendar; | |||
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.ValueEval; | |||
import org.apache.poi.ss.usermodel.DateUtil; | |||
/** | |||
* Calculates the number of days between two dates based on a 360-day year | |||
* (twelve 30-day months), which is used in some accounting calculations. Use | |||
* this function to help compute payments if your accounting system is based on | |||
* twelve 30-day months. | |||
* | |||
* @author PUdalau | |||
*/ | |||
public class Days360 extends Var2or3ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
double result; | |||
try { | |||
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
result = evaluate(d0, d1, false); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
double result; | |||
try { | |||
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
ValueEval ve = OperandResolver.getSingleValue(arg2, srcRowIndex, srcColumnIndex); | |||
Boolean method = OperandResolver.coerceValueToBoolean(ve, false); | |||
result = evaluate(d0, d1, method == null ? false : method.booleanValue()); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
private static double evaluate(double d0, double d1, boolean method) { | |||
Calendar startingDate = getStartingDate(d0); | |||
Calendar endingDate = getEndingDateAccordingToStartingDate(d1, startingDate); | |||
long startingDay = startingDate.get(Calendar.MONTH) * 30 + startingDate.get(Calendar.DAY_OF_MONTH); | |||
long endingDay = (endingDate.get(Calendar.YEAR) - startingDate.get(Calendar.YEAR)) * 360 | |||
+ endingDate.get(Calendar.MONTH) * 30 + endingDate.get(Calendar.DAY_OF_MONTH); | |||
return endingDay - startingDay; | |||
} | |||
private static Calendar getDate(double date) { | |||
Calendar processedDate = new GregorianCalendar(); | |||
processedDate.setTime(DateUtil.getJavaDate(date, false)); | |||
return processedDate; | |||
} | |||
private static Calendar getStartingDate(double date) { | |||
Calendar startingDate = getDate(date); | |||
if (isLastDayOfMonth(startingDate)) { | |||
startingDate.set(Calendar.DAY_OF_MONTH, 30); | |||
} | |||
return startingDate; | |||
} | |||
private static Calendar getEndingDateAccordingToStartingDate(double date, Calendar startingDate) { | |||
Calendar endingDate = getDate(date); | |||
endingDate.setTime(DateUtil.getJavaDate(date, false)); | |||
if (isLastDayOfMonth(endingDate)) { | |||
if (startingDate.get(Calendar.DATE) < 30) { | |||
endingDate = getFirstDayOfNextMonth(endingDate); | |||
} | |||
} | |||
return endingDate; | |||
} | |||
private static boolean isLastDayOfMonth(Calendar date) { | |||
Calendar clone = (Calendar) date.clone(); | |||
clone.add(java.util.Calendar.MONTH, 1); | |||
clone.add(java.util.Calendar.DAY_OF_MONTH, -1); | |||
int lastDayOfMonth = clone.get(Calendar.DAY_OF_MONTH); | |||
return date.get(Calendar.DAY_OF_MONTH) == lastDayOfMonth; | |||
} | |||
private static Calendar getFirstDayOfNextMonth(Calendar date) { | |||
Calendar newDate = (Calendar) date.clone(); | |||
if (date.get(Calendar.MONTH) < Calendar.DECEMBER) { | |||
newDate.set(Calendar.MONTH, date.get(Calendar.MONTH) + 1); | |||
} else { | |||
newDate.set(Calendar.MONTH, 1); | |||
newDate.set(Calendar.YEAR, date.get(Calendar.YEAR) + 1); | |||
} | |||
newDate.set(Calendar.DATE, 1); | |||
return newDate; | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.usermodel.ErrorConstants; | |||
/** | |||
* Implementation for the ERROR.TYPE() Excel function. | |||
* <p> | |||
* <b>Syntax:</b><br/> | |||
* <b>ERROR.TYPE</b>(<b>errorValue</b>)</p> | |||
* <p> | |||
* Returns a number corresponding to the error type of the supplied argument.<p/> | |||
* <p> | |||
* <table border="1" cellpadding="1" cellspacing="1" summary="Return values for ERROR.TYPE()"> | |||
* <tr><td>errorValue</td><td>Return Value</td></tr> | |||
* <tr><td>#NULL!</td><td>1</td></tr> | |||
* <tr><td>#DIV/0!</td><td>2</td></tr> | |||
* <tr><td>#VALUE!</td><td>3</td></tr> | |||
* <tr><td>#REF!</td><td>4</td></tr> | |||
* <tr><td>#NAME?</td><td>5</td></tr> | |||
* <tr><td>#NUM!</td><td>6</td></tr> | |||
* <tr><td>#N/A!</td><td>7</td></tr> | |||
* <tr><td>everything else</td><td>#N/A!</td></tr> | |||
* </table> | |||
* | |||
* Note - the results of ERROR.TYPE() are different to the constants defined in | |||
* <tt>ErrorConstants</tt>. | |||
* </p> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Errortype extends Fixed1ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
try { | |||
OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
return ErrorEval.NA; | |||
} catch (EvaluationException e) { | |||
int result = translateErrorCodeToErrorTypeValue(e.getErrorEval().getErrorCode()); | |||
return new NumberEval(result); | |||
} | |||
} | |||
private int translateErrorCodeToErrorTypeValue(int errorCode) { | |||
switch (errorCode) { | |||
case ErrorConstants.ERROR_NULL: return 1; | |||
case ErrorConstants.ERROR_DIV_0: return 2; | |||
case ErrorConstants.ERROR_VALUE: return 3; | |||
case ErrorConstants.ERROR_REF: return 4; | |||
case ErrorConstants.ERROR_NAME: return 5; | |||
case ErrorConstants.ERROR_NUM: return 6; | |||
case ErrorConstants.ERROR_NA : return 7; | |||
} | |||
throw new IllegalArgumentException("Invalid error code (" + errorCode + ")"); | |||
} | |||
} |
@@ -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.functions; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* | |||
*/ | |||
public final class Even extends NumericFunction.OneArg { | |||
private static final long PARITY_MASK = 0xFFFFFFFFFFFFFFFEL; | |||
protected double evaluate(double d) { | |||
if (d==0) { | |||
return 0; | |||
} | |||
long result; | |||
if (d>0) { | |||
result = calcEven(d); | |||
} else { | |||
result = -calcEven(-d); | |||
} | |||
return result; | |||
} | |||
private static long calcEven(double d) { | |||
long x = ((long) d) & PARITY_MASK; | |||
if (x == d) { | |||
return x; | |||
} | |||
return x + 2; | |||
} | |||
} |
@@ -0,0 +1,118 @@ | |||
/* ==================================================================== | |||
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.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public abstract class FinanceFunction implements Function3Arg, Function4Arg { | |||
private static final ValueEval DEFAULT_ARG3 = NumberEval.ZERO; | |||
private static final ValueEval DEFAULT_ARG4 = BoolEval.FALSE; | |||
protected FinanceFunction() { | |||
// no instance fields | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, DEFAULT_ARG3); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2, ValueEval arg3) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, arg3, DEFAULT_ARG4); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2, ValueEval arg3, ValueEval arg4) { | |||
double result; | |||
try { | |||
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
double d2 = NumericFunction.singleOperandEvaluate(arg2, srcRowIndex, srcColumnIndex); | |||
double d3 = NumericFunction.singleOperandEvaluate(arg3, srcRowIndex, srcColumnIndex); | |||
double d4 = NumericFunction.singleOperandEvaluate(arg4, srcRowIndex, srcColumnIndex); | |||
result = evaluate(d0, d1, d2, d3, d4 != 0.0); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
switch (args.length) { | |||
case 3: | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], DEFAULT_ARG3, DEFAULT_ARG4); | |||
case 4: | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], args[3], DEFAULT_ARG4); | |||
case 5: | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], args[3], args[4]); | |||
} | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
protected double evaluate(double[] ds) throws EvaluationException { | |||
// All finance functions have 3 to 5 args, first 4 are numbers, last is boolean | |||
// default for last 2 args are 0.0 and false | |||
// Text boolean literals are not valid for the last arg | |||
double arg3 = 0.0; | |||
double arg4 = 0.0; | |||
switch(ds.length) { | |||
case 5: | |||
arg4 = ds[4]; | |||
case 4: | |||
arg3 = ds[3]; | |||
case 3: | |||
break; | |||
default: | |||
throw new IllegalStateException("Wrong number of arguments"); | |||
} | |||
return evaluate(ds[0], ds[1], ds[2], arg3, arg4!=0.0); | |||
} | |||
protected abstract double evaluate(double rate, double arg1, double arg2, double arg3, boolean type) throws EvaluationException ; | |||
public static final Function FV = new FinanceFunction() { | |||
protected double evaluate(double rate, double arg1, double arg2, double arg3, boolean type) { | |||
return FinanceLib.fv(rate, arg1, arg2, arg3, type); | |||
} | |||
}; | |||
public static final Function NPER = new FinanceFunction() { | |||
protected double evaluate(double rate, double arg1, double arg2, double arg3, boolean type) { | |||
return FinanceLib.nper(rate, arg1, arg2, arg3, type); | |||
} | |||
}; | |||
public static final Function PMT = new FinanceFunction() { | |||
protected double evaluate(double rate, double arg1, double arg2, double arg3, boolean type) { | |||
return FinanceLib.pmt(rate, arg1, arg2, arg3, type); | |||
} | |||
}; | |||
public static final Function PV = new FinanceFunction() { | |||
protected double evaluate(double rate, double arg1, double arg2, double arg3, boolean type) { | |||
return FinanceLib.pv(rate, arg1, arg2, arg3, type); | |||
} | |||
}; | |||
} |
@@ -0,0 +1,185 @@ | |||
/* ==================================================================== | |||
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; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* | |||
* | |||
* This class is a functon library for common fiscal functions. | |||
* <b>Glossary of terms/abbreviations:</b> | |||
* <br/> | |||
* <ul> | |||
* <li><em>FV:</em> Future Value</li> | |||
* <li><em>PV:</em> Present Value</li> | |||
* <li><em>NPV:</em> Net Present Value</li> | |||
* <li><em>PMT:</em> (Periodic) Payment</li> | |||
* | |||
* </ul> | |||
* For more info on the terms/abbreviations please use the references below | |||
* (hyperlinks are subject to change): | |||
* </br>Online References: | |||
* <ol> | |||
* <li>GNU Emacs Calc 2.02 Manual: http://theory.uwinnipeg.ca/gnu/calc/calc_203.html</li> | |||
* <li>Yahoo Financial Glossary: http://biz.yahoo.com/f/g/nn.html#y</li> | |||
* <li>MS Excel function reference: http://office.microsoft.com/en-us/assistance/CH062528251033.aspx</li> | |||
* </ol> | |||
* <h3>Implementation Notes:</h3> | |||
* Symbols used in the formulae that follow:<br/> | |||
* <ul> | |||
* <li>p: present value</li> | |||
* <li>f: future value</li> | |||
* <li>n: number of periods</li> | |||
* <li>y: payment (in each period)</li> | |||
* <li>r: rate</li> | |||
* <li>^: the power operator (NOT the java bitwise XOR operator!)</li> | |||
* </ul> | |||
* [From MS Excel function reference] Following are some of the key formulas | |||
* that are used in this implementation: | |||
* <pre> | |||
* p(1+r)^n + y(1+rt)((1+r)^n-1)/r + f=0 ...{when r!=0} | |||
* ny + p + f=0 ...{when r=0} | |||
* </pre> | |||
*/ | |||
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<iSize; i++) { | |||
npv += cfs[i] / trate; | |||
trate *= r1; | |||
} | |||
return npv; | |||
} | |||
/** | |||
* | |||
* @param r | |||
* @param n | |||
* @param p | |||
* @param f | |||
* @param t | |||
*/ | |||
public static double pmt(double r, double n, double p, double f, boolean t) { | |||
double retval = 0; | |||
if (r == 0) { | |||
retval = -1*(f+p)/n; | |||
} | |||
else { | |||
double r1 = r + 1; | |||
retval = ( f + p * Math.pow(r1, n) ) * r | |||
/ | |||
((t ? r1 : 1) * (1 - Math.pow(r1, n))); | |||
} | |||
return retval; | |||
} | |||
/** | |||
* | |||
* @param r | |||
* @param y | |||
* @param p | |||
* @param f | |||
* @param t | |||
*/ | |||
public static double nper(double r, double y, double p, double f, boolean t) { | |||
double retval = 0; | |||
if (r == 0) { | |||
retval = -1 * (f + p) / y; | |||
} else { | |||
double r1 = r + 1; | |||
double ryr = (t ? r1 : 1) * y / r; | |||
double a1 = ((ryr - f) < 0) | |||
? Math.log(f - ryr) | |||
: Math.log(ryr - f); | |||
double a2 = ((ryr - f) < 0) | |||
? Math.log(-p - ryr) | |||
: Math.log(p + ryr); | |||
double a3 = Math.log(r1); | |||
retval = (a1 - a2) / a3; | |||
} | |||
return retval; | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Convenience base class for functions that only take zero arguments. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public abstract class Fixed0ArgFunction implements Function0Arg { | |||
public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
if (args.length != 0) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return evaluate(srcRowIndex, srcColumnIndex); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Convenience base class for functions that must take exactly one argument. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public abstract class Fixed1ArgFunction implements Function1Arg { | |||
public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
if (args.length != 1) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0]); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Convenience base class for functions that must take exactly two arguments. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public abstract class Fixed2ArgFunction implements Function2Arg { | |||
public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
if (args.length != 2) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1]); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Convenience base class for functions that must take exactly three arguments. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public abstract class Fixed3ArgFunction implements Function3Arg { | |||
public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
if (args.length != 3) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2]); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Convenience base class for functions that must take exactly four arguments. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public abstract class Fixed4ArgFunction implements Function4Arg { | |||
public final ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
if (args.length != 4) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], args[3]); | |||
} | |||
} |
@@ -0,0 +1,51 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.ss.formula.functions; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.OperationEvaluationContext; | |||
/** | |||
* For most Excel functions, involving references ((cell, area), (2d, 3d)), the references are | |||
* passed in as arguments, and the exact location remains fixed. However, a select few Excel | |||
* functions have the ability to access cells that were not part of any reference passed as an | |||
* argument.<br/> | |||
* Two important functions with this feature are <b>INDIRECT</b> and <b>OFFSET</b><p/> | |||
* | |||
* When POI evaluates formulas, each reference argument is capable of evaluating any cell inside | |||
* its range. Actually, even cells outside the reference range but on the same sheet can be | |||
* evaluated. This allows <b>OFFSET</b> to be implemented like most other functions - taking only | |||
* the arguments, and source cell coordinates. | |||
* | |||
* For the moment this interface only exists to serve the <b>INDIRECT</b> which can decode | |||
* arbitrary text into cell references, and evaluate them.. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public interface FreeRefFunction { | |||
/** | |||
* @param args the pre-evaluated arguments for this function. args is never <code>null</code>, | |||
* 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 <code>null</code>. Possibly an instance of <tt>ErrorEval</tt> in the case of | |||
* a specified Excel error (Exceptions are never thrown to represent Excel errors). | |||
*/ | |||
ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec); | |||
} |
@@ -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 <code>null</code>. | |||
* @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 <code>null</code>. | |||
* <b>Note</b> - Excel uses the error code <i>#NUM!</i> instead of IEEE <i>NaN</i>, 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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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.<p/> | |||
* | |||
* HLOOKUP finds a column in a lookup table by the first row value and returns the value from another row.<br/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>HLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>row_index_num</b>, range_lookup)<p/> | |||
* | |||
* <b>lookup_value</b> The value to be found in the first column of the table array.<br/> | |||
* <b>table_array</b> An area reference for the lookup data. <br/> | |||
* <b>row_index_num</b> a 1 based index specifying which row value of the lookup data will be returned.<br/> | |||
* <b>range_lookup</b> If TRUE (default), HLOOKUP finds the largest value less than or equal to | |||
* the lookup_value. If FALSE, only exact matches will be considered<br/> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Hlookup extends Var3or4ArgFunction { | |||
private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE; | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, DEFAULT_ARG3); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2, ValueEval arg3) { | |||
try { | |||
// Evaluation order: | |||
// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 row_index, fetch result | |||
ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
TwoDEval tableArray = LookupUtils.resolveTableArrayArg(arg1); | |||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcRowIndex, srcColumnIndex); | |||
int colIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createRowVector(tableArray, 0), isRangeLookup); | |||
int rowIndex = LookupUtils.resolveRowOrColIndexArg(arg2, srcRowIndex, srcColumnIndex); | |||
ValueVector resultCol = createResultColumnVector(tableArray, rowIndex); | |||
return resultCol.getItem(colIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
/** | |||
* Returns one column from an <tt>AreaEval</tt> | |||
* | |||
* @param rowIndex assumed to be non-negative | |||
* | |||
* @throws EvaluationException (#REF!) if colIndex is too high | |||
*/ | |||
private ValueVector createResultColumnVector(TwoDEval tableArray, int rowIndex) throws EvaluationException { | |||
if(rowIndex >= tableArray.getHeight()) { | |||
throw EvaluationException.invalidRef(); | |||
} | |||
return LookupUtils.createRowVector(tableArray, rowIndex); | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
/* ==================================================================== | |||
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; | |||
/** | |||
* Implementation of Excel HYPERLINK function.<p/> | |||
* | |||
* In Excel this function has special behaviour - it causes the displayed cell value to behave like | |||
* a hyperlink in the GUI. From an evaluation perspective however, it is very simple.<p/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>HYPERLINK</b>(<b>link_location</b>, friendly_name)<p/> | |||
* | |||
* <b>link_location</b> The URL of the hyperlink <br/> | |||
* <b>friendly_name</b> (optional) the value to display<p/> | |||
* | |||
* Returns last argument. Leaves type unchanged (does not convert to {@link org.apache.poi.ss.formula.eval.StringEval}). | |||
* | |||
* @author Wayne Clingingsmith | |||
*/ | |||
public final class Hyperlink extends Var1or2ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
return arg0; | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
// note - if last arg is MissingArgEval, result will be NumberEval.ZERO, | |||
// but WorkbookEvaluator does that translation | |||
return arg1; | |||
} | |||
} |
@@ -0,0 +1,79 @@ | |||
/* ==================================================================== | |||
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.BoolEval; | |||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Implementation for the Excel function IF | |||
* | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public final class IfFunc extends Var2or3ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
boolean b; | |||
try { | |||
b = evaluateFirstArg(arg0, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (b) { | |||
if (arg1 == MissingArgEval.instance) { | |||
return BlankEval.instance; | |||
} | |||
return arg1; | |||
} | |||
return BoolEval.FALSE; | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
boolean b; | |||
try { | |||
b = evaluateFirstArg(arg0, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (b) { | |||
if (arg1 == MissingArgEval.instance) { | |||
return BlankEval.instance; | |||
} | |||
return arg1; | |||
} | |||
if (arg2 == MissingArgEval.instance) { | |||
return BlankEval.instance; | |||
} | |||
return arg2; | |||
} | |||
public static boolean evaluateFirstArg(ValueEval arg, int srcCellRow, int srcCellCol) | |||
throws EvaluationException { | |||
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | |||
Boolean b = OperandResolver.coerceValueToBoolean(ve, false); | |||
if (b == null) { | |||
return false; | |||
} | |||
return b.booleanValue(); | |||
} | |||
} |
@@ -0,0 +1,171 @@ | |||
/* ==================================================================== | |||
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.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
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 the Excel function INDEX | |||
* <p> | |||
* | |||
* Syntax : <br/> | |||
* INDEX ( reference, row_num[, column_num [, area_num]])</br> | |||
* INDEX ( array, row_num[, column_num]) | |||
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><th>reference</th><td>typically an area reference, possibly a union of areas</td></tr> | |||
* <tr><th>array</th><td>a literal array value (currently not supported)</td></tr> | |||
* <tr><th>row_num</th><td>selects the row within the array or area reference</td></tr> | |||
* <tr><th>column_num</th><td>selects column within the array or area reference. default is 1</td></tr> | |||
* <tr><th>area_num</th><td>used when reference is a union of areas</td></tr> | |||
* </table> | |||
* </p> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Index implements Function2Arg, Function3Arg, Function4Arg { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
TwoDEval reference = convertFirstArg(arg0); | |||
int columnIx = 0; | |||
try { | |||
int rowIx = resolveIndexArg(arg1, srcRowIndex, srcColumnIndex); | |||
if (!reference.isColumn()) { | |||
if (!reference.isRow()) { | |||
// always an error with 2-D area refs | |||
// Note - the type of error changes if the pRowArg is negative | |||
return ErrorEval.REF_INVALID; | |||
} | |||
// When the two-arg version of INDEX() has been invoked and the reference | |||
// is a single column ref, the row arg seems to get used as the column index | |||
columnIx = rowIx; | |||
rowIx = 0; | |||
} | |||
return getValueFromArea(reference, rowIx, columnIx); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
TwoDEval reference = convertFirstArg(arg0); | |||
try { | |||
int columnIx = resolveIndexArg(arg2, srcRowIndex, srcColumnIndex); | |||
int rowIx = resolveIndexArg(arg1, srcRowIndex, srcColumnIndex); | |||
return getValueFromArea(reference, rowIx, columnIx); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2, ValueEval arg3) { | |||
throw new RuntimeException("Incomplete code" | |||
+ " - don't know how to support the 'area_num' parameter yet)"); | |||
// Excel expression might look like this "INDEX( (A1:B4, C3:D6, D2:E5 ), 1, 2, 3) | |||
// In this example, the 3rd area would be used i.e. D2:E5, and the overall result would be E2 | |||
// Token array might be encoded like this: MemAreaPtg, AreaPtg, AreaPtg, UnionPtg, UnionPtg, ParenthesesPtg | |||
// The formula parser doesn't seem to support this yet. Not sure if the evaluator does either | |||
} | |||
private static TwoDEval convertFirstArg(ValueEval arg0) { | |||
ValueEval firstArg = arg0; | |||
if (firstArg instanceof RefEval) { | |||
// convert to area ref for simpler code in getValueFromArea() | |||
return ((RefEval)firstArg).offset(0, 0, 0, 0); | |||
} | |||
if((firstArg instanceof TwoDEval)) { | |||
return (TwoDEval) firstArg; | |||
} | |||
// else the other variation of this function takes an array as the first argument | |||
// it seems like interface 'ArrayEval' does not even exist yet | |||
throw new RuntimeException("Incomplete code - cannot handle first arg of type (" | |||
+ firstArg.getClass().getName() + ")"); | |||
} | |||
public 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]); | |||
case 4: | |||
return evaluate(srcRowIndex, srcColumnIndex, args[0], args[1], args[2], args[3]); | |||
} | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
private static ValueEval getValueFromArea(TwoDEval ae, int pRowIx, int pColumnIx) | |||
throws EvaluationException { | |||
assert pRowIx >= 0; | |||
assert pColumnIx >= 0; | |||
TwoDEval result = ae; | |||
if (pRowIx != 0) { | |||
// Slightly irregular logic for bounds checking errors | |||
if (pRowIx > ae.getHeight()) { | |||
// high bounds check fail gives #REF! if arg was explicitly passed | |||
throw new EvaluationException(ErrorEval.REF_INVALID); | |||
} | |||
result = result.getRow(pRowIx-1); | |||
} | |||
if (pColumnIx != 0) { | |||
// Slightly irregular logic for bounds checking errors | |||
if (pColumnIx > ae.getWidth()) { | |||
// high bounds check fail gives #REF! if arg was explicitly passed | |||
throw new EvaluationException(ErrorEval.REF_INVALID); | |||
} | |||
result = result.getColumn(pColumnIx-1); | |||
} | |||
return result; | |||
} | |||
/** | |||
* @param arg a 1-based index. | |||
* @return the resolved 1-based index. Zero if the arg was missing or blank | |||
* @throws EvaluationException if the arg is an error value evaluates to a negative numeric value | |||
*/ | |||
private static int resolveIndexArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { | |||
ValueEval ev = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | |||
if (ev == MissingArgEval.instance) { | |||
return 0; | |||
} | |||
if (ev == BlankEval.instance) { | |||
return 0; | |||
} | |||
int result = OperandResolver.coerceValueToInt(ev); | |||
if (result < 0) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,239 @@ | |||
/* ==================================================================== | |||
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.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.OperationEvaluationContext; | |||
/** | |||
* Implementation for Excel function INDIRECT<p/> | |||
* | |||
* INDIRECT() returns the cell or area reference denoted by the text argument.<p/> | |||
* | |||
* <b>Syntax</b>:</br> | |||
* <b>INDIRECT</b>(<b>ref_text</b>,isA1Style)<p/> | |||
* | |||
* <b>ref_text</b> a string representation of the desired reference as it would | |||
* normally be written in a cell formula.<br/> | |||
* <b>isA1Style</b> (default TRUE) specifies whether the ref_text should be | |||
* interpreted as A1-style or R1C1-style. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Indirect implements FreeRefFunction { | |||
public static final FreeRefFunction instance = new Indirect(); | |||
private Indirect() { | |||
// enforce singleton | |||
} | |||
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { | |||
if (args.length < 1) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
boolean isA1style; | |||
String text; | |||
try { | |||
ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec | |||
.getColumnIndex()); | |||
text = OperandResolver.coerceValueToString(ve); | |||
switch (args.length) { | |||
case 1: | |||
isA1style = true; | |||
break; | |||
case 2: | |||
isA1style = evaluateBooleanArg(args[1], ec); | |||
break; | |||
default: | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return evaluateIndirect(ec, text, isA1style); | |||
} | |||
private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec) | |||
throws EvaluationException { | |||
ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex()); | |||
if (ve == BlankEval.instance || ve == MissingArgEval.instance) { | |||
return false; | |||
} | |||
// numeric quantities follow standard boolean conversion rules | |||
// for strings, only "TRUE" and "FALSE" (case insensitive) are valid | |||
return OperandResolver.coerceValueToBoolean(ve, false).booleanValue(); | |||
} | |||
private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text, | |||
boolean isA1style) { | |||
// Search backwards for '!' because sheet names can contain '!' | |||
int plingPos = text.lastIndexOf('!'); | |||
String workbookName; | |||
String sheetName; | |||
String refText; // whitespace around this gets trimmed OK | |||
if (plingPos < 0) { | |||
workbookName = null; | |||
sheetName = null; | |||
refText = text; | |||
} else { | |||
String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos)); | |||
if (parts == null) { | |||
return ErrorEval.REF_INVALID; | |||
} | |||
workbookName = parts[0]; | |||
sheetName = parts[1]; | |||
refText = text.substring(plingPos + 1); | |||
} | |||
String refStrPart1; | |||
String refStrPart2; | |||
int colonPos = refText.indexOf(':'); | |||
if (colonPos < 0) { | |||
refStrPart1 = refText.trim(); | |||
refStrPart2 = null; | |||
} else { | |||
refStrPart1 = refText.substring(0, colonPos).trim(); | |||
refStrPart2 = refText.substring(colonPos + 1).trim(); | |||
} | |||
return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style); | |||
} | |||
/** | |||
* @return array of length 2: {workbookName, sheetName,}. Second element will always be | |||
* present. First element may be null if sheetName is unqualified. | |||
* Returns <code>null</code> 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 <code>null</code> 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; | |||
} | |||
} |
@@ -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.<p/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>ISNA</b>(<b>value</b>)<p/> | |||
* | |||
* <b>value</b> The value to be tested<br/> | |||
* <br/> | |||
* Returns <tt>TRUE</tt> if the specified value is '#N/A', <tt>FALSE</tt> otherwise. | |||
*/ | |||
public static final Function ISNA = new LogicalFunction() { | |||
protected boolean evaluate(ValueEval arg) { | |||
return arg == ErrorEval.NA; | |||
} | |||
}; | |||
public static final Function ISREF = new Fixed1ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
if (arg0 instanceof RefEval || arg0 instanceof AreaEval) { | |||
return BoolEval.TRUE; | |||
} | |||
return BoolEval.FALSE; | |||
} | |||
}; | |||
} |
@@ -0,0 +1,76 @@ | |||
/* ==================================================================== | |||
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.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 Excel function LOOKUP.<p/> | |||
* | |||
* LOOKUP finds an index row in a lookup table by the first column value and returns the value from another column. | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>VLOOKUP</b>(<b>lookup_value</b>, <b>lookup_vector</b>, result_vector)<p/> | |||
* | |||
* <b>lookup_value</b> The value to be found in the lookup vector.<br/> | |||
* <b>lookup_vector</> An area reference for the lookup data. <br/> | |||
* <b>result_vector</b> Single row or single column area reference from which the result value is chosen.<br/> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Lookup extends Var2or3ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
// complex rules to choose lookupVector and resultVector from the single area ref | |||
throw new RuntimeException("Two arg version of LOOKUP not supported yet"); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
try { | |||
ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
TwoDEval aeLookupVector = LookupUtils.resolveTableArrayArg(arg1); | |||
TwoDEval aeResultVector = LookupUtils.resolveTableArrayArg(arg2); | |||
ValueVector lookupVector = createVector(aeLookupVector); | |||
ValueVector resultVector = createVector(aeResultVector); | |||
if(lookupVector.getSize() > resultVector.getSize()) { | |||
// Excel seems to handle this by accessing past the end of the result vector. | |||
throw new RuntimeException("Lookup vector and result vector of differing sizes not supported yet"); | |||
} | |||
int index = LookupUtils.lookupIndexOfValue(lookupValue, lookupVector, true); | |||
return resultVector.getItem(index); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
private static ValueVector createVector(TwoDEval ae) { | |||
ValueVector result = LookupUtils.createVector(ae); | |||
if (result != null) { | |||
return result; | |||
} | |||
// extra complexity required to emulate the way LOOKUP can handles these abnormal cases. | |||
throw new RuntimeException("non-vector lookup or result areas not supported yet"); | |||
} | |||
} |
@@ -0,0 +1,603 @@ | |||
/* ==================================================================== | |||
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.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.NumericValueEval; | |||
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; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* Common functionality used by VLOOKUP, HLOOKUP, LOOKUP and MATCH | |||
* | |||
* @author Josh Micich | |||
*/ | |||
final class LookupUtils { | |||
/** | |||
* Represents a single row or column within an <tt>AreaEval</tt>. | |||
*/ | |||
public interface ValueVector { | |||
ValueEval getItem(int index); | |||
int getSize(); | |||
} | |||
private static final class RowVector implements ValueVector { | |||
private final TwoDEval _tableArray; | |||
private final int _size; | |||
private final int _rowIndex; | |||
public RowVector(TwoDEval tableArray, int rowIndex) { | |||
_rowIndex = rowIndex; | |||
int lastRowIx = tableArray.getHeight() - 1; | |||
if(rowIndex < 0 || rowIndex > lastRowIx) { | |||
throw new IllegalArgumentException("Specified row index (" + rowIndex | |||
+ ") is outside the allowed range (0.." + lastRowIx + ")"); | |||
} | |||
_tableArray = tableArray; | |||
_size = tableArray.getWidth(); | |||
} | |||
public ValueEval getItem(int index) { | |||
if(index > _size) { | |||
throw new ArrayIndexOutOfBoundsException("Specified index (" + index | |||
+ ") is outside the allowed range (0.." + (_size-1) + ")"); | |||
} | |||
return _tableArray.getValue(_rowIndex, index); | |||
} | |||
public int getSize() { | |||
return _size; | |||
} | |||
} | |||
private static final class ColumnVector implements ValueVector { | |||
private final TwoDEval _tableArray; | |||
private final int _size; | |||
private final int _columnIndex; | |||
public ColumnVector(TwoDEval tableArray, int columnIndex) { | |||
_columnIndex = columnIndex; | |||
int lastColIx = tableArray.getWidth()-1; | |||
if(columnIndex < 0 || columnIndex > lastColIx) { | |||
throw new IllegalArgumentException("Specified column index (" + columnIndex | |||
+ ") is outside the allowed range (0.." + lastColIx + ")"); | |||
} | |||
_tableArray = tableArray; | |||
_size = _tableArray.getHeight(); | |||
} | |||
public ValueEval getItem(int index) { | |||
if(index > _size) { | |||
throw new ArrayIndexOutOfBoundsException("Specified index (" + index | |||
+ ") is outside the allowed range (0.." + (_size-1) + ")"); | |||
} | |||
return _tableArray.getValue(index, _columnIndex); | |||
} | |||
public int getSize() { | |||
return _size; | |||
} | |||
} | |||
public static ValueVector createRowVector(TwoDEval tableArray, int relativeRowIndex) { | |||
return new RowVector(tableArray, relativeRowIndex); | |||
} | |||
public static ValueVector createColumnVector(TwoDEval tableArray, int relativeColumnIndex) { | |||
return new ColumnVector(tableArray, relativeColumnIndex); | |||
} | |||
/** | |||
* @return <code>null</code> 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 <b>4</b> valued comparison results.<p/> | |||
* 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.<p/> | |||
* | |||
* A simple int might have done the same job, but there is risk in confusion with the well | |||
* known <tt>Comparable.compareTo()</tt> and <tt>Comparator.compare()</tt> 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 <tt>CompareResult</tt>: <tt>LESS_THAN</tt>, <tt>EQUAL</tt>, | |||
* <tt>GREATER_THAN</tt> or <tt>TYPE_MISMATCH</tt> | |||
*/ | |||
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 (<b>col_index_num</b> | |||
* or <b>row_index_num</b> respectively).<br> | |||
* Sample behaviour: | |||
* <table border="0" cellpadding="1" cellspacing="2" summary="Sample behaviour"> | |||
* <tr><th>Input Return</th><th>Value </th><th>Thrown Error</th></tr> | |||
* <tr><td>5</td><td>4</td><td> </td></tr> | |||
* <tr><td>2.9</td><td>2</td><td> </td></tr> | |||
* <tr><td>"5"</td><td>4</td><td> </td></tr> | |||
* <tr><td>"2.18e1"</td><td>21</td><td> </td></tr> | |||
* <tr><td>"-$2"</td><td>-3</td><td>*</td></tr> | |||
* <tr><td>FALSE</td><td>-1</td><td>*</td></tr> | |||
* <tr><td>TRUE</td><td>0</td><td> </td></tr> | |||
* <tr><td>"TRUE"</td><td> </td><td>#REF!</td></tr> | |||
* <tr><td>"abc"</td><td> </td><td>#REF!</td></tr> | |||
* <tr><td>""</td><td> </td><td>#REF!</td></tr> | |||
* <tr><td><blank></td><td> </td><td>#VALUE!</td></tr> | |||
* </table><br/> | |||
* | |||
* Note - out of range errors (result index too high) are handled by the caller. | |||
* @return column or row index as a zero-based value, never negative. | |||
* @throws EvaluationException when the specified arg cannot be coerced to a non-negative integer | |||
*/ | |||
public static int resolveRowOrColIndexArg(ValueEval rowColIndexArg, int srcCellRow, int srcCellCol) throws EvaluationException { | |||
if(rowColIndexArg == null) { | |||
throw new IllegalArgumentException("argument must not be null"); | |||
} | |||
ValueEval veRowColIndexArg; | |||
try { | |||
veRowColIndexArg = OperandResolver.getSingleValue(rowColIndexArg, srcCellRow, (short)srcCellCol); | |||
} catch (EvaluationException e) { | |||
// All errors get translated to #REF! | |||
throw EvaluationException.invalidRef(); | |||
} | |||
int oneBasedIndex; | |||
if(veRowColIndexArg instanceof StringEval) { | |||
StringEval se = (StringEval) veRowColIndexArg; | |||
String strVal = se.getStringValue(); | |||
Double dVal = OperandResolver.parseDouble(strVal); | |||
if(dVal == null) { | |||
// String does not resolve to a number. Raise #REF! error. | |||
throw EvaluationException.invalidRef(); | |||
// This includes text booleans "TRUE" and "FALSE". They are not valid. | |||
} | |||
// else - numeric value parses OK | |||
} | |||
// actual BoolEval values get interpreted as FALSE->0 and TRUE->1 | |||
oneBasedIndex = OperandResolver.coerceValueToInt(veRowColIndexArg); | |||
if (oneBasedIndex < 1) { | |||
// note this is asymmetric with the errors when the index is too large (#REF!) | |||
throw EvaluationException.invalidValue(); | |||
} | |||
return oneBasedIndex - 1; // convert to zero based | |||
} | |||
/** | |||
* The second argument (table_array) should be an area ref, but can actually be a cell ref, in | |||
* which case it is interpreted as a 1x1 area ref. Other scalar values cause #VALUE! error. | |||
*/ | |||
public static TwoDEval resolveTableArrayArg(ValueEval eval) throws EvaluationException { | |||
if (eval instanceof TwoDEval) { | |||
return (TwoDEval) eval; | |||
} | |||
if(eval instanceof RefEval) { | |||
RefEval refEval = (RefEval) eval; | |||
// Make this cell ref look like a 1x1 area ref. | |||
// It doesn't matter if eval is a 2D or 3D ref, because that detail is never asked of AreaEval. | |||
return refEval.offset(0, 0, 0, 0); | |||
} | |||
throw EvaluationException.invalidValue(); | |||
} | |||
/** | |||
* Resolves the last (optional) parameter (<b>range_lookup</b>) to the VLOOKUP and HLOOKUP functions. | |||
* @param rangeLookupArg must not be <code>null</code> | |||
*/ | |||
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 lookup<vector[newMid]), | |||
// this execution path only moves highIx it down as far as newMid, not midIx, | |||
// which would be more efficient. | |||
bsi.narrowSearch(newMid, cr.isLessThan()); | |||
return -1; | |||
} | |||
} | |||
/** | |||
* Once the binary search has found a single match, (V/H)LOOKUP steps one by one over subsequent | |||
* values to choose the last matching item. | |||
*/ | |||
private static int findLastIndexInRunOfEqualValues(LookupValueComparer lookupComparer, ValueVector vector, | |||
int firstFoundIndex, int maxIx) { | |||
for(int i=firstFoundIndex+1; i<maxIx; i++) { | |||
if(!lookupComparer.compareTo(vector.getItem(i)).isEqual()) { | |||
return i-1; | |||
} | |||
} | |||
return maxIx - 1; | |||
} | |||
public static LookupValueComparer createLookupComparer(ValueEval lookupValue) { | |||
if (lookupValue == BlankEval.instance) { | |||
// blank eval translates to zero | |||
// Note - a blank eval in the lookup column/row never matches anything | |||
// empty string in the lookup column/row can only be matched by explicit empty string | |||
return new NumberLookupComparer(NumberEval.ZERO); | |||
} | |||
if (lookupValue instanceof StringEval) { | |||
return new StringLookupComparer((StringEval) lookupValue); | |||
} | |||
if (lookupValue instanceof NumberEval) { | |||
return new NumberLookupComparer((NumberEval) lookupValue); | |||
} | |||
if (lookupValue instanceof BoolEval) { | |||
return new BooleanLookupComparer((BoolEval) lookupValue); | |||
} | |||
throw new IllegalArgumentException("Bad lookup value type (" + lookupValue.getClass().getName() + ")"); | |||
} | |||
} |
@@ -0,0 +1,251 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.NumericValueEval; | |||
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; | |||
import org.apache.poi.ss.formula.functions.LookupUtils.CompareResult; | |||
import org.apache.poi.ss.formula.functions.LookupUtils.LookupValueComparer; | |||
import org.apache.poi.ss.formula.functions.LookupUtils.ValueVector; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* Implementation for the MATCH() Excel function.<p/> | |||
* | |||
* <b>Syntax:</b><br/> | |||
* <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/> | |||
* | |||
* Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified | |||
* <b>lookup_value</b> is found.<p/> | |||
* | |||
* Specific matching behaviour can be modified with the optional <b>match_type</b> parameter. | |||
* | |||
* <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description"> | |||
* <tr><th>Value</th><th>Matching Behaviour</th></tr> | |||
* <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value. | |||
* The lookup_array must be in ascending <i>order</i>*.</td></tr> | |||
* <tr><td>0</td><td>find the first value that is exactly equal to lookup_value. | |||
* The lookup_array can be in any order.</td></tr> | |||
* <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value. | |||
* The lookup_array must be in descending <i>order</i>*.</td></tr> | |||
* </table> | |||
* | |||
* * Note regarding <i>order</i> - For the <b>match_type</b> cases that require the lookup_array to | |||
* be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed | |||
* behaviour in Excel is to return the lowest index value for which every item after that index | |||
* breaks the match rule.<br> | |||
* The (ascending) sort order expected by MATCH() is:<br/> | |||
* numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/> | |||
* MATCH() ignores all elements in the lookup_array with a different type to the lookup_value. | |||
* Type conversion of the lookup_array elements is never performed. | |||
* | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Match extends Var2or3ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
// default match_type is 1.0 | |||
return eval(srcRowIndex, srcColumnIndex, arg0, arg1, 1.0); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
double match_type; | |||
try { | |||
match_type = evaluateMatchTypeArg(arg2, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
// Excel/MATCH() seems to have slightly abnormal handling of errors with | |||
// the last parameter. Errors do not propagate up. Every error gets | |||
// translated into #REF! | |||
return ErrorEval.REF_INVALID; | |||
} | |||
return eval(srcRowIndex, srcColumnIndex, arg0, arg1, match_type); | |||
} | |||
private static ValueEval eval(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
double match_type) { | |||
boolean matchExact = match_type == 0; | |||
// Note - Excel does not strictly require -1 and +1 | |||
boolean findLargestLessThanOrEqual = match_type > 0; | |||
try { | |||
ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
ValueVector lookupRange = evaluateLookupRange(arg1); | |||
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual); | |||
return new NumberEval(index + 1); // +1 to convert to 1-based | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
private static final class SingleValueVector implements ValueVector { | |||
private final ValueEval _value; | |||
public SingleValueVector(ValueEval value) { | |||
_value = value; | |||
} | |||
public ValueEval getItem(int index) { | |||
if (index != 0) { | |||
throw new RuntimeException("Invalid index (" | |||
+ index + ") only zero is allowed"); | |||
} | |||
return _value; | |||
} | |||
public int getSize() { | |||
return 1; | |||
} | |||
} | |||
private static ValueVector evaluateLookupRange(ValueEval eval) throws EvaluationException { | |||
if (eval instanceof RefEval) { | |||
RefEval re = (RefEval) eval; | |||
return new SingleValueVector(re.getInnerValueEval()); | |||
} | |||
if (eval instanceof TwoDEval) { | |||
ValueVector result = LookupUtils.createVector((TwoDEval)eval); | |||
if (result == null) { | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
return result; | |||
} | |||
// Error handling for lookup_range arg is also unusual | |||
if(eval instanceof NumericValueEval) { | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
if (eval instanceof StringEval) { | |||
StringEval se = (StringEval) eval; | |||
Double d = OperandResolver.parseDouble(se.getStringValue()); | |||
if(d == null) { | |||
// plain string | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
// else looks like a number | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")"); | |||
} | |||
private static double evaluateMatchTypeArg(ValueEval arg, int srcCellRow, int srcCellCol) | |||
throws EvaluationException { | |||
ValueEval match_type = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | |||
if(match_type instanceof ErrorEval) { | |||
throw new EvaluationException((ErrorEval)match_type); | |||
} | |||
if(match_type instanceof NumericValueEval) { | |||
NumericValueEval ne = (NumericValueEval) match_type; | |||
return ne.getNumberValue(); | |||
} | |||
if (match_type instanceof StringEval) { | |||
StringEval se = (StringEval) match_type; | |||
Double d = OperandResolver.parseDouble(se.getStringValue()); | |||
if(d == null) { | |||
// plain string | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
// if the string parses as a number, it is OK | |||
return d.doubleValue(); | |||
} | |||
throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")"); | |||
} | |||
/** | |||
* @return zero based index | |||
*/ | |||
private static int findIndexOfValue(ValueEval lookupValue, ValueVector lookupRange, | |||
boolean matchExact, boolean findLargestLessThanOrEqual) throws EvaluationException { | |||
LookupValueComparer lookupComparer = createLookupComparer(lookupValue, matchExact); | |||
int size = lookupRange.getSize(); | |||
if(matchExact) { | |||
for (int i = 0; i < size; i++) { | |||
if(lookupComparer.compareTo(lookupRange.getItem(i)).isEqual()) { | |||
return i; | |||
} | |||
} | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
if(findLargestLessThanOrEqual) { | |||
// Note - backward iteration | |||
for (int i = size - 1; i>=0; i--) { | |||
CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i)); | |||
if(cmp.isTypeMismatch()) { | |||
continue; | |||
} | |||
if(!cmp.isLessThan()) { | |||
return i; | |||
} | |||
} | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
// else - find smallest greater than or equal to | |||
// TODO - is binary search used for (match_type==+1) ? | |||
for (int i = 0; i<size; i++) { | |||
CompareResult cmp = lookupComparer.compareTo(lookupRange.getItem(i)); | |||
if(cmp.isEqual()) { | |||
return i; | |||
} | |||
if(cmp.isGreaterThan()) { | |||
if(i<1) { | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
return i-1; | |||
} | |||
} | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
private static LookupValueComparer createLookupComparer(ValueEval lookupValue, boolean matchExact) { | |||
if (matchExact && lookupValue instanceof StringEval) { | |||
String stringValue = ((StringEval) lookupValue).getStringValue(); | |||
if(isLookupValueWild(stringValue)) { | |||
throw new RuntimeException("Wildcard lookup values '" + stringValue + "' not supported yet"); | |||
} | |||
} | |||
return LookupUtils.createLookupComparer(lookupValue); | |||
} | |||
private static boolean isLookupValueWild(String stringValue) { | |||
if(stringValue.indexOf('?') >=0 || stringValue.indexOf('*') >=0) { | |||
return true; | |||
} | |||
return false; | |||
} | |||
} |
@@ -0,0 +1,444 @@ | |||
/* ==================================================================== | |||
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; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* This class is an extension to the standard math library | |||
* provided by java.lang.Math class. It follows the Math class | |||
* in that it has a private constructor and all static methods. | |||
*/ | |||
final class MathX { | |||
private MathX() { | |||
// no instances of this class | |||
} | |||
/** | |||
* 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. | |||
* <p>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. | |||
* <p>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. | |||
* <p>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. | |||
* <p>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. | |||
* <p>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. | |||
* <p>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 | |||
* <br/> | |||
* If d > 0, returns short 1 | |||
* <br/> | |||
* If d == 0, returns short 0 | |||
* <p> 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<iSize; i++) { | |||
sum += values[i]; | |||
} | |||
ave = sum / values.length; | |||
return ave; | |||
} | |||
/** | |||
* sum of all values | |||
* @param values | |||
*/ | |||
public static double sum(double[] values) { | |||
double sum = 0; | |||
for (int i=0, iSize=values.length; i<iSize; i++) { | |||
sum += values[i]; | |||
} | |||
return sum; | |||
} | |||
/** | |||
* sum of squares of all values | |||
* @param values | |||
*/ | |||
public static double sumsq(double[] values) { | |||
double sumsq = 0; | |||
for (int i=0, iSize=values.length; i<iSize; i++) { | |||
sumsq += values[i]*values[i]; | |||
} | |||
return sumsq; | |||
} | |||
/** | |||
* product of all values | |||
* @param values | |||
*/ | |||
public static double product(double[] values) { | |||
double product = 0; | |||
if (values!=null && values.length > 0) { | |||
product = 1; | |||
for (int i=0, iSize=values.length; i<iSize; i++) { | |||
product *= values[i]; | |||
} | |||
} | |||
return product; | |||
} | |||
/** | |||
* min of all values. If supplied array is zero length, | |||
* Double.POSITIVE_INFINITY is returned. | |||
* @param values | |||
*/ | |||
public static double min(double[] values) { | |||
double min = Double.POSITIVE_INFINITY; | |||
for (int i=0, iSize=values.length; i<iSize; i++) { | |||
min = Math.min(min, values[i]); | |||
} | |||
return min; | |||
} | |||
/** | |||
* min of all values. If supplied array is zero length, | |||
* Double.NEGATIVE_INFINITY is returned. | |||
* @param values | |||
*/ | |||
public static double max(double[] values) { | |||
double max = Double.NEGATIVE_INFINITY; | |||
for (int i=0, iSize=values.length; i<iSize; i++) { | |||
max = Math.max(max, values[i]); | |||
} | |||
return max; | |||
} | |||
/** | |||
* Note: this function is different from java.lang.Math.floor(..). | |||
* <p> | |||
* When n and s are "valid" arguments, the returned value is: Math.floor(n/s) * s; | |||
* <br/> | |||
* n and s are invalid if any of following conditions are true: | |||
* <ul> | |||
* <li>s is zero</li> | |||
* <li>n is negative and s is positive</li> | |||
* <li>n is positive and s is negative</li> | |||
* </ul> | |||
* 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(..). | |||
* <p> | |||
* When n and s are "valid" arguments, the returned value is: Math.ceiling(n/s) * s; | |||
* <br/> | |||
* n and s are invalid if any of following conditions are true: | |||
* <ul> | |||
* <li>s is zero</li> | |||
* <li>n is negative and s is positive</li> | |||
* <li>n is positive and s is negative</li> | |||
* </ul> | |||
* 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; | |||
} | |||
/** | |||
* <br/> for all n >= 1; factorial n = n * (n-1) * (n-2) * ... * 1 | |||
* <br/> else if n == 0; factorial n = 1 | |||
* <br/> else if n < 0; factorial n = Double.NaN | |||
* <br/> 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. | |||
* <br/> The result has the sign of the divisor. | |||
* <br/> Examples: | |||
* <ul> | |||
* <li>mod(3.4, 2) = 1.4</li> | |||
* <li>mod(-3.4, 2) = 0.6</li> | |||
* <li>mod(-3.4, -2) = -1.4</li> | |||
* <li>mod(3.4, -2) = -0.6</li> | |||
* </ul> | |||
* 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 || n<k) { | |||
d= Double.NaN; | |||
} | |||
else { | |||
int minnk = Math.min(n-k, k); | |||
int maxnk = Math.max(n-k, k); | |||
for (int i=maxnk; i<n; i++) { | |||
d *= i+1; | |||
} | |||
d /= factorial(minnk); | |||
} | |||
return d; | |||
} | |||
} |
@@ -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; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* | |||
*/ | |||
public abstract class MinaMaxa extends MultiOperandNumericFunction { | |||
protected MinaMaxa() { | |||
super(true, true); | |||
} | |||
public static final Function MAXA = new MinaMaxa() { | |||
protected double evaluate(double[] values) { | |||
return values.length > 0 ? MathX.max(values) : 0; | |||
} | |||
}; | |||
public static final Function MINA = new MinaMaxa() { | |||
protected double evaluate(double[] values) { | |||
return values.length > 0 ? MathX.min(values) : 0; | |||
} | |||
}; | |||
} |
@@ -0,0 +1,133 @@ | |||
/* ==================================================================== | |||
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.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
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.RefEval; | |||
import org.apache.poi.ss.formula.eval.StringEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* | |||
*/ | |||
public final class Mode implements Function { | |||
/** | |||
* if v is zero length or contains no duplicates, return value is | |||
* Double.NaN. Else returns the value that occurs most times and if there is | |||
* a tie, returns the first such value. | |||
* | |||
* @param v | |||
*/ | |||
public static double evaluate(double[] v) throws EvaluationException { | |||
if (v.length < 2) { | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
// very naive impl, may need to be optimized | |||
int[] counts = new int[v.length]; | |||
Arrays.fill(counts, 1); | |||
for (int i = 0, iSize = v.length; i < iSize; i++) { | |||
for (int j = i + 1, jSize = v.length; j < jSize; j++) { | |||
if (v[i] == v[j]) | |||
counts[i]++; | |||
} | |||
} | |||
double maxv = 0; | |||
int maxc = 0; | |||
for (int i = 0, iSize = counts.length; i < iSize; i++) { | |||
if (counts[i] > maxc) { | |||
maxv = v[i]; | |||
maxc = counts[i]; | |||
} | |||
} | |||
if (maxc > 1) { | |||
return maxv; | |||
} | |||
throw new EvaluationException(ErrorEval.NA); | |||
} | |||
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
double result; | |||
try { | |||
List<Double> temp = new ArrayList<Double>(); | |||
for (int i = 0; i < args.length; i++) { | |||
collectValues(args[i], temp); | |||
} | |||
double[] values = new double[temp.size()]; | |||
for (int i = 0; i < values.length; i++) { | |||
values[i] = temp.get(i).doubleValue(); | |||
} | |||
result = evaluate(values); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
private static void collectValues(ValueEval arg, List<Double> temp) throws EvaluationException { | |||
if (arg instanceof TwoDEval) { | |||
TwoDEval ae = (TwoDEval) arg; | |||
int width = ae.getWidth(); | |||
int height = ae.getHeight(); | |||
for (int rrIx = 0; rrIx < height; rrIx++) { | |||
for (int rcIx = 0; rcIx < width; rcIx++) { | |||
ValueEval ve1 = ae.getValue(rrIx, rcIx); | |||
collectValue(ve1, temp, false); | |||
} | |||
} | |||
return; | |||
} | |||
if (arg instanceof RefEval) { | |||
RefEval re = (RefEval) arg; | |||
collectValue(re.getInnerValueEval(), temp, true); | |||
return; | |||
} | |||
collectValue(arg, temp, true); | |||
} | |||
private static void collectValue(ValueEval arg, List<Double> temp, boolean mustBeNumber) | |||
throws EvaluationException { | |||
if (arg instanceof ErrorEval) { | |||
throw new EvaluationException((ErrorEval) arg); | |||
} | |||
if (arg == BlankEval.instance || arg instanceof BoolEval || arg instanceof StringEval) { | |||
if (mustBeNumber) { | |||
throw EvaluationException.invalidValue(); | |||
} | |||
return; | |||
} | |||
if (arg instanceof NumberEval) { | |||
temp.add(new Double(((NumberEval) arg).getNumberValue())); | |||
return; | |||
} | |||
throw new RuntimeException("Unexpected value type (" + arg.getClass().getName() + ")"); | |||
} | |||
} |
@@ -0,0 +1,197 @@ | |||
/* ==================================================================== | |||
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.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; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* This is the super class for all excel function evaluator | |||
* classes that take variable number of operands, and | |||
* where the order of operands does not matter | |||
*/ | |||
public abstract class MultiOperandNumericFunction implements Function { | |||
private final boolean _isReferenceBoolCounted; | |||
private final boolean _isBlankCounted; | |||
protected MultiOperandNumericFunction(boolean isReferenceBoolCounted, boolean isBlankCounted) { | |||
_isReferenceBoolCounted = isReferenceBoolCounted; | |||
_isBlankCounted = isBlankCounted; | |||
} | |||
static final double[] EMPTY_DOUBLE_ARRAY = { }; | |||
private static class DoubleList { | |||
private double[] _array; | |||
private int _count; | |||
public DoubleList() { | |||
_array = new double[8]; | |||
_count = 0; | |||
} | |||
public double[] toArray() { | |||
if(_count < 1) { | |||
return EMPTY_DOUBLE_ARRAY; | |||
} | |||
double[] result = new double[_count]; | |||
System.arraycopy(_array, 0, result, 0, _count); | |||
return result; | |||
} | |||
private void ensureCapacity(int reqSize) { | |||
if(reqSize > _array.length) { | |||
int newSize = reqSize * 3 / 2; // grow with 50% extra | |||
double[] newArr = new double[newSize]; | |||
System.arraycopy(_array, 0, newArr, 0, _count); | |||
_array = newArr; | |||
} | |||
} | |||
public void add(double value) { | |||
ensureCapacity(_count + 1); | |||
_array[_count] = value; | |||
_count++; | |||
} | |||
} | |||
private static final int DEFAULT_MAX_NUM_OPERANDS = 30; | |||
public final ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
double d; | |||
try { | |||
double[] values = getNumberArray(args); | |||
d = evaluate(values); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (Double.isNaN(d) || Double.isInfinite(d)) | |||
return ErrorEval.NUM_ERROR; | |||
return new NumberEval(d); | |||
} | |||
protected abstract double evaluate(double[] values) throws EvaluationException; | |||
/** | |||
* Maximum number of operands accepted by this function. | |||
* Subclasses may override to change default value. | |||
*/ | |||
protected int getMaxNumOperands() { | |||
return DEFAULT_MAX_NUM_OPERANDS; | |||
} | |||
/** | |||
* Returns a double array that contains values for the numeric cells | |||
* from among the list of operands. Blanks and Blank equivalent cells | |||
* are ignored. Error operands or cells containing operands of type | |||
* that are considered invalid and would result in #VALUE! error in | |||
* excel cause this function to return <code>null</code>. | |||
* | |||
* @return never <code>null</code> | |||
*/ | |||
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<iSize; i++) { | |||
collectValues(operands[i], retval); | |||
} | |||
return retval.toArray(); | |||
} | |||
/** | |||
* Collects values from a single argument | |||
*/ | |||
private void collectValues(ValueEval operand, DoubleList temp) throws EvaluationException { | |||
if (operand instanceof TwoDEval) { | |||
TwoDEval ae = (TwoDEval) operand; | |||
int width = ae.getWidth(); | |||
int height = ae.getHeight(); | |||
for (int rrIx=0; rrIx<height; rrIx++) { | |||
for (int rcIx=0; rcIx<width; rcIx++) { | |||
ValueEval ve = ae.getValue(rrIx, rcIx); | |||
collectValue(ve, true, temp); | |||
} | |||
} | |||
return; | |||
} | |||
if (operand instanceof RefEval) { | |||
RefEval re = (RefEval) operand; | |||
collectValue(re.getInnerValueEval(), true, temp); | |||
return; | |||
} | |||
collectValue(operand, false, temp); | |||
} | |||
private void collectValue(ValueEval ve, boolean isViaReference, DoubleList temp) throws EvaluationException { | |||
if (ve == null) { | |||
throw new IllegalArgumentException("ve must not be null"); | |||
} | |||
if (ve instanceof NumberEval) { | |||
NumberEval ne = (NumberEval) ve; | |||
temp.add(ne.getNumberValue()); | |||
return; | |||
} | |||
if (ve instanceof ErrorEval) { | |||
throw new EvaluationException((ErrorEval) ve); | |||
} | |||
if (ve instanceof StringEval) { | |||
if (isViaReference) { | |||
// ignore all ref strings | |||
return; | |||
} | |||
String s = ((StringEval) ve).getStringValue(); | |||
Double d = OperandResolver.parseDouble(s); | |||
if(d == null) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
temp.add(d.doubleValue()); | |||
return; | |||
} | |||
if (ve instanceof BoolEval) { | |||
if (!isViaReference || _isReferenceBoolCounted) { | |||
BoolEval boolEval = (BoolEval) ve; | |||
temp.add(boolEval.getNumberValue()); | |||
} | |||
return; | |||
} | |||
if (ve == BlankEval.instance) { | |||
if (_isBlankCounted) { | |||
temp.add(0.0); | |||
} | |||
return; | |||
} | |||
throw new RuntimeException("Invalid ValueEval type passed for conversion: (" | |||
+ ve.getClass() + ")"); | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
/* ==================================================================== | |||
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; | |||
/** | |||
* Implementation of Excel function NA() | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Na extends Fixed0ArgFunction { | |||
public ValueEval evaluate(int srcCellRow, int srcCellCol) { | |||
return ErrorEval.NA; | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/* ==================================================================== | |||
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; | |||
import org.apache.poi.ss.formula.eval.NotImplementedException; | |||
/** | |||
* | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* This is the default implementation of a Function class. | |||
* The default behaviour is to raise a POI internal error | |||
* ({@link NotImplementedException}). This error should alert | |||
* the user that the formula contained a function that is not | |||
* yet implemented. | |||
*/ | |||
public final class NotImplementedFunction implements Function { | |||
private final String _functionName; | |||
protected NotImplementedFunction() { | |||
_functionName = getClass().getName(); | |||
} | |||
public NotImplementedFunction(String name) { | |||
_functionName = name; | |||
} | |||
public ValueEval evaluate(ValueEval[] operands, int srcRow, int srcCol) { | |||
throw new NotImplementedException(_functionName); | |||
} | |||
public String getFunctionName() { | |||
return _functionName; | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
/* ==================================================================== | |||
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.Date; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.usermodel.DateUtil; | |||
/** | |||
* Implementation of Excel NOW() Function | |||
* | |||
* @author Frank Taffelt | |||
*/ | |||
public final class Now extends Fixed0ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
Date now = new Date(System.currentTimeMillis()); | |||
return new NumberEval(DateUtil.getExcelDate(now)); | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Calculates the net present value of an investment by using a discount rate | |||
* and a series of future payments (negative values) and income (positive | |||
* values). Minimum 2 arguments, first arg is the rate of discount over the | |||
* length of one period others up to 254 arguments representing the payments and | |||
* income. | |||
* | |||
* @author SPetrakovsky | |||
*/ | |||
public final class Npv implements Function2Arg, Function3Arg, Function4Arg { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
double result; | |||
try { | |||
double rate = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
result = evaluate(rate, d1); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
double result; | |||
try { | |||
double rate = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
double d2 = NumericFunction.singleOperandEvaluate(arg2, srcRowIndex, srcColumnIndex); | |||
result = evaluate(rate, d1, d2); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2, ValueEval arg3) { | |||
double result; | |||
try { | |||
double rate = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
double d2 = NumericFunction.singleOperandEvaluate(arg2, srcRowIndex, srcColumnIndex); | |||
double d3 = NumericFunction.singleOperandEvaluate(arg3, srcRowIndex, srcColumnIndex); | |||
result = evaluate(rate, d1, d2, d3); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
int nArgs = args.length; | |||
if (nArgs<2) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
int np = nArgs-1; | |||
double[] ds = new double[np]; | |||
double result; | |||
try { | |||
double rate = NumericFunction.singleOperandEvaluate(args[0], srcRowIndex, srcColumnIndex); | |||
for (int i = 0; i < ds.length; i++) { | |||
ds[i] = NumericFunction.singleOperandEvaluate(args[i+1], srcRowIndex, srcColumnIndex); | |||
} | |||
result = evaluate(rate, ds); | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
private static double evaluate(double rate, double...ds) { | |||
double sum = 0; | |||
for (int i = 0; i < ds.length; i++) { | |||
sum += ds[i] / Math.pow(rate + 1, i); | |||
} | |||
return sum; | |||
} | |||
} |
@@ -0,0 +1,495 @@ | |||
/* ==================================================================== | |||
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.*; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* @author Josh Micich | |||
* @author Stephen Wolke (smwolke at geistig.com) | |||
*/ | |||
public abstract class NumericFunction implements Function { | |||
static final double ZERO = 0.0; | |||
static final double TEN = 10.0; | |||
static final double LOG_10_TO_BASE_e = Math.log(TEN); | |||
protected static final double singleOperandEvaluate(ValueEval arg, int srcRowIndex, int srcColumnIndex) throws EvaluationException { | |||
if (arg == null) { | |||
throw new IllegalArgumentException("arg must not be null"); | |||
} | |||
ValueEval ve = OperandResolver.getSingleValue(arg, srcRowIndex, srcColumnIndex); | |||
double result = OperandResolver.coerceValueToDouble(ve); | |||
checkValue(result); | |||
return result; | |||
} | |||
/** | |||
* @throws EvaluationException (#NUM!) if <tt>result</tt> is <tt>NaN</> or <tt>Infinity</tt> | |||
*/ | |||
static final void checkValue(double result) throws EvaluationException { | |||
if (Double.isNaN(result) || Double.isInfinite(result)) { | |||
throw new EvaluationException(ErrorEval.NUM_ERROR); | |||
} | |||
} | |||
public final ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
double result; | |||
try { | |||
result = eval(args, srcCellRow, srcCellCol); | |||
checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
protected abstract double eval(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException; | |||
/* -------------------------------------------------------------------------- */ | |||
// intermediate sub-classes (one-arg, two-arg and multi-arg) | |||
public static abstract class OneArg extends Fixed1ArgFunction { | |||
protected OneArg() { | |||
// no fields to initialise | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
double result; | |||
try { | |||
double d = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
result = evaluate(d); | |||
checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
protected final double eval(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException { | |||
if (args.length != 1) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
double d = singleOperandEvaluate(args[0], srcCellRow, srcCellCol); | |||
return evaluate(d); | |||
} | |||
protected abstract double evaluate(double d) throws EvaluationException; | |||
} | |||
public static abstract class TwoArg extends Fixed2ArgFunction { | |||
protected TwoArg() { | |||
// no fields to initialise | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
double result; | |||
try { | |||
double d0 = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
result = evaluate(d0, d1); | |||
checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
protected abstract double evaluate(double d0, double d1) throws EvaluationException; | |||
} | |||
/* -------------------------------------------------------------------------- */ | |||
public static final Function ABS = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.abs(d); | |||
} | |||
}; | |||
public static final Function ACOS = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.acos(d); | |||
} | |||
}; | |||
public static final Function ACOSH = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.acosh(d); | |||
} | |||
}; | |||
public static final Function ASIN = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.asin(d); | |||
} | |||
}; | |||
public static final Function ASINH = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.asinh(d); | |||
} | |||
}; | |||
public static final Function ATAN = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.atan(d); | |||
} | |||
}; | |||
public static final Function ATANH = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.atanh(d); | |||
} | |||
}; | |||
public static final Function COS = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.cos(d); | |||
} | |||
}; | |||
public static final Function COSH = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.cosh(d); | |||
} | |||
}; | |||
public static final Function DEGREES = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.toDegrees(d); | |||
} | |||
}; | |||
static final NumberEval DOLLAR_ARG2_DEFAULT = new NumberEval(2.0); | |||
public static final Function DOLLAR = new Var1or2ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, DOLLAR_ARG2_DEFAULT); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, | |||
ValueEval arg1) { | |||
double val; | |||
double d1; | |||
try { | |||
val = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
d1 = singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
// second arg converts to int by truncating toward zero | |||
int nPlaces = (int)d1; | |||
if (nPlaces > 127) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
// TODO - DOLLAR() function impl is NQR | |||
// result should be StringEval, with leading '$' and thousands separators | |||
// current junits are asserting incorrect behaviour | |||
return new NumberEval(val); | |||
} | |||
}; | |||
public static final Function EXP = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.pow(Math.E, d); | |||
} | |||
}; | |||
public static final Function FACT = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.factorial((int)d); | |||
} | |||
}; | |||
public static final Function INT = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.round(d-0.5); | |||
} | |||
}; | |||
public static final Function LN = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.log(d); | |||
} | |||
}; | |||
public static final Function LOG10 = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.log(d) / LOG_10_TO_BASE_e; | |||
} | |||
}; | |||
public static final Function RADIANS = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.toRadians(d); | |||
} | |||
}; | |||
public static final Function SIGN = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.sign(d); | |||
} | |||
}; | |||
public static final Function SIN = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.sin(d); | |||
} | |||
}; | |||
public static final Function SINH = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.sinh(d); | |||
} | |||
}; | |||
public static final Function SQRT = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.sqrt(d); | |||
} | |||
}; | |||
public static final Function TAN = new OneArg() { | |||
protected double evaluate(double d) { | |||
return Math.tan(d); | |||
} | |||
}; | |||
public static final Function TANH = new OneArg() { | |||
protected double evaluate(double d) { | |||
return MathX.tanh(d); | |||
} | |||
}; | |||
/* -------------------------------------------------------------------------- */ | |||
public static final Function ATAN2 = new TwoArg() { | |||
protected double evaluate(double d0, double d1) throws EvaluationException { | |||
if (d0 == ZERO && d1 == ZERO) { | |||
throw new EvaluationException(ErrorEval.DIV_ZERO); | |||
} | |||
return Math.atan2(d1, d0); | |||
} | |||
}; | |||
public static final Function CEILING = new TwoArg() { | |||
protected double evaluate(double d0, double d1) { | |||
return MathX.ceiling(d0, d1); | |||
} | |||
}; | |||
public static final Function COMBIN = new TwoArg() { | |||
protected double evaluate(double d0, double d1) throws EvaluationException { | |||
if (d0 > Integer.MAX_VALUE || d1 > Integer.MAX_VALUE) { | |||
throw new EvaluationException(ErrorEval.NUM_ERROR); | |||
} | |||
return MathX.nChooseK((int) d0, (int) d1); | |||
} | |||
}; | |||
public static final Function FLOOR = new TwoArg() { | |||
protected double evaluate(double d0, double d1) throws EvaluationException { | |||
if (d1 == ZERO) { | |||
if (d0 == ZERO) { | |||
return ZERO; | |||
} | |||
throw new EvaluationException(ErrorEval.DIV_ZERO); | |||
} | |||
return MathX.floor(d0, d1); | |||
} | |||
}; | |||
public static final Function MOD = new TwoArg() { | |||
protected double evaluate(double d0, double d1) throws EvaluationException { | |||
if (d1 == ZERO) { | |||
throw new EvaluationException(ErrorEval.DIV_ZERO); | |||
} | |||
return MathX.mod(d0, d1); | |||
} | |||
}; | |||
public static final Function POWER = new TwoArg() { | |||
protected double evaluate(double d0, double d1) { | |||
return Math.pow(d0, d1); | |||
} | |||
}; | |||
public static final Function ROUND = new TwoArg() { | |||
protected double evaluate(double d0, double d1) { | |||
return MathX.round(d0, (int)d1); | |||
} | |||
}; | |||
public static final Function ROUNDDOWN = new TwoArg() { | |||
protected double evaluate(double d0, double d1) { | |||
return MathX.roundDown(d0, (int)d1); | |||
} | |||
}; | |||
public static final Function ROUNDUP = new TwoArg() { | |||
protected double evaluate(double d0, double d1) { | |||
return MathX.roundUp(d0, (int)d1); | |||
} | |||
}; | |||
static final NumberEval TRUNC_ARG2_DEFAULT = new NumberEval(0); | |||
public static final Function TRUNC = new Var1or2ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, TRUNC_ARG2_DEFAULT); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
double result; | |||
try { | |||
double d0 = singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
double multi = Math.pow(10d,d1); | |||
result = Math.floor(d0 * multi) / multi; | |||
checkValue(result); | |||
}catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
}; | |||
/* -------------------------------------------------------------------------- */ | |||
private static final class Log extends Var1or2ArgFunction { | |||
public Log() { | |||
// no instance fields | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
double result; | |||
try { | |||
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
result = Math.log(d0) / LOG_10_TO_BASE_e; | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, | |||
ValueEval arg1) { | |||
double result; | |||
try { | |||
double d0 = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
double d1 = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
double logE = Math.log(d0); | |||
double base = d1; | |||
if (base == Math.E) { | |||
result = logE; | |||
} else { | |||
result = logE / Math.log(base); | |||
} | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
} | |||
public static final Function LOG = new Log(); | |||
static final NumberEval PI_EVAL = new NumberEval(Math.PI); | |||
public static final Function PI = new Fixed0ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
return PI_EVAL; | |||
} | |||
}; | |||
public static final Function RAND = new Fixed0ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
return new NumberEval(Math.random()); | |||
} | |||
}; | |||
public static final Function POISSON = new Fixed3ArgFunction() { | |||
private final static double DEFAULT_RETURN_RESULT =1; | |||
/** | |||
* This checks is x = 0 and the mean = 0. | |||
* Excel currently returns the value 1 where as the | |||
* maths common implementation will error. | |||
* @param x The number. | |||
* @param mean The mean. | |||
* @return If a default value should be returned. | |||
*/ | |||
private boolean isDefaultResult(double x, double mean) { | |||
if ( x == 0 && mean == 0 ) { | |||
return true; | |||
} | |||
return false; | |||
} | |||
private boolean checkArgument(double aDouble) throws EvaluationException { | |||
NumericFunction.checkValue(aDouble); | |||
// make sure that the number is positive | |||
if (aDouble < 0) { | |||
throw new EvaluationException(ErrorEval.NUM_ERROR); | |||
} | |||
return true; | |||
} | |||
private double probability(int k, double lambda) { | |||
return Math.pow(lambda, k) * Math.exp(-lambda) / factorial(k); | |||
} | |||
private double cumulativeProbability(int x, double lambda) { | |||
double result = 0; | |||
for(int k = 0; k <= x; k++){ | |||
result += probability(k, lambda); | |||
} | |||
return result; | |||
} | |||
/** All long-representable factorials */ | |||
private final long[] FACTORIALS = new long[] { | |||
1l, 1l, 2l, | |||
6l, 24l, 120l, | |||
720l, 5040l, 40320l, | |||
362880l, 3628800l, 39916800l, | |||
479001600l, 6227020800l, 87178291200l, | |||
1307674368000l, 20922789888000l, 355687428096000l, | |||
6402373705728000l, 121645100408832000l, 2432902008176640000l }; | |||
public long factorial(final int n) { | |||
if (n < 0 || n > 20) { | |||
throw new IllegalArgumentException("Valid argument should be in the range [0..20]"); | |||
} | |||
return FACTORIALS[n]; | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, ValueEval arg2) { | |||
// arguments/result for this function | |||
double mean=0; | |||
double x=0; | |||
boolean cumulative = ((BoolEval)arg2).getBooleanValue(); | |||
double result=0; | |||
try { | |||
x = NumericFunction.singleOperandEvaluate(arg0, srcRowIndex, srcColumnIndex); | |||
mean = NumericFunction.singleOperandEvaluate(arg1, srcRowIndex, srcColumnIndex); | |||
// check for default result : excel implementation for 0,0 | |||
// is different to Math Common. | |||
if (isDefaultResult(x,mean)) { | |||
return new NumberEval(DEFAULT_RETURN_RESULT); | |||
} | |||
// check the arguments : as per excel function def | |||
checkArgument(x); | |||
checkArgument(mean); | |||
// truncate x : as per excel function def | |||
if ( cumulative ) { | |||
result = cumulativeProbability((int)x, mean); | |||
} else { | |||
result = probability((int)x, mean); | |||
} | |||
// check the result | |||
NumericFunction.checkValue(result); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
}; | |||
} |
@@ -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; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* | |||
*/ | |||
public final class Odd extends NumericFunction.OneArg { | |||
private static final long PARITY_MASK = 0xFFFFFFFFFFFFFFFEL; | |||
protected double evaluate(double d) { | |||
if (d==0) { | |||
return 1; | |||
} | |||
if (d>0) { | |||
return calcOdd(d); | |||
} | |||
return -calcOdd(-d); | |||
} | |||
private static long calcOdd(double d) { | |||
double dpm1 = d+1; | |||
long x = ((long) dpm1) & PARITY_MASK; | |||
if (x == dpm1) { | |||
return x-1; | |||
} | |||
return x + 1; | |||
} | |||
} |
@@ -0,0 +1,227 @@ | |||
/* ==================================================================== | |||
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.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.RefEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Implementation for Excel function OFFSET()<p/> | |||
* | |||
* OFFSET returns an area reference that is a specified number of rows and columns from a | |||
* reference cell or area.<p/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>OFFSET</b>(<b>reference</b>, <b>rows</b>, <b>cols</b>, height, width)<p/> | |||
* <b>reference</b> is the base reference.<br/> | |||
* <b>rows</b> is the number of rows up or down from the base reference.<br/> | |||
* <b>cols</b> is the number of columns left or right from the base reference.<br/> | |||
* <b>height</b> (default same height as base reference) is the row count for the returned area reference.<br/> | |||
* <b>width</b> (default same width as base reference) is the column count for the returned area reference.<br/> | |||
* | |||
* @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.<p/> | |||
* | |||
* 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. <p/> | |||
* | |||
* @param translationAmount may be zero negative or positive | |||
* | |||
* @return the equivalent <tt>LinearOffsetRange</tt> 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); | |||
} | |||
} |
@@ -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:<p/> | |||
* Replaces part of a text string based on the number of characters | |||
* you specify, with another text string.<br/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>REPLACE</b>(<b>oldText</b>, <b>startNum</b>, <b>numChars</b>, <b>newText</b>)<p/> | |||
* | |||
* <b>oldText</b> The text string containing characters to replace<br/> | |||
* <b>startNum</b> The position of the first character to replace (1-based)<br/> | |||
* <b>numChars</b> The number of characters to replace<br/> | |||
* <b>newText</b> The new text value to replace the removed section<br/> | |||
* | |||
* @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()); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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<iSize; i++) { | |||
s += v[i]; | |||
} | |||
m = s / v.length; | |||
s = 0; | |||
for (int i=0, iSize=v.length; i<iSize; i++) { | |||
s += Math.abs(v[i]-m); | |||
} | |||
r = s / v.length; | |||
return r; | |||
} | |||
public static double stdev(double[] v) { | |||
double r = Double.NaN; | |||
if (v!=null && v.length > 1) { | |||
r = Math.sqrt( devsq(v) / (v.length - 1) ); | |||
} | |||
return r; | |||
} | |||
public static double median(double[] v) { | |||
double r = Double.NaN; | |||
if (v!=null && v.length >= 1) { | |||
int n = v.length; | |||
Arrays.sort(v); | |||
r = (n % 2 == 0) | |||
? (v[n / 2] + v[n / 2 - 1]) / 2 | |||
: v[n / 2]; | |||
} | |||
return r; | |||
} | |||
public static double devsq(double[] v) { | |||
double r = Double.NaN; | |||
if (v!=null && v.length >= 1) { | |||
double m = 0; | |||
double s = 0; | |||
int n = v.length; | |||
for (int i=0; i<n; i++) { | |||
s += v[i]; | |||
} | |||
m = s / n; | |||
s = 0; | |||
for (int i=0; i<n; i++) { | |||
s += (v[i]- m) * (v[i] - m); | |||
} | |||
r = (n == 1) | |||
? 0 | |||
: s; | |||
} | |||
return r; | |||
} | |||
/** | |||
* returns the kth largest element in the array. Duplicates | |||
* are considered as distinct values. Hence, eg. | |||
* for array {1,2,4,3,3} & k=2, returned value is 3. | |||
* <br/> | |||
* k <= 0 & k >= v.length and null or empty arrays | |||
* will result in return value Double.NaN | |||
*/ | |||
public static double kthLargest(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[v.length-index-1]; | |||
} | |||
return r; | |||
} | |||
/** | |||
* returns the kth smallest element in the array. Duplicates | |||
* are considered as distinct values. Hence, eg. | |||
* for array {1,1,2,4,3,3} & k=2, returned value is 1. | |||
* <br/> | |||
* 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; | |||
} | |||
} |
@@ -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:<P/> | |||
* 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(); | |||
} | |||
} | |||
} |
@@ -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<p> | |||
* | |||
* <b>Syntax :</b> <br/> | |||
* SUBTOTAL ( <b>functionCode</b>, <b>ref1</b>, ref2 ... ) <br/> | |||
* <table border="1" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><td><b>functionCode</b></td><td>(1-11) Selects the underlying aggregate function to be used (see table below)</td></tr> | |||
* <tr><td><b>ref1</b>, ref2 ...</td><td>Arguments to be passed to the underlying aggregate function</td></tr> | |||
* </table><br/> | |||
* </p> | |||
* | |||
* <table border="1" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><th>functionCode</th><th>Aggregate Function</th></tr> | |||
* <tr align='center'><td>1</td><td>AVERAGE</td></tr> | |||
* <tr align='center'><td>2</td><td>COUNT</td></tr> | |||
* <tr align='center'><td>3</td><td>COUNTA</td></tr> | |||
* <tr align='center'><td>4</td><td>MAX</td></tr> | |||
* <tr align='center'><td>5</td><td>MIN</td></tr> | |||
* <tr align='center'><td>6</td><td>PRODUCT</td></tr> | |||
* <tr align='center'><td>7</td><td>STDEV</td></tr> | |||
* <tr align='center'><td>8</td><td>STDEVP *</td></tr> | |||
* <tr align='center'><td>9</td><td>AVERAGE</td></tr> | |||
* <tr align='center'><td>10</td><td>VAR *</td></tr> | |||
* <tr align='center'><td>11</td><td>VARP *</td></tr> | |||
* <tr align='center'><td>101-111</td><td>*</td></tr> | |||
* </table><br/> | |||
* * Not implemented in POI yet. Functions 101-111 are the same as functions 1-11 but with | |||
* the option 'ignore hidden values'. | |||
* <p/> | |||
* | |||
* @author Paul Tomlin < pault at bulk sms dot com > | |||
*/ | |||
public class Subtotal implements Function { | |||
private static Function findFunction(int functionCode) throws EvaluationException { | |||
switch (functionCode) { | |||
case 1: return AggregateFunction.AVERAGE; | |||
case 2: return new Count(); | |||
case 3: return new Counta(); | |||
case 4: return AggregateFunction.MAX; | |||
case 5: return AggregateFunction.MIN; | |||
case 6: return AggregateFunction.PRODUCT; | |||
case 7: return AggregateFunction.STDEV; | |||
case 8: throw new NotImplementedException("STDEVP"); | |||
case 9: return AggregateFunction.SUM; | |||
case 10: throw new NotImplementedException("VAR"); | |||
case 11: throw new NotImplementedException("VARP"); | |||
} | |||
if (functionCode > 100 && functionCode < 112) { | |||
throw new NotImplementedException("SUBTOTAL - with 'exclude hidden values' option"); | |||
} | |||
throw EvaluationException.invalidValue(); | |||
} | |||
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
int nInnerArgs = args.length-1; // -1: first arg is used to select from a basic aggregate function | |||
if (nInnerArgs < 1) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
Function innerFunc; | |||
try { | |||
ValueEval ve = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex); | |||
int functionCode = OperandResolver.coerceValueToInt(ve); | |||
innerFunc = findFunction(functionCode); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
ValueEval[] innerArgs = new ValueEval[nInnerArgs]; | |||
System.arraycopy(args, 1, innerArgs, 0, nInnerArgs); | |||
return innerFunc.evaluate(innerArgs, srcRowIndex, srcColumnIndex); | |||
} | |||
} |
@@ -0,0 +1,127 @@ | |||
/* ==================================================================== | |||
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.EvaluationException; | |||
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.functions.CountUtils.I_MatchPredicate; | |||
/** | |||
* Implementation for the Excel function SUMIF<p> | |||
* | |||
* Syntax : <br/> | |||
* SUMIF ( <b>range</b>, <b>criteria</b>, sum_range ) <br/> | |||
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><th>range</th><td>The range over which criteria is applied. Also used for addend values when the third parameter is not present</td></tr> | |||
* <tr><th>criteria</th><td>The value or expression used to filter rows from <b>range</b></td></tr> | |||
* <tr><th>sum_range</th><td>Locates the top-left corner of the corresponding range of addends - values to be added (after being selected by the criteria)</td></tr> | |||
* </table><br/> | |||
* </p> | |||
* @author Josh Micich | |||
*/ | |||
public final class Sumif extends Var2or3ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
AreaEval aeRange; | |||
try { | |||
aeRange = convertRangeArg(arg0); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return eval(srcRowIndex, srcColumnIndex, arg1, aeRange, aeRange); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
AreaEval aeRange; | |||
AreaEval aeSum; | |||
try { | |||
aeRange = convertRangeArg(arg0); | |||
aeSum = createSumRange(arg2, aeRange); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return eval(srcRowIndex, srcColumnIndex, arg1, aeRange, aeSum); | |||
} | |||
private static ValueEval eval(int srcRowIndex, int srcColumnIndex, ValueEval arg1, AreaEval aeRange, | |||
AreaEval aeSum) { | |||
// TODO - junit to prove last arg must be srcColumnIndex and not srcRowIndex | |||
I_MatchPredicate mp = Countif.createCriteriaPredicate(arg1, srcRowIndex, srcColumnIndex); | |||
double result = sumMatchingCells(aeRange, mp, aeSum); | |||
return new NumberEval(result); | |||
} | |||
private static double sumMatchingCells(AreaEval aeRange, I_MatchPredicate mp, AreaEval aeSum) { | |||
int height=aeRange.getHeight(); | |||
int width= aeRange.getWidth(); | |||
double result = 0.0; | |||
for (int r=0; r<height; r++) { | |||
for (int c=0; c<width; c++) { | |||
result += accumulate(aeRange, mp, aeSum, r, c); | |||
} | |||
} | |||
return result; | |||
} | |||
private static double accumulate(AreaEval aeRange, I_MatchPredicate mp, AreaEval aeSum, int relRowIndex, | |||
int relColIndex) { | |||
if (!mp.matches(aeRange.getRelativeValue(relRowIndex, relColIndex))) { | |||
return 0.0; | |||
} | |||
ValueEval addend = aeSum.getRelativeValue(relRowIndex, relColIndex); | |||
if (addend instanceof NumberEval) { | |||
return ((NumberEval)addend).getNumberValue(); | |||
} | |||
// everything else (including string and boolean values) counts as zero | |||
return 0.0; | |||
} | |||
/** | |||
* @return a range of the same dimensions as aeRange using eval to define the top left corner. | |||
* @throws EvaluationException if eval is not a reference | |||
*/ | |||
private static AreaEval createSumRange(ValueEval eval, AreaEval aeRange) throws EvaluationException { | |||
if (eval instanceof AreaEval) { | |||
return ((AreaEval) eval).offset(0, aeRange.getHeight()-1, 0, aeRange.getWidth()-1); | |||
} | |||
if (eval instanceof RefEval) { | |||
return ((RefEval)eval).offset(0, aeRange.getHeight()-1, 0, aeRange.getWidth()-1); | |||
} | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
private static AreaEval convertRangeArg(ValueEval eval) throws EvaluationException { | |||
if (eval instanceof AreaEval) { | |||
return (AreaEval) eval; | |||
} | |||
if (eval instanceof RefEval) { | |||
return ((RefEval)eval).offset(0, 0, 0, 0); | |||
} | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
} |
@@ -0,0 +1,231 @@ | |||
/* ==================================================================== | |||
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.ErrorEval; | |||
import org.apache.poi.ss.formula.eval.EvaluationException; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.NumericValueEval; | |||
import org.apache.poi.ss.formula.eval.RefEval; | |||
import org.apache.poi.ss.formula.eval.StringEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* Implementation for the Excel function SUMPRODUCT<p> | |||
* | |||
* Syntax : <br/> | |||
* SUMPRODUCT ( array1[, array2[, array3[, ...]]]) | |||
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions"> | |||
* <tr><th>array1, ... arrayN </th><td>typically area references, | |||
* possibly cell references or scalar values</td></tr> | |||
* </table><br/> | |||
* | |||
* Let A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub> represent the element in the <b>i</b>th row <b>j</b>th column | |||
* of the <b>n</b>th array<br/> | |||
* Assuming each array has the same dimensions (W, H), the result is defined as:<br/> | |||
* SUMPRODUCT = Σ<sub><b>i</b>: 1..H</sub> | |||
* ( Σ<sub><b>j</b>: 1..W</sub> | |||
* ( Π<sub><b>n</b>: 1..N</sub> | |||
* A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub> | |||
* ) | |||
* ) | |||
* </p> | |||
* @author Josh Micich | |||
*/ | |||
public final class Sumproduct implements Function { | |||
public ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
int maxN = args.length; | |||
if(maxN < 1) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
ValueEval firstArg = args[0]; | |||
try { | |||
if(firstArg instanceof NumericValueEval) { | |||
return evaluateSingleProduct(args); | |||
} | |||
if(firstArg instanceof RefEval) { | |||
return evaluateSingleProduct(args); | |||
} | |||
if (firstArg instanceof TwoDEval) { | |||
TwoDEval ae = (TwoDEval) firstArg; | |||
if(ae.isRow() && ae.isColumn()) { | |||
return evaluateSingleProduct(args); | |||
} | |||
return evaluateAreaSumProduct(args); | |||
} | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
throw new RuntimeException("Invalid arg type for SUMPRODUCT: (" | |||
+ firstArg.getClass().getName() + ")"); | |||
} | |||
private static ValueEval evaluateSingleProduct(ValueEval[] evalArgs) throws EvaluationException { | |||
int maxN = evalArgs.length; | |||
double term = 1D; | |||
for(int n=0; n<maxN; n++) { | |||
double val = getScalarValue(evalArgs[n]); | |||
term *= val; | |||
} | |||
return new NumberEval(term); | |||
} | |||
private static double getScalarValue(ValueEval arg) throws EvaluationException { | |||
ValueEval eval; | |||
if (arg instanceof RefEval) { | |||
RefEval re = (RefEval) arg; | |||
eval = re.getInnerValueEval(); | |||
} else { | |||
eval = arg; | |||
} | |||
if (eval == null) { | |||
throw new RuntimeException("parameter may not be null"); | |||
} | |||
if (eval instanceof AreaEval) { | |||
AreaEval ae = (AreaEval) eval; | |||
// an area ref can work as a scalar value if it is 1x1 | |||
if(!ae.isColumn() || !ae.isRow()) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
eval = ae.getRelativeValue(0, 0); | |||
} | |||
return getProductTerm(eval, true); | |||
} | |||
private static ValueEval evaluateAreaSumProduct(ValueEval[] evalArgs) throws EvaluationException { | |||
int maxN = evalArgs.length; | |||
TwoDEval[] args = new TwoDEval[maxN]; | |||
try { | |||
System.arraycopy(evalArgs, 0, args, 0, maxN); | |||
} catch (ArrayStoreException e) { | |||
// one of the other args was not an AreaRef | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
TwoDEval firstArg = args[0]; | |||
int height = firstArg.getHeight(); | |||
int width = firstArg.getWidth(); // TODO - junit | |||
// first check dimensions | |||
if (!areasAllSameSize(args, height, width)) { | |||
// normally this results in #VALUE!, | |||
// but errors in individual cells take precedence | |||
for (int i = 1; i < args.length; i++) { | |||
throwFirstError(args[i]); | |||
} | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
double acc = 0; | |||
for (int rrIx=0; rrIx<height; rrIx++) { | |||
for (int rcIx=0; rcIx<width; rcIx++) { | |||
double term = 1D; | |||
for(int n=0; n<maxN; n++) { | |||
double val = getProductTerm(args[n].getValue(rrIx, rcIx), false); | |||
term *= val; | |||
} | |||
acc += term; | |||
} | |||
} | |||
return new NumberEval(acc); | |||
} | |||
private static void throwFirstError(TwoDEval areaEval) throws EvaluationException { | |||
int height = areaEval.getHeight(); | |||
int width = areaEval.getWidth(); | |||
for (int rrIx=0; rrIx<height; rrIx++) { | |||
for (int rcIx=0; rcIx<width; rcIx++) { | |||
ValueEval ve = areaEval.getValue(rrIx, rcIx); | |||
if (ve instanceof ErrorEval) { | |||
throw new EvaluationException((ErrorEval) ve); | |||
} | |||
} | |||
} | |||
} | |||
private static boolean areasAllSameSize(TwoDEval[] args, int height, int width) { | |||
for (int i = 0; i < args.length; i++) { | |||
TwoDEval areaEval = args[i]; | |||
// check that height and width match | |||
if(areaEval.getHeight() != height) { | |||
return false; | |||
} | |||
if(areaEval.getWidth() != width) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
/** | |||
* Determines a <code>double</code> value for the specified <code>ValueEval</code>. | |||
* @param isScalarProduct <code>false</code> for SUMPRODUCTs over area refs. | |||
* @throws EvaluationException if <code>ve</code> represents an error value. | |||
* <p/> | |||
* Note - string values and empty cells are interpreted differently depending on | |||
* <code>isScalarProduct</code>. 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() + ")"); | |||
} | |||
} |
@@ -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()<p/> | |||
* | |||
* Calculates the sum of differences of squares in two arrays of the same size.<br/> | |||
* <b>Syntax</b>:<br/> | |||
* <b>SUMX2MY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/> | |||
* | |||
* result = Σ<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>-y<sub>i</sub><sup>2</sup>) | |||
* | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public final class Sumx2my2 extends XYNumericFunction { | |||
private static final Accumulator XSquaredMinusYSquaredAccumulator = new Accumulator() { | |||
public double accumulate(double x, double y) { | |||
return x * x - y * y; | |||
} | |||
}; | |||
protected Accumulator createAccumulator() { | |||
return XSquaredMinusYSquaredAccumulator; | |||
} | |||
} |
@@ -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 SUMX2PY2()<p/> | |||
* | |||
* Calculates the sum of squares in two arrays of the same size.<br/> | |||
* <b>Syntax</b>:<br/> | |||
* <b>SUMX2PY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/> | |||
* | |||
* result = Σ<sub>i: 0..n</sub>(x<sub>i</sub><sup>2</sup>+y<sub>i</sub><sup>2</sup>) | |||
* | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public final class Sumx2py2 extends XYNumericFunction { | |||
private static final Accumulator XSquaredPlusYSquaredAccumulator = new Accumulator() { | |||
public double accumulate(double x, double y) { | |||
return x * x + y * y; | |||
} | |||
}; | |||
protected Accumulator createAccumulator() { | |||
return XSquaredPlusYSquaredAccumulator; | |||
} | |||
} |
@@ -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 SUMXMY2()<p/> | |||
* | |||
* Calculates the sum of squares of differences between two arrays of the same size.<br/> | |||
* <b>Syntax</b>:<br/> | |||
* <b>SUMXMY2</b>(<b>arrayX</b>, <b>arrayY</b>)<p/> | |||
* | |||
* result = Σ<sub>i: 0..n</sub>(x<sub>i</sub>-y<sub>i</sub>)<sup>2</sup> | |||
* | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public final class Sumxmy2 extends XYNumericFunction { | |||
private static final Accumulator XMinusYSquaredAccumulator = new Accumulator() { | |||
public double accumulate(double x, double y) { | |||
double xmy = x - y; | |||
return xmy * xmy; | |||
} | |||
}; | |||
protected Accumulator createAccumulator() { | |||
return XMinusYSquaredAccumulator; | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.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.RefEval; | |||
import org.apache.poi.ss.formula.eval.StringEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Implementation of Excel T() function | |||
* <p/> | |||
* If the argument is a text or error value it is returned unmodified. All other argument types | |||
* cause an empty string result. If the argument is an area, the first (top-left) cell is used | |||
* (regardless of the coordinates of the evaluating formula cell). | |||
*/ | |||
public final class T extends Fixed1ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
ValueEval arg = arg0; | |||
if (arg instanceof RefEval) { | |||
arg = ((RefEval) arg).getInnerValueEval(); | |||
} else if (arg instanceof AreaEval) { | |||
// when the arg is an area, choose the top left cell | |||
arg = ((AreaEval) arg).getRelativeValue(0, 0); | |||
} | |||
if (arg instanceof StringEval) { | |||
// Text values are returned unmodified | |||
return arg; | |||
} | |||
if (arg instanceof ErrorEval) { | |||
// Error values also returned unmodified | |||
return arg; | |||
} | |||
// for all other argument types the result is empty string | |||
return StringEval.EMPTY_INSTANCE; | |||
} | |||
} |
@@ -0,0 +1,372 @@ | |||
/* ==================================================================== | |||
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.text.DateFormat; | |||
import java.text.DecimalFormat; | |||
import java.text.NumberFormat; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Calendar; | |||
import java.util.GregorianCalendar; | |||
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.StringEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
* @author Josh Micich | |||
* @author Stephen Wolke (smwolke at geistig.com) | |||
*/ | |||
public abstract class TextFunction implements Function { | |||
protected static final String EMPTY_STRING = ""; | |||
protected static final String evaluateStringArg(ValueEval eval, int srcRow, int srcCol) throws EvaluationException { | |||
ValueEval ve = OperandResolver.getSingleValue(eval, srcRow, srcCol); | |||
return OperandResolver.coerceValueToString(ve); | |||
} | |||
protected static final int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { | |||
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | |||
return OperandResolver.coerceValueToInt(ve); | |||
} | |||
protected static final double evaluateDoubleArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException { | |||
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol); | |||
return OperandResolver.coerceValueToDouble(ve); | |||
} | |||
public final ValueEval evaluate(ValueEval[] args, int srcCellRow, int srcCellCol) { | |||
try { | |||
return evaluateFunc(args, srcCellRow, srcCellCol); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
protected abstract ValueEval evaluateFunc(ValueEval[] args, int srcCellRow, int srcCellCol) throws EvaluationException; | |||
/* ---------------------------------------------------------------------- */ | |||
private static abstract class SingleArgTextFunc extends Fixed1ArgFunction { | |||
protected SingleArgTextFunc() { | |||
// no fields to initialise | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
String arg; | |||
try { | |||
arg = evaluateStringArg(arg0, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return evaluate(arg); | |||
} | |||
protected abstract ValueEval evaluate(String arg); | |||
} | |||
public static final Function LEN = new SingleArgTextFunc() { | |||
protected ValueEval evaluate(String arg) { | |||
return new NumberEval(arg.length()); | |||
} | |||
}; | |||
public static final Function LOWER = new SingleArgTextFunc() { | |||
protected ValueEval evaluate(String arg) { | |||
return new StringEval(arg.toLowerCase()); | |||
} | |||
}; | |||
public static final Function UPPER = new SingleArgTextFunc() { | |||
protected ValueEval evaluate(String arg) { | |||
return new StringEval(arg.toUpperCase()); | |||
} | |||
}; | |||
/** | |||
* An implementation of the TRIM function: | |||
* Removes leading and trailing spaces from value if evaluated operand | |||
* value is string. | |||
* Author: Manda Wilson < wilson at c bio dot msk cc dot org > | |||
*/ | |||
public static final Function TRIM = new SingleArgTextFunc() { | |||
protected ValueEval evaluate(String arg) { | |||
return new StringEval(arg.trim()); | |||
} | |||
}; | |||
/** | |||
* An implementation of the MID function<br/> | |||
* MID returns a specific number of | |||
* characters from a text string, starting at the specified position.<p/> | |||
* | |||
* <b>Syntax<b>:<br/> <b>MID</b>(<b>text</b>, <b>start_num</b>, | |||
* <b>num_chars</b>)<br/> | |||
* | |||
* Author: Manda Wilson < wilson at c bio dot msk cc dot org > | |||
*/ | |||
public static final Function MID = new Fixed3ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, | |||
ValueEval arg1, ValueEval arg2) { | |||
String text; | |||
int startCharNum; | |||
int numChars; | |||
try { | |||
text = evaluateStringArg(arg0, srcRowIndex, srcColumnIndex); | |||
startCharNum = evaluateIntArg(arg1, srcRowIndex, srcColumnIndex); | |||
numChars = evaluateIntArg(arg2, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
int startIx = startCharNum - 1; // convert to zero-based | |||
// Note - for start_num arg, blank/zero causes error(#VALUE!), | |||
// but for num_chars causes empty string to be returned. | |||
if (startIx < 0) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
if (numChars < 0) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
int len = text.length(); | |||
if (numChars < 0 || startIx > len) { | |||
return new StringEval(""); | |||
} | |||
int endIx = Math.min(startIx + numChars, len); | |||
String result = text.substring(startIx, endIx); | |||
return new StringEval(result); | |||
} | |||
}; | |||
private static final class LeftRight extends Var1or2ArgFunction { | |||
private static final ValueEval DEFAULT_ARG1 = new NumberEval(1.0); | |||
private final boolean _isLeft; | |||
protected LeftRight(boolean isLeft) { | |||
_isLeft = isLeft; | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, DEFAULT_ARG1); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, | |||
ValueEval arg1) { | |||
String arg; | |||
int index; | |||
try { | |||
arg = evaluateStringArg(arg0, srcRowIndex, srcColumnIndex); | |||
index = evaluateIntArg(arg1, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if(index < 0) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
String result; | |||
if (_isLeft) { | |||
result = arg.substring(0, Math.min(arg.length(), index)); | |||
} else { | |||
result = arg.substring(Math.max(0, arg.length()-index)); | |||
} | |||
return new StringEval(result); | |||
} | |||
} | |||
public static final Function LEFT = new LeftRight(true); | |||
public static final Function RIGHT = new LeftRight(false); | |||
public static final Function CONCATENATE = new Function() { | |||
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { | |||
StringBuilder sb = new StringBuilder(); | |||
for (int i=0, iSize=args.length; i<iSize; i++) { | |||
try { | |||
sb.append(evaluateStringArg(args[i], srcRowIndex, srcColumnIndex)); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
return new StringEval(sb.toString()); | |||
} | |||
}; | |||
public static final Function EXACT = new Fixed2ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, | |||
ValueEval arg1) { | |||
String s0; | |||
String s1; | |||
try { | |||
s0 = evaluateStringArg(arg0, srcRowIndex, srcColumnIndex); | |||
s1 = evaluateStringArg(arg1, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return BoolEval.valueOf(s0.equals(s1)); | |||
} | |||
}; | |||
/** | |||
* An implementation of the TEXT function<br/> | |||
* TEXT returns a number value formatted with the given | |||
* number formatting string. This function is not a complete implementation of | |||
* the Excel function. This function implements decimal formatting | |||
* with the Java class DecimalFormat. For date formatting this function uses | |||
* the SimpleDateFormat class.<p/> | |||
* | |||
* <b>Syntax<b>:<br/> <b>TEXT</b>(<b>value</b>, <b>format_text</b>)<br/> | |||
* | |||
*/ | |||
public static final Function TEXT = new Fixed2ArgFunction() { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
double s0; | |||
String s1; | |||
try { | |||
s0 = evaluateDoubleArg(arg0, srcRowIndex, srcColumnIndex); | |||
s1 = evaluateStringArg(arg1, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (s1.matches("[\\d,\\#,\\.,\\$,\\,]+")) { | |||
NumberFormat formatter = new DecimalFormat(s1); | |||
return new StringEval(formatter.format(s0)); | |||
} else if (s1.indexOf("/") == s1.lastIndexOf("/") && s1.indexOf("/") >=0 && !s1.contains("-")) { | |||
double wholePart = Math.floor(s0); | |||
double decPart = s0 - wholePart; | |||
if (wholePart * decPart == 0) { | |||
return new StringEval("0"); | |||
} | |||
String[] parts = s1.split(" "); | |||
String[] fractParts; | |||
if (parts.length == 2) { | |||
fractParts = parts[1].split("/"); | |||
} else { | |||
fractParts = s1.split("/"); | |||
} | |||
if (fractParts.length == 2) { | |||
double minVal = 1.0; | |||
double currDenom = Math.pow(10 , fractParts[1].length()) - 1d; | |||
double currNeum = 0; | |||
for (int i = (int)(Math.pow(10, fractParts[1].length())- 1d); i > 0; i--) { | |||
for(int i2 = (int)(Math.pow(10, fractParts[1].length())- 1d); i2 > 0; i2--){ | |||
if (minVal >= Math.abs((double)i2/(double)i - decPart)) { | |||
currDenom = i; | |||
currNeum = i2; | |||
minVal = Math.abs((double)i2/(double)i - decPart); | |||
} | |||
} | |||
} | |||
NumberFormat neumFormatter = new DecimalFormat(fractParts[0]); | |||
NumberFormat denomFormatter = new DecimalFormat(fractParts[1]); | |||
if (parts.length == 2) { | |||
NumberFormat wholeFormatter = new DecimalFormat(parts[0]); | |||
String result = wholeFormatter.format(wholePart) + " " + neumFormatter.format(currNeum) + "/" + denomFormatter.format(currDenom); | |||
return new StringEval(result); | |||
} else { | |||
String result = neumFormatter.format(currNeum + (currDenom * wholePart)) + "/" + denomFormatter.format(currDenom); | |||
return new StringEval(result); | |||
} | |||
} else { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
} else { | |||
try { | |||
DateFormat dateFormatter = new SimpleDateFormat(s1); | |||
Calendar cal = new GregorianCalendar(1899, 11, 30, 0, 0, 0); | |||
cal.add(Calendar.DATE, (int)Math.floor(s0)); | |||
double dayFraction = s0 - Math.floor(s0); | |||
cal.add(Calendar.MILLISECOND, (int) Math.round(dayFraction * 24 * 60 * 60 * 1000)); | |||
return new StringEval(dateFormatter.format(cal.getTime())); | |||
} catch (Exception e) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
} | |||
} | |||
}; | |||
private static final class SearchFind extends Var2or3ArgFunction { | |||
private final boolean _isCaseSensitive; | |||
public SearchFind(boolean isCaseSensitive) { | |||
_isCaseSensitive = isCaseSensitive; | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
try { | |||
String needle = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex); | |||
String haystack = TextFunction.evaluateStringArg(arg1, srcRowIndex, srcColumnIndex); | |||
return eval(haystack, needle, 0); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
try { | |||
String needle = TextFunction.evaluateStringArg(arg0, srcRowIndex, srcColumnIndex); | |||
String haystack = TextFunction.evaluateStringArg(arg1, srcRowIndex, srcColumnIndex); | |||
// evaluate third arg and convert from 1-based to 0-based index | |||
int startpos = TextFunction.evaluateIntArg(arg2, srcRowIndex, srcColumnIndex) - 1; | |||
if (startpos < 0) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return eval(haystack, needle, startpos); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
private ValueEval eval(String haystack, String needle, int startIndex) { | |||
int result; | |||
if (_isCaseSensitive) { | |||
result = haystack.indexOf(needle, startIndex); | |||
} else { | |||
result = haystack.toUpperCase().indexOf(needle.toUpperCase(), startIndex); | |||
} | |||
if (result == -1) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return new NumberEval(result + 1); | |||
} | |||
} | |||
/** | |||
* Implementation of the FIND() function.<p/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>FIND</b>(<b>find_text</b>, <b>within_text</b>, start_num)<p/> | |||
* | |||
* FIND returns the character position of the first (case sensitive) occurrence of | |||
* <tt>find_text</tt> inside <tt>within_text</tt>. The third parameter, | |||
* <tt>start_num</tt>, is optional (default=1) and specifies where to start searching | |||
* from. Character positions are 1-based.<p/> | |||
* | |||
* Author: Torstein Tauno Svendsen (torstei@officenet.no) | |||
*/ | |||
public static final Function FIND = new SearchFind(true); | |||
/** | |||
* Implementation of the FIND() function.<p/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>SEARCH</b>(<b>find_text</b>, <b>within_text</b>, start_num)<p/> | |||
* | |||
* SEARCH is a case-insensitive version of FIND() | |||
*/ | |||
public static final Function SEARCH = new SearchFind(false); | |||
} |
@@ -0,0 +1,87 @@ | |||
/* ==================================================================== | |||
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.MissingArgEval; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Implementation for the Excel function TIME | |||
* | |||
* @author Steven Butler (sebutler @ gmail dot com) | |||
* | |||
* Based on POI {@link DateFunc} | |||
*/ | |||
public final class TimeFunc extends Fixed3ArgFunction { | |||
private static final int SECONDS_PER_MINUTE = 60; | |||
private static final int SECONDS_PER_HOUR = 3600; | |||
private static final int HOURS_PER_DAY = 24; | |||
private static final int SECONDS_PER_DAY = HOURS_PER_DAY * SECONDS_PER_HOUR; | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
double result; | |||
try { | |||
result = evaluate(evalArg(arg0, srcRowIndex, srcColumnIndex), evalArg(arg1, srcRowIndex, srcColumnIndex), evalArg(arg2, srcRowIndex, srcColumnIndex)); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
return new NumberEval(result); | |||
} | |||
private static int evalArg(ValueEval arg, int srcRowIndex, int srcColumnIndex) throws EvaluationException { | |||
if (arg == MissingArgEval.instance) { | |||
return 0; | |||
} | |||
ValueEval ev = OperandResolver.getSingleValue(arg, srcRowIndex, srcColumnIndex); | |||
// Excel silently truncates double values to integers | |||
return OperandResolver.coerceValueToInt(ev); | |||
} | |||
/** | |||
* Converts the supplied hours, minutes and seconds to an Excel time value. | |||
* | |||
* | |||
* @param ds array of 3 doubles containing hours, minutes and seconds. | |||
* Non-integer inputs are truncated to an integer before further calculation | |||
* of the time value. | |||
* @return An Excel representation of a time of day. | |||
* If the time value represents more than a day, the days are removed from | |||
* the result, leaving only the time of day component. | |||
* @throws org.apache.poi.ss.formula.eval.EvaluationException | |||
* If any of the arguments are greater than 32767 or the hours | |||
* minutes and seconds when combined form a time value less than 0, the function | |||
* evaluates to an error. | |||
*/ | |||
private static double evaluate(int hours, int minutes, int seconds) throws EvaluationException { | |||
if (hours > 32767 || minutes > 32767 || seconds > 32767) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
int totalSeconds = hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds; | |||
if (totalSeconds < 0) { | |||
throw new EvaluationException(ErrorEval.VALUE_INVALID); | |||
} | |||
return (totalSeconds % SECONDS_PER_DAY) / (double)SECONDS_PER_DAY; | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
/* ==================================================================== | |||
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.Calendar; | |||
import java.util.GregorianCalendar; | |||
import org.apache.poi.ss.formula.eval.NumberEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.usermodel.DateUtil; | |||
/** | |||
* Implementation of Excel TODAY() Function<br/> | |||
* | |||
* @author Frank Taffelt | |||
*/ | |||
public final class Today extends Fixed0ArgFunction { | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex) { | |||
Calendar now = new GregorianCalendar(); | |||
now.set(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DATE),0,0,0); | |||
now.set(Calendar.MILLISECOND, 0); | |||
return new NumberEval(DateUtil.getExcelDate(now.getTime())); | |||
} | |||
} |
@@ -0,0 +1,184 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.OperandResolver; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
/** | |||
* Implementation for Excel VALUE() function.<p/> | |||
* | |||
* <b>Syntax</b>:<br/> <b>VALUE</b>(<b>text</b>)<br/> | |||
* | |||
* Converts the text argument to a number. Leading and/or trailing whitespace is | |||
* ignored. Currency symbols and thousands separators are stripped out. | |||
* Scientific notation is also supported. If the supplied text does not convert | |||
* properly the result is <b>#VALUE!</b> error. Blank string converts to zero. | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Value extends Fixed1ArgFunction { | |||
/** "1,0000" is valid, "1,00" is not */ | |||
private static final int MIN_DISTANCE_BETWEEN_THOUSANDS_SEPARATOR = 4; | |||
private static final Double ZERO = new Double(0.0); | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) { | |||
ValueEval veText; | |||
try { | |||
veText = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
String strText = OperandResolver.coerceValueToString(veText); | |||
Double result = convertTextToNumber(strText); | |||
if (result == null) { | |||
return ErrorEval.VALUE_INVALID; | |||
} | |||
return new NumberEval(result.doubleValue()); | |||
} | |||
/** | |||
* TODO see if the same functionality is needed in {@link OperandResolver#parseDouble(String)} | |||
* | |||
* @return <code>null</code> 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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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.<p/> | |||
* | |||
* VLOOKUP finds a row in a lookup table by the first column value and returns the value from another column.<br/> | |||
* | |||
* <b>Syntax</b>:<br/> | |||
* <b>VLOOKUP</b>(<b>lookup_value</b>, <b>table_array</b>, <b>col_index_num</b>, range_lookup)<p/> | |||
* | |||
* <b>lookup_value</b> The value to be found in the first column of the table array.<br/> | |||
* <b>table_array</b> An area reference for the lookup data. <br/> | |||
* <b>col_index_num</b> a 1 based index specifying which column value of the lookup data will be returned.<br/> | |||
* <b>range_lookup</b> If TRUE (default), VLOOKUP finds the largest value less than or equal to | |||
* the lookup_value. If FALSE, only exact matches will be considered<br/> | |||
* | |||
* @author Josh Micich | |||
*/ | |||
public final class Vlookup extends Var3or4ArgFunction { | |||
private static final ValueEval DEFAULT_ARG3 = BoolEval.TRUE; | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2) { | |||
return evaluate(srcRowIndex, srcColumnIndex, arg0, arg1, arg2, DEFAULT_ARG3); | |||
} | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1, | |||
ValueEval arg2, ValueEval arg3) { | |||
try { | |||
// Evaluation order: | |||
// arg0 lookup_value, arg1 table_array, arg3 range_lookup, find lookup value, arg2 col_index, fetch result | |||
ValueEval lookupValue = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex); | |||
TwoDEval tableArray = LookupUtils.resolveTableArrayArg(arg1); | |||
boolean isRangeLookup = LookupUtils.resolveRangeLookupArg(arg3, srcRowIndex, srcColumnIndex); | |||
int rowIndex = LookupUtils.lookupIndexOfValue(lookupValue, LookupUtils.createColumnVector(tableArray, 0), isRangeLookup); | |||
int colIndex = LookupUtils.resolveRowOrColIndexArg(arg2, srcRowIndex, srcColumnIndex); | |||
ValueVector resultCol = createResultColumnVector(tableArray, colIndex); | |||
return resultCol.getItem(rowIndex); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
} | |||
/** | |||
* Returns one column from an <tt>AreaEval</tt> | |||
* | |||
* @param colIndex assumed to be non-negative | |||
* | |||
* @throws EvaluationException (#REF!) if colIndex is too high | |||
*/ | |||
private ValueVector createResultColumnVector(TwoDEval tableArray, int colIndex) throws EvaluationException { | |||
if(colIndex >= tableArray.getWidth()) { | |||
throw EvaluationException.invalidRef(); | |||
} | |||
return LookupUtils.createColumnVector(tableArray, colIndex); | |||
} | |||
} |
@@ -0,0 +1,177 @@ | |||
/* ==================================================================== | |||
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.NumberEval; | |||
import org.apache.poi.ss.formula.eval.RefEval; | |||
import org.apache.poi.ss.formula.eval.ValueEval; | |||
import org.apache.poi.ss.formula.functions.LookupUtils.ValueVector; | |||
import org.apache.poi.ss.formula.TwoDEval; | |||
/** | |||
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > | |||
*/ | |||
public abstract class XYNumericFunction extends Fixed2ArgFunction { | |||
private static abstract class ValueArray implements ValueVector { | |||
private final int _size; | |||
protected ValueArray(int size) { | |||
_size = size; | |||
} | |||
public ValueEval getItem(int index) { | |||
if (index < 0 || index > _size) { | |||
throw new IllegalArgumentException("Specified index " + index | |||
+ " is outside range (0.." + (_size - 1) + ")"); | |||
} | |||
return getItemInternal(index); | |||
} | |||
protected abstract ValueEval getItemInternal(int index); | |||
public final int getSize() { | |||
return _size; | |||
} | |||
} | |||
private static final class SingleCellValueArray extends ValueArray { | |||
private final ValueEval _value; | |||
public SingleCellValueArray(ValueEval value) { | |||
super(1); | |||
_value = value; | |||
} | |||
protected ValueEval getItemInternal(int index) { | |||
return _value; | |||
} | |||
} | |||
private static final class RefValueArray extends ValueArray { | |||
private final RefEval _ref; | |||
public RefValueArray(RefEval ref) { | |||
super(1); | |||
_ref = ref; | |||
} | |||
protected ValueEval getItemInternal(int index) { | |||
return _ref.getInnerValueEval(); | |||
} | |||
} | |||
private static final class AreaValueArray extends ValueArray { | |||
private final TwoDEval _ae; | |||
private final int _width; | |||
public AreaValueArray(TwoDEval ae) { | |||
super(ae.getWidth() * ae.getHeight()); | |||
_ae = ae; | |||
_width = ae.getWidth(); | |||
} | |||
protected ValueEval getItemInternal(int index) { | |||
int rowIx = index / _width; | |||
int colIx = index % _width; | |||
return _ae.getValue(rowIx, colIx); | |||
} | |||
} | |||
protected static interface Accumulator { | |||
double accumulate(double x, double y); | |||
} | |||
/** | |||
* Constructs a new instance of the Accumulator used to calculated this function | |||
*/ | |||
protected abstract Accumulator createAccumulator(); | |||
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) { | |||
double result; | |||
try { | |||
ValueVector vvX = createValueVector(arg0); | |||
ValueVector vvY = createValueVector(arg1); | |||
int size = vvX.getSize(); | |||
if (size == 0 || vvY.getSize() != size) { | |||
return ErrorEval.NA; | |||
} | |||
result = evaluateInternal(vvX, vvY, size); | |||
} catch (EvaluationException e) { | |||
return e.getErrorEval(); | |||
} | |||
if (Double.isNaN(result) || Double.isInfinite(result)) { | |||
return ErrorEval.NUM_ERROR; | |||
} | |||
return new NumberEval(result); | |||
} | |||
private double evaluateInternal(ValueVector x, ValueVector y, int size) | |||
throws EvaluationException { | |||
Accumulator acc = createAccumulator(); | |||
// error handling is as if the x is fully evaluated before y | |||
ErrorEval firstXerr = null; | |||
ErrorEval firstYerr = null; | |||
boolean accumlatedSome = false; | |||
double result = 0.0; | |||
for (int i = 0; i < size; i++) { | |||
ValueEval vx = x.getItem(i); | |||
ValueEval vy = y.getItem(i); | |||
if (vx instanceof ErrorEval) { | |||
if (firstXerr == null) { | |||
firstXerr = (ErrorEval) vx; | |||
continue; | |||
} | |||
} | |||
if (vy instanceof ErrorEval) { | |||
if (firstYerr == null) { | |||
firstYerr = (ErrorEval) vy; | |||
continue; | |||
} | |||
} | |||
// only count pairs if both elements are numbers | |||
if (vx instanceof NumberEval && vy instanceof NumberEval) { | |||
accumlatedSome = true; | |||
NumberEval nx = (NumberEval) vx; | |||
NumberEval ny = (NumberEval) vy; | |||
result += acc.accumulate(nx.getNumberValue(), ny.getNumberValue()); | |||
} else { | |||
// all other combinations of value types are silently ignored | |||
} | |||
} | |||
if (firstXerr != null) { | |||
throw new EvaluationException(firstXerr); | |||
} | |||
if (firstYerr != null) { | |||
throw new EvaluationException(firstYerr); | |||
} | |||
if (!accumlatedSome) { | |||
throw new EvaluationException(ErrorEval.DIV_ZERO); | |||
} | |||
return result; | |||
} | |||
private static ValueVector createValueVector(ValueEval arg) throws EvaluationException { | |||
if (arg instanceof ErrorEval) { | |||
throw new EvaluationException((ErrorEval) arg); | |||
} | |||
if (arg instanceof TwoDEval) { | |||
return new AreaValueArray((TwoDEval) arg); | |||
} | |||
if (arg instanceof RefEval) { | |||
return new RefValueArray((RefEval) arg); | |||
} | |||
return new SingleCellValueArray(arg); | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* ==================================================================== | |||
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 org.apache.poi.ss.formula.functions.FreeRefFunction; | |||
/** | |||
* Collects add-in libraries and VB macro functions together into one UDF finder | |||
* | |||
* @author PUdalau | |||
*/ | |||
public final class AggregatingUDFFinder implements UDFFinder { | |||
private final UDFFinder[] _usedToolPacks; | |||
public AggregatingUDFFinder(UDFFinder ... usedToolPacks) { | |||
_usedToolPacks = usedToolPacks.clone(); | |||
} | |||
/** | |||
* Returns executor by specified name. Returns <code>null</code> if | |||
* function isn't contained by any registered tool pack. | |||
* | |||
* @param name Name of function. | |||
* @return Function executor. <code>null</code> 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; | |||
} | |||
} |
@@ -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 Map<String, FreeRefFunction> _functionsByName; | |||
public DefaultUDFFinder(String[] functionNames, FreeRefFunction[] functionImpls) { | |||
int nFuncs = functionNames.length; | |||
if (functionImpls.length != nFuncs) { | |||
throw new IllegalArgumentException( | |||
"Mismatch in number of function names and implementations"); | |||
} | |||
HashMap<String, FreeRefFunction> m = new HashMap<String, FreeRefFunction>(nFuncs * 3 / 2); | |||
for (int i = 0; i < functionImpls.length; i++) { | |||
m.put(functionNames[i], functionImpls[i]); | |||
} | |||
_functionsByName = m; | |||
} | |||
public FreeRefFunction findFunction(String name) { | |||
return _functionsByName.get(name); | |||
} | |||
} |