mirror of https://github.com/apache/poi.git
moved common formula-related code to org.apache.poi.ss.formula, eliminated dependencies on HSSF, reduced the number of eclipse warnings
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1037436 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
41b294edbf
commit
b34e1e25d6
|
@ -0,0 +1,164 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
||||||
|
import org.apache.poi.ss.formula.udf.UDFFinder;
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
import org.apache.poi.ss.formula.eval.NotImplementedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Micich
|
||||||
|
* @author Petr Udalau - systematized work of add-in libraries and user defined functions.
|
||||||
|
*/
|
||||||
|
public final class AnalysisToolPak implements UDFFinder {
|
||||||
|
|
||||||
|
public static final UDFFinder instance = new AnalysisToolPak();
|
||||||
|
|
||||||
|
private static final class NotImplemented implements FreeRefFunction {
|
||||||
|
private final String _functionName;
|
||||||
|
|
||||||
|
public NotImplemented(String functionName) {
|
||||||
|
_functionName = functionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
|
||||||
|
throw new NotImplementedException(_functionName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Map<String, FreeRefFunction> _functionsByName = createFunctionsMap();
|
||||||
|
|
||||||
|
|
||||||
|
private AnalysisToolPak() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public FreeRefFunction findFunction(String name) {
|
||||||
|
return _functionsByName.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, FreeRefFunction> createFunctionsMap() {
|
||||||
|
Map<String, FreeRefFunction> m = new HashMap<String, FreeRefFunction>(100);
|
||||||
|
|
||||||
|
r(m, "ACCRINT", null);
|
||||||
|
r(m, "ACCRINTM", null);
|
||||||
|
r(m, "AMORDEGRC", null);
|
||||||
|
r(m, "AMORLINC", null);
|
||||||
|
r(m, "BESSELI", null);
|
||||||
|
r(m, "BESSELJ", null);
|
||||||
|
r(m, "BESSELK", null);
|
||||||
|
r(m, "BESSELY", null);
|
||||||
|
r(m, "BIN2DEC", null);
|
||||||
|
r(m, "BIN2HEX", null);
|
||||||
|
r(m, "BIN2OCT", null);
|
||||||
|
r(m, "CO MPLEX", null);
|
||||||
|
r(m, "CONVERT", null);
|
||||||
|
r(m, "COUPDAYBS", null);
|
||||||
|
r(m, "COUPDAYS", null);
|
||||||
|
r(m, "COUPDAYSNC", null);
|
||||||
|
r(m, "COUPNCD", null);
|
||||||
|
r(m, "COUPNUM", null);
|
||||||
|
r(m, "COUPPCD", null);
|
||||||
|
r(m, "CUMIPMT", null);
|
||||||
|
r(m, "CUMPRINC", null);
|
||||||
|
r(m, "DEC2BIN", null);
|
||||||
|
r(m, "DEC2HEX", null);
|
||||||
|
r(m, "DEC2OCT", null);
|
||||||
|
r(m, "DELTA", null);
|
||||||
|
r(m, "DISC", null);
|
||||||
|
r(m, "DOLLARDE", null);
|
||||||
|
r(m, "DOLLARFR", null);
|
||||||
|
r(m, "DURATION", null);
|
||||||
|
r(m, "EDATE", null);
|
||||||
|
r(m, "EFFECT", null);
|
||||||
|
r(m, "EOMONTH", null);
|
||||||
|
r(m, "ERF", null);
|
||||||
|
r(m, "ERFC", null);
|
||||||
|
r(m, "FACTDOUBLE", null);
|
||||||
|
r(m, "FVSCHEDULE", null);
|
||||||
|
r(m, "GCD", null);
|
||||||
|
r(m, "GESTEP", null);
|
||||||
|
r(m, "HEX2BIN", null);
|
||||||
|
r(m, "HEX2DEC", null);
|
||||||
|
r(m, "HEX2OCT", null);
|
||||||
|
r(m, "IMABS", null);
|
||||||
|
r(m, "IMAGINARY", null);
|
||||||
|
r(m, "IMARGUMENT", null);
|
||||||
|
r(m, "IMCONJUGATE", null);
|
||||||
|
r(m, "IMCOS", null);
|
||||||
|
r(m, "IMDIV", null);
|
||||||
|
r(m, "IMEXP", null);
|
||||||
|
r(m, "IMLN", null);
|
||||||
|
r(m, "IMLOG10", null);
|
||||||
|
r(m, "IMLOG2", null);
|
||||||
|
r(m, "IMPOWER", null);
|
||||||
|
r(m, "IMPRODUCT", null);
|
||||||
|
r(m, "IMREAL", null);
|
||||||
|
r(m, "IMSIN", null);
|
||||||
|
r(m, "IMSQRT", null);
|
||||||
|
r(m, "IMSUB", null);
|
||||||
|
r(m, "IMSUM", null);
|
||||||
|
r(m, "INTRATE", null);
|
||||||
|
r(m, "ISEVEN", ParityFunction.IS_EVEN);
|
||||||
|
r(m, "ISODD", ParityFunction.IS_ODD);
|
||||||
|
r(m, "LCM", null);
|
||||||
|
r(m, "MDURATION", null);
|
||||||
|
r(m, "MROUND", null);
|
||||||
|
r(m, "MULTINOMIAL", null);
|
||||||
|
r(m, "NETWORKDAYS", null);
|
||||||
|
r(m, "NOMINAL", null);
|
||||||
|
r(m, "OCT2BIN", null);
|
||||||
|
r(m, "OCT2DEC", null);
|
||||||
|
r(m, "OCT2HEX", null);
|
||||||
|
r(m, "ODDFPRICE", null);
|
||||||
|
r(m, "ODDFYIELD", null);
|
||||||
|
r(m, "ODDLPRICE", null);
|
||||||
|
r(m, "ODDLYIELD", null);
|
||||||
|
r(m, "PRICE", null);
|
||||||
|
r(m, "PRICEDISC", null);
|
||||||
|
r(m, "PRICEMAT", null);
|
||||||
|
r(m, "QUOTIENT", null);
|
||||||
|
r(m, "RANDBETWEEN", RandBetween.instance);
|
||||||
|
r(m, "RECEIVED", null);
|
||||||
|
r(m, "SERIESSUM", null);
|
||||||
|
r(m, "SQRTPI", null);
|
||||||
|
r(m, "TBILLEQ", null);
|
||||||
|
r(m, "TBILLPRICE", null);
|
||||||
|
r(m, "TBILLYIELD", null);
|
||||||
|
r(m, "WEEKNUM", null);
|
||||||
|
r(m, "WORKDAY", null);
|
||||||
|
r(m, "XIRR", null);
|
||||||
|
r(m, "XNPV", null);
|
||||||
|
r(m, "YEARFRAC", YearFrac.instance);
|
||||||
|
r(m, "YIELD", null);
|
||||||
|
r(m, "YIELDDISC", null);
|
||||||
|
r(m, "YIELDMAT", null);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void r(Map<String, FreeRefFunction> m, String functionName, FreeRefFunction pFunc) {
|
||||||
|
FreeRefFunction func = pFunc == null ? new NotImplemented(functionName) : pFunc;
|
||||||
|
m.put(functionName, func);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
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.ValueEval;
|
||||||
|
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
/**
|
||||||
|
* Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
final class ParityFunction implements FreeRefFunction {
|
||||||
|
|
||||||
|
public static final FreeRefFunction IS_EVEN = new ParityFunction(0);
|
||||||
|
public static final FreeRefFunction IS_ODD = new ParityFunction(1);
|
||||||
|
private final int _desiredParity;
|
||||||
|
|
||||||
|
private ParityFunction(int desiredParity) {
|
||||||
|
_desiredParity = desiredParity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
|
||||||
|
if (args.length != 1) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
int val;
|
||||||
|
try {
|
||||||
|
val = evaluateArgParity(args[0], ec.getRowIndex(), ec.getColumnIndex());
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
|
||||||
|
return BoolEval.valueOf(val == _desiredParity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int evaluateArgParity(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short)srcCellCol);
|
||||||
|
|
||||||
|
double d = OperandResolver.coerceValueToDouble(ve);
|
||||||
|
if (d < 0) {
|
||||||
|
d = -d;
|
||||||
|
}
|
||||||
|
long v = (long) Math.floor(d);
|
||||||
|
return (int) (v & 0x0001);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
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.formula.functions.FreeRefFunction;
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Excel 'Analysis ToolPak' function RANDBETWEEN()<br/>
|
||||||
|
*
|
||||||
|
* Returns a random integer number between the numbers you specify.<p/>
|
||||||
|
*
|
||||||
|
* <b>Syntax</b><br/>
|
||||||
|
* <b>RANDBETWEEN</b>(<b>bottom</b>, <b>top</b>)<p/>
|
||||||
|
*
|
||||||
|
* <b>bottom</b> is the smallest integer RANDBETWEEN will return.<br/>
|
||||||
|
* <b>top</b> is the largest integer RANDBETWEEN will return.<br/>
|
||||||
|
|
||||||
|
* @author Brendan Nolan
|
||||||
|
*/
|
||||||
|
final class RandBetween implements FreeRefFunction{
|
||||||
|
|
||||||
|
public static final FreeRefFunction instance = new RandBetween();
|
||||||
|
|
||||||
|
private RandBetween() {
|
||||||
|
//enforces singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate for RANDBETWEEN(). Must be given two arguments. Bottom must be greater than top.
|
||||||
|
* Bottom is rounded up and top value is rounded down. After rounding top has to be set greater
|
||||||
|
* than top.
|
||||||
|
*
|
||||||
|
* @see org.apache.poi.ss.formula.functions.FreeRefFunction#evaluate(org.apache.poi.ss.formula.eval.ValueEval[], org.apache.poi.ss.formula.OperationEvaluationContext)
|
||||||
|
*/
|
||||||
|
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
|
||||||
|
|
||||||
|
double bottom, top;
|
||||||
|
|
||||||
|
if (args.length != 2) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bottom = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec.getColumnIndex()));
|
||||||
|
top = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[1], ec.getRowIndex(), ec.getColumnIndex()));
|
||||||
|
if(bottom > top) {
|
||||||
|
return ErrorEval.NUM_ERROR;
|
||||||
|
}
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom = Math.ceil(bottom);
|
||||||
|
top = Math.floor(top);
|
||||||
|
|
||||||
|
if(bottom > top) {
|
||||||
|
top = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NumberEval((bottom + (int)(Math.random() * ((top - bottom) + 1))));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import org.apache.poi.ss.formula.functions.FreeRefFunction;
|
||||||
|
import org.apache.poi.ss.formula.OperationEvaluationContext;
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
/**
|
||||||
|
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
|
||||||
|
*
|
||||||
|
* Returns the fraction of the year spanned by two dates.<p/>
|
||||||
|
*
|
||||||
|
* <b>Syntax</b><br/>
|
||||||
|
* <b>YEARFRAC</b>(<b>startDate</b>, <b>endDate</b>, basis)<p/>
|
||||||
|
*
|
||||||
|
* The <b>basis</b> optionally specifies the behaviour of YEARFRAC as follows:
|
||||||
|
*
|
||||||
|
* <table border="0" cellpadding="1" cellspacing="0" summary="basis parameter description">
|
||||||
|
* <tr><th>Value</th><th>Days per Month</th><th>Days per Year</th></tr>
|
||||||
|
* <tr align='center'><td>0 (default)</td><td>30</td><td>360</td></tr>
|
||||||
|
* <tr align='center'><td>1</td><td>actual</td><td>actual</td></tr>
|
||||||
|
* <tr align='center'><td>2</td><td>actual</td><td>360</td></tr>
|
||||||
|
* <tr align='center'><td>3</td><td>actual</td><td>365</td></tr>
|
||||||
|
* <tr align='center'><td>4</td><td>30</td><td>360</td></tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final class YearFrac implements FreeRefFunction {
|
||||||
|
|
||||||
|
public static final FreeRefFunction instance = new YearFrac();
|
||||||
|
|
||||||
|
private YearFrac() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
|
||||||
|
int srcCellRow = ec.getRowIndex();
|
||||||
|
int srcCellCol = ec.getColumnIndex();
|
||||||
|
double result;
|
||||||
|
try {
|
||||||
|
int basis = 0; // default
|
||||||
|
switch(args.length) {
|
||||||
|
case 3:
|
||||||
|
basis = evaluateIntArg(args[2], srcCellRow, srcCellCol);
|
||||||
|
case 2:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return ErrorEval.VALUE_INVALID;
|
||||||
|
}
|
||||||
|
double startDateVal = evaluateDateArg(args[0], srcCellRow, srcCellCol);
|
||||||
|
double endDateVal = evaluateDateArg(args[1], srcCellRow, srcCellCol);
|
||||||
|
result = YearFracCalculator.calculate(startDateVal, endDateVal, basis);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NumberEval(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double evaluateDateArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
|
||||||
|
|
||||||
|
if (ve instanceof StringEval) {
|
||||||
|
String strVal = ((StringEval) ve).getStringValue();
|
||||||
|
Double dVal = OperandResolver.parseDouble(strVal);
|
||||||
|
if (dVal != null) {
|
||||||
|
return dVal.doubleValue();
|
||||||
|
}
|
||||||
|
Calendar date = parseDate(strVal);
|
||||||
|
return DateUtil.getExcelDate(date, false);
|
||||||
|
}
|
||||||
|
return OperandResolver.coerceValueToDouble(ve);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Calendar parseDate(String strVal) throws EvaluationException {
|
||||||
|
String[] parts = Pattern.compile("/").split(strVal);
|
||||||
|
if (parts.length != 3) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
String part2 = parts[2];
|
||||||
|
int spacePos = part2.indexOf(' ');
|
||||||
|
if (spacePos > 0) {
|
||||||
|
// drop time portion if present
|
||||||
|
part2 = part2.substring(0, spacePos);
|
||||||
|
}
|
||||||
|
int f0;
|
||||||
|
int f1;
|
||||||
|
int f2;
|
||||||
|
try {
|
||||||
|
f0 = Integer.parseInt(parts[0]);
|
||||||
|
f1 = Integer.parseInt(parts[1]);
|
||||||
|
f2 = Integer.parseInt(part2);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
if (f0<0 || f1<0 || f2<0 || (f0>12 && f1>12 && f2>12)) {
|
||||||
|
// easy to see this cannot be a valid date
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f0 >= 1900 && f0 < 9999) {
|
||||||
|
// when 4 digit value appears first, the format is YYYY/MM/DD, regardless of OS settings
|
||||||
|
return makeDate(f0, f1, f2);
|
||||||
|
}
|
||||||
|
// otherwise the format seems to depend on OS settings (default date format)
|
||||||
|
if (false) {
|
||||||
|
// MM/DD/YYYY is probably a good guess, if the in the US
|
||||||
|
return makeDate(f2, f0, f1);
|
||||||
|
}
|
||||||
|
// TODO - find a way to choose the correct date format
|
||||||
|
throw new RuntimeException("Unable to determine date format for text '" + strVal + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param month 1-based
|
||||||
|
*/
|
||||||
|
private static Calendar makeDate(int year, int month, int day) throws EvaluationException {
|
||||||
|
if (month < 1 || month > 12) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
Calendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
|
||||||
|
cal.set(Calendar.MILLISECOND, 0);
|
||||||
|
if (day <1 || day>cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
cal.set(Calendar.DAY_OF_MONTH, day);
|
||||||
|
return cal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int evaluateIntArg(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, (short) srcCellCol);
|
||||||
|
return OperandResolver.coerceValueToInt(ve);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,344 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.atp;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
|
import org.apache.poi.ss.formula.eval.EvaluationException;
|
||||||
|
import org.apache.poi.ss.usermodel.DateUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal calculation methods for Excel 'Analysis ToolPak' function YEARFRAC()<br/>
|
||||||
|
*
|
||||||
|
* Algorithm inspired by www.dwheeler.com/yearfrac
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
final class YearFracCalculator {
|
||||||
|
/** use UTC time-zone to avoid daylight savings issues */
|
||||||
|
private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");
|
||||||
|
private static final int MS_PER_HOUR = 60 * 60 * 1000;
|
||||||
|
private static final int MS_PER_DAY = 24 * MS_PER_HOUR;
|
||||||
|
private static final int DAYS_PER_NORMAL_YEAR = 365;
|
||||||
|
private static final int DAYS_PER_LEAP_YEAR = DAYS_PER_NORMAL_YEAR + 1;
|
||||||
|
|
||||||
|
/** the length of normal long months i.e. 31 */
|
||||||
|
private static final int LONG_MONTH_LEN = 31;
|
||||||
|
/** the length of normal short months i.e. 30 */
|
||||||
|
private static final int SHORT_MONTH_LEN = 30;
|
||||||
|
private static final int SHORT_FEB_LEN = 28;
|
||||||
|
private static final int LONG_FEB_LEN = SHORT_FEB_LEN + 1;
|
||||||
|
|
||||||
|
private YearFracCalculator() {
|
||||||
|
// no instances of this class
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static double calculate(double pStartDateVal, double pEndDateVal, int basis) throws EvaluationException {
|
||||||
|
|
||||||
|
if (basis < 0 || basis >= 5) {
|
||||||
|
// if basis is invalid the result is #NUM!
|
||||||
|
throw new EvaluationException(ErrorEval.NUM_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// common logic for all bases
|
||||||
|
|
||||||
|
// truncate day values
|
||||||
|
int startDateVal = (int) Math.floor(pStartDateVal);
|
||||||
|
int endDateVal = (int) Math.floor(pEndDateVal);
|
||||||
|
if (startDateVal == endDateVal) {
|
||||||
|
// when dates are equal, result is zero
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// swap start and end if out of order
|
||||||
|
if (startDateVal > endDateVal) {
|
||||||
|
int temp = startDateVal;
|
||||||
|
startDateVal = endDateVal;
|
||||||
|
endDateVal = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (basis) {
|
||||||
|
case 0: return basis0(startDateVal, endDateVal);
|
||||||
|
case 1: return basis1(startDateVal, endDateVal);
|
||||||
|
case 2: return basis2(startDateVal, endDateVal);
|
||||||
|
case 3: return basis3(startDateVal, endDateVal);
|
||||||
|
case 4: return basis4(startDateVal, endDateVal);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("cannot happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param startDateVal assumed to be less than or equal to endDateVal
|
||||||
|
* @param endDateVal assumed to be greater than or equal to startDateVal
|
||||||
|
*/
|
||||||
|
public static double basis0(int startDateVal, int endDateVal) {
|
||||||
|
SimpleDate startDate = createDate(startDateVal);
|
||||||
|
SimpleDate endDate = createDate(endDateVal);
|
||||||
|
int date1day = startDate.day;
|
||||||
|
int date2day = endDate.day;
|
||||||
|
|
||||||
|
// basis zero has funny adjustments to the day-of-month fields when at end-of-month
|
||||||
|
if (date1day == LONG_MONTH_LEN && date2day == LONG_MONTH_LEN) {
|
||||||
|
date1day = SHORT_MONTH_LEN;
|
||||||
|
date2day = SHORT_MONTH_LEN;
|
||||||
|
} else if (date1day == LONG_MONTH_LEN) {
|
||||||
|
date1day = SHORT_MONTH_LEN;
|
||||||
|
} else if (date1day == SHORT_MONTH_LEN && date2day == LONG_MONTH_LEN) {
|
||||||
|
date2day = SHORT_MONTH_LEN;
|
||||||
|
// Note: If date2day==31, it STAYS 31 if date1day < 30.
|
||||||
|
// Special fixes for February:
|
||||||
|
} else if (startDate.month == 2 && isLastDayOfMonth(startDate)) {
|
||||||
|
// Note - these assignments deliberately set Feb 30 date.
|
||||||
|
date1day = SHORT_MONTH_LEN;
|
||||||
|
if (endDate.month == 2 && isLastDayOfMonth(endDate)) {
|
||||||
|
// only adjusted when first date is last day in Feb
|
||||||
|
date2day = SHORT_MONTH_LEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return calculateAdjusted(startDate, endDate, date1day, date2day);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param startDateVal assumed to be less than or equal to endDateVal
|
||||||
|
* @param endDateVal assumed to be greater than or equal to startDateVal
|
||||||
|
*/
|
||||||
|
public static double basis1(int startDateVal, int endDateVal) {
|
||||||
|
SimpleDate startDate = createDate(startDateVal);
|
||||||
|
SimpleDate endDate = createDate(endDateVal);
|
||||||
|
double yearLength;
|
||||||
|
if (isGreaterThanOneYear(startDate, endDate)) {
|
||||||
|
yearLength = averageYearLength(startDate.year, endDate.year);
|
||||||
|
} else if (shouldCountFeb29(startDate, endDate)) {
|
||||||
|
yearLength = DAYS_PER_LEAP_YEAR;
|
||||||
|
} else {
|
||||||
|
yearLength = DAYS_PER_NORMAL_YEAR;
|
||||||
|
}
|
||||||
|
return dateDiff(startDate.tsMilliseconds, endDate.tsMilliseconds) / yearLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param startDateVal assumed to be less than or equal to endDateVal
|
||||||
|
* @param endDateVal assumed to be greater than or equal to startDateVal
|
||||||
|
*/
|
||||||
|
public static double basis2(int startDateVal, int endDateVal) {
|
||||||
|
return (endDateVal - startDateVal) / 360.0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param startDateVal assumed to be less than or equal to endDateVal
|
||||||
|
* @param endDateVal assumed to be greater than or equal to startDateVal
|
||||||
|
*/
|
||||||
|
public static double basis3(double startDateVal, double endDateVal) {
|
||||||
|
return (endDateVal - startDateVal) / 365.0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param startDateVal assumed to be less than or equal to endDateVal
|
||||||
|
* @param endDateVal assumed to be greater than or equal to startDateVal
|
||||||
|
*/
|
||||||
|
public static double basis4(int startDateVal, int endDateVal) {
|
||||||
|
SimpleDate startDate = createDate(startDateVal);
|
||||||
|
SimpleDate endDate = createDate(endDateVal);
|
||||||
|
int date1day = startDate.day;
|
||||||
|
int date2day = endDate.day;
|
||||||
|
|
||||||
|
|
||||||
|
// basis four has funny adjustments to the day-of-month fields when at end-of-month
|
||||||
|
if (date1day == LONG_MONTH_LEN) {
|
||||||
|
date1day = SHORT_MONTH_LEN;
|
||||||
|
}
|
||||||
|
if (date2day == LONG_MONTH_LEN) {
|
||||||
|
date2day = SHORT_MONTH_LEN;
|
||||||
|
}
|
||||||
|
// Note - no adjustments for end of Feb
|
||||||
|
return calculateAdjusted(startDate, endDate, date1day, date2day);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static double calculateAdjusted(SimpleDate startDate, SimpleDate endDate, int date1day,
|
||||||
|
int date2day) {
|
||||||
|
double dayCount
|
||||||
|
= (endDate.year - startDate.year) * 360
|
||||||
|
+ (endDate.month - startDate.month) * SHORT_MONTH_LEN
|
||||||
|
+ (date2day - date1day) * 1;
|
||||||
|
return dayCount / 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isLastDayOfMonth(SimpleDate date) {
|
||||||
|
if (date.day < SHORT_FEB_LEN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return date.day == getLastDayOfMonth(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLastDayOfMonth(SimpleDate date) {
|
||||||
|
switch (date.month) {
|
||||||
|
case 1:
|
||||||
|
case 3:
|
||||||
|
case 5:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
case 10:
|
||||||
|
case 12:
|
||||||
|
return LONG_MONTH_LEN;
|
||||||
|
case 4:
|
||||||
|
case 6:
|
||||||
|
case 9:
|
||||||
|
case 11:
|
||||||
|
return SHORT_MONTH_LEN;
|
||||||
|
}
|
||||||
|
if (isLeapYear(date.year)) {
|
||||||
|
return LONG_FEB_LEN;
|
||||||
|
}
|
||||||
|
return SHORT_FEB_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assumes dates are no more than 1 year apart.
|
||||||
|
* @return <code>true</code> if dates both within a leap year, or span a period including Feb 29
|
||||||
|
*/
|
||||||
|
private static boolean shouldCountFeb29(SimpleDate start, SimpleDate end) {
|
||||||
|
boolean startIsLeapYear = isLeapYear(start.year);
|
||||||
|
if (startIsLeapYear && start.year == end.year) {
|
||||||
|
// note - dates may not actually span Feb-29, but it gets counted anyway in this case
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean endIsLeapYear = isLeapYear(end.year);
|
||||||
|
if (!startIsLeapYear && !endIsLeapYear) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (startIsLeapYear) {
|
||||||
|
switch (start.month) {
|
||||||
|
case SimpleDate.JANUARY:
|
||||||
|
case SimpleDate.FEBRUARY:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (endIsLeapYear) {
|
||||||
|
switch (end.month) {
|
||||||
|
case SimpleDate.JANUARY:
|
||||||
|
return false;
|
||||||
|
case SimpleDate.FEBRUARY:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return end.day == LONG_FEB_LEN;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the whole number of days between the two time-stamps. Both time-stamps are
|
||||||
|
* assumed to represent 12:00 midnight on the respective day.
|
||||||
|
*/
|
||||||
|
private static int dateDiff(long startDateMS, long endDateMS) {
|
||||||
|
long msDiff = endDateMS - startDateMS;
|
||||||
|
|
||||||
|
// some extra checks to make sure we don't hide some other bug with the rounding
|
||||||
|
int remainderHours = (int) ((msDiff % MS_PER_DAY) / MS_PER_HOUR);
|
||||||
|
switch (remainderHours) {
|
||||||
|
case 0: // normal case
|
||||||
|
break;
|
||||||
|
case 1: // transition from normal time to daylight savings adjusted
|
||||||
|
case 23: // transition from daylight savings adjusted to normal time
|
||||||
|
// Unexpected since we are using UTC_TIME_ZONE
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Unexpected date diff between " + startDateMS + " and " + endDateMS);
|
||||||
|
|
||||||
|
}
|
||||||
|
return (int) (0.5 + ((double)msDiff / MS_PER_DAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double averageYearLength(int startYear, int endYear) {
|
||||||
|
int dayCount = 0;
|
||||||
|
for (int i=startYear; i<=endYear; i++) {
|
||||||
|
dayCount += DAYS_PER_NORMAL_YEAR;
|
||||||
|
if (isLeapYear(i)) {
|
||||||
|
dayCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double numberOfYears = endYear-startYear+1;
|
||||||
|
return dayCount / numberOfYears;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isLeapYear(int i) {
|
||||||
|
// leap years are always divisible by 4
|
||||||
|
if (i % 4 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// each 4th century is a leap year
|
||||||
|
if (i % 400 == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// all other centuries are *not* leap years
|
||||||
|
if (i % 100 == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isGreaterThanOneYear(SimpleDate start, SimpleDate end) {
|
||||||
|
if (start.year == end.year) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (start.year + 1 != end.year) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start.month > end.month) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (start.month < end.month) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start.day < end.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimpleDate createDate(int dayCount) {
|
||||||
|
GregorianCalendar calendar = new GregorianCalendar(UTC_TIME_ZONE);
|
||||||
|
DateUtil.setCalendar(calendar, dayCount, 0, false);
|
||||||
|
return new SimpleDate(calendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SimpleDate {
|
||||||
|
|
||||||
|
public static final int JANUARY = 1;
|
||||||
|
public static final int FEBRUARY = 2;
|
||||||
|
|
||||||
|
public final int year;
|
||||||
|
/** 1-based month */
|
||||||
|
public final int month;
|
||||||
|
/** day of month */
|
||||||
|
public final int day;
|
||||||
|
/** milliseconds since 1970 */
|
||||||
|
public long tsMilliseconds;
|
||||||
|
|
||||||
|
public SimpleDate(Calendar cal) {
|
||||||
|
year = cal.get(Calendar.YEAR);
|
||||||
|
month = cal.get(Calendar.MONTH) + 1;
|
||||||
|
day = cal.get(Calendar.DAY_OF_MONTH);
|
||||||
|
tsMilliseconds = cal.getTimeInMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.TwoDEval;
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface AreaEval extends TwoDEval {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the 0-based index of the first row in
|
||||||
|
* this area.
|
||||||
|
*/
|
||||||
|
int getFirstRow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the 0-based index of the last row in
|
||||||
|
* this area.
|
||||||
|
*/
|
||||||
|
int getLastRow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the 0-based index of the first col in
|
||||||
|
* this area.
|
||||||
|
*/
|
||||||
|
int getFirstColumn();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the 0-based index of the last col in
|
||||||
|
* this area.
|
||||||
|
*/
|
||||||
|
int getLastColumn();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ValueEval from within this area at the specified row and col index. Never
|
||||||
|
* <code>null</code> (possibly {@link BlankEval}). The specified indexes should be absolute
|
||||||
|
* indexes in the sheet and not relative indexes within the area.
|
||||||
|
*/
|
||||||
|
ValueEval getAbsoluteValue(int row, int col);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if the cell at row and col specified
|
||||||
|
* as absolute indexes in the sheet is contained in
|
||||||
|
* this area.
|
||||||
|
* @param row
|
||||||
|
* @param col
|
||||||
|
*/
|
||||||
|
boolean contains(int row, int col);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if the specified col is in range
|
||||||
|
* @param col
|
||||||
|
*/
|
||||||
|
boolean containsColumn(int col);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if the specified row is in range
|
||||||
|
* @param row
|
||||||
|
*/
|
||||||
|
boolean containsRow(int row);
|
||||||
|
|
||||||
|
int getWidth();
|
||||||
|
int getHeight();
|
||||||
|
/**
|
||||||
|
* @return the ValueEval from within this area at the specified relativeRowIndex and
|
||||||
|
* relativeColumnIndex. Never <code>null</code> (possibly {@link BlankEval}). The
|
||||||
|
* specified indexes should relative to the top left corner of this area.
|
||||||
|
*/
|
||||||
|
ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link AreaEval} offset by a relative amount from from the upper left cell
|
||||||
|
* of this area
|
||||||
|
*/
|
||||||
|
AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx);
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.formula.AreaI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public abstract class AreaEvalBase implements AreaEval {
|
||||||
|
|
||||||
|
private final int _firstColumn;
|
||||||
|
private final int _firstRow;
|
||||||
|
private final int _lastColumn;
|
||||||
|
private final int _lastRow;
|
||||||
|
private final int _nColumns;
|
||||||
|
private final int _nRows;
|
||||||
|
|
||||||
|
protected AreaEvalBase(int firstRow, int firstColumn, int lastRow, int lastColumn) {
|
||||||
|
_firstColumn = firstColumn;
|
||||||
|
_firstRow = firstRow;
|
||||||
|
_lastColumn = lastColumn;
|
||||||
|
_lastRow = lastRow;
|
||||||
|
|
||||||
|
_nColumns = _lastColumn - _firstColumn + 1;
|
||||||
|
_nRows = _lastRow - _firstRow + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AreaEvalBase(AreaI ptg) {
|
||||||
|
_firstRow = ptg.getFirstRow();
|
||||||
|
_firstColumn = ptg.getFirstColumn();
|
||||||
|
_lastRow = ptg.getLastRow();
|
||||||
|
_lastColumn = ptg.getLastColumn();
|
||||||
|
|
||||||
|
_nColumns = _lastColumn - _firstColumn + 1;
|
||||||
|
_nRows = _lastRow - _firstRow + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getFirstColumn() {
|
||||||
|
return _firstColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getFirstRow() {
|
||||||
|
return _firstRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getLastColumn() {
|
||||||
|
return _lastColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getLastRow() {
|
||||||
|
return _lastRow;
|
||||||
|
}
|
||||||
|
public final ValueEval getAbsoluteValue(int row, int col) {
|
||||||
|
int rowOffsetIx = row - _firstRow;
|
||||||
|
int colOffsetIx = col - _firstColumn;
|
||||||
|
|
||||||
|
if(rowOffsetIx < 0 || rowOffsetIx >= _nRows) {
|
||||||
|
throw new IllegalArgumentException("Specified row index (" + row
|
||||||
|
+ ") is outside the allowed range (" + _firstRow + ".." + _lastRow + ")");
|
||||||
|
}
|
||||||
|
if(colOffsetIx < 0 || colOffsetIx >= _nColumns) {
|
||||||
|
throw new IllegalArgumentException("Specified column index (" + col
|
||||||
|
+ ") is outside the allowed range (" + _firstColumn + ".." + col + ")");
|
||||||
|
}
|
||||||
|
return getRelativeValue(rowOffsetIx, colOffsetIx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean contains(int row, int col) {
|
||||||
|
return _firstRow <= row && _lastRow >= row
|
||||||
|
&& _firstColumn <= col && _lastColumn >= col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean containsRow(int row) {
|
||||||
|
return _firstRow <= row && _lastRow >= row;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean containsColumn(int col) {
|
||||||
|
return _firstColumn <= col && _lastColumn >= col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isColumn() {
|
||||||
|
return _firstColumn == _lastColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isRow() {
|
||||||
|
return _firstRow == _lastRow;
|
||||||
|
}
|
||||||
|
public int getHeight() {
|
||||||
|
return _lastRow-_firstRow+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ValueEval getValue(int row, int col) {
|
||||||
|
return getRelativeValue(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ValueEval getRelativeValue(int relativeRowIndex, int relativeColumnIndex);
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return _lastColumn-_firstColumn+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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com > This class is a
|
||||||
|
* marker class. It is a special value for empty cells.
|
||||||
|
*/
|
||||||
|
public final class BlankEval implements ValueEval {
|
||||||
|
|
||||||
|
public static final BlankEval instance = new BlankEval();
|
||||||
|
/**
|
||||||
|
* @deprecated (Nov 2009) use {@link #instance}
|
||||||
|
*/
|
||||||
|
public static final BlankEval INSTANCE = instance;
|
||||||
|
|
||||||
|
private BlankEval() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public final class BoolEval implements NumericValueEval, StringValueEval {
|
||||||
|
|
||||||
|
private boolean _value;
|
||||||
|
|
||||||
|
public static final BoolEval FALSE = new BoolEval(false);
|
||||||
|
|
||||||
|
public static final BoolEval TRUE = new BoolEval(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for the following:<br/>
|
||||||
|
* <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
|
||||||
|
*
|
||||||
|
* @return the <tt>BoolEval</tt> instance representing <tt>b</tt>.
|
||||||
|
*/
|
||||||
|
public static final BoolEval valueOf(boolean b) {
|
||||||
|
return b ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BoolEval(boolean value) {
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBooleanValue() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getNumberValue() {
|
||||||
|
return _value ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringValue() {
|
||||||
|
return _value ? "TRUE" : "FALSE";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(getStringValue());
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public final class ConcatEval extends Fixed2ArgFunction {
|
||||||
|
|
||||||
|
public static final Function instance = new ConcatEval();
|
||||||
|
|
||||||
|
private ConcatEval() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
|
||||||
|
ValueEval ve0;
|
||||||
|
ValueEval ve1;
|
||||||
|
try {
|
||||||
|
ve0 = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
|
||||||
|
ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getText(ve0));
|
||||||
|
sb.append(getText(ve1));
|
||||||
|
return new StringEval(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getText(ValueEval ve) {
|
||||||
|
if (ve instanceof StringValueEval) {
|
||||||
|
StringValueEval sve = (StringValueEval) ve;
|
||||||
|
return sve.getStringValue();
|
||||||
|
}
|
||||||
|
if (ve == BlankEval.instance) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
throw new IllegalAccessError("Unexpected value type ("
|
||||||
|
+ ve.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* 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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.usermodel.ErrorConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class ErrorEval implements ValueEval {
|
||||||
|
|
||||||
|
// convenient access to namespace
|
||||||
|
private static final ErrorConstants EC = null;
|
||||||
|
|
||||||
|
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
|
||||||
|
public static final ErrorEval NULL_INTERSECTION = new ErrorEval(EC.ERROR_NULL);
|
||||||
|
/** <b>#DIV/0!</b> - Division by zero */
|
||||||
|
public static final ErrorEval DIV_ZERO = new ErrorEval(EC.ERROR_DIV_0);
|
||||||
|
/** <b>#VALUE!</b> - Wrong type of operand */
|
||||||
|
public static final ErrorEval VALUE_INVALID = new ErrorEval(EC.ERROR_VALUE);
|
||||||
|
/** <b>#REF!</b> - Illegal or deleted cell reference */
|
||||||
|
public static final ErrorEval REF_INVALID = new ErrorEval(EC.ERROR_REF);
|
||||||
|
/** <b>#NAME?</b> - Wrong function or range name */
|
||||||
|
public static final ErrorEval NAME_INVALID = new ErrorEval(EC.ERROR_NAME);
|
||||||
|
/** <b>#NUM!</b> - Value range overflow */
|
||||||
|
public static final ErrorEval NUM_ERROR = new ErrorEval(EC.ERROR_NUM);
|
||||||
|
/** <b>#N/A</b> - Argument or function not available */
|
||||||
|
public static final ErrorEval NA = new ErrorEval(EC.ERROR_NA);
|
||||||
|
|
||||||
|
|
||||||
|
// POI internal error codes
|
||||||
|
private static final int CIRCULAR_REF_ERROR_CODE = 0xFFFFFFC4;
|
||||||
|
private static final int FUNCTION_NOT_IMPLEMENTED_CODE = 0xFFFFFFE2;
|
||||||
|
|
||||||
|
// Note - Excel does not seem to represent this condition with an error code
|
||||||
|
public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(CIRCULAR_REF_ERROR_CODE);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates an Excel internal error code into the corresponding POI ErrorEval instance
|
||||||
|
* @param errorCode
|
||||||
|
*/
|
||||||
|
public static ErrorEval valueOf(int errorCode) {
|
||||||
|
switch(errorCode) {
|
||||||
|
case ErrorConstants.ERROR_NULL: return NULL_INTERSECTION;
|
||||||
|
case ErrorConstants.ERROR_DIV_0: return DIV_ZERO;
|
||||||
|
case ErrorConstants.ERROR_VALUE: return VALUE_INVALID;
|
||||||
|
case ErrorConstants.ERROR_REF: return REF_INVALID;
|
||||||
|
case ErrorConstants.ERROR_NAME: return NAME_INVALID;
|
||||||
|
case ErrorConstants.ERROR_NUM: return NUM_ERROR;
|
||||||
|
case ErrorConstants.ERROR_NA: return NA;
|
||||||
|
// non-std errors (conditions modeled as errors by POI)
|
||||||
|
case CIRCULAR_REF_ERROR_CODE: return CIRCULAR_REF_ERROR;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Unexpected error code (" + errorCode + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts error codes to text. Handles non-standard error codes OK.
|
||||||
|
* For debug/test purposes (and for formatting error messages).
|
||||||
|
* @return the String representation of the specified Excel error code.
|
||||||
|
*/
|
||||||
|
public static String getText(int errorCode) {
|
||||||
|
if(ErrorConstants.isValidCode(errorCode)) {
|
||||||
|
return ErrorConstants.getText(errorCode);
|
||||||
|
}
|
||||||
|
// It is desirable to make these (arbitrary) strings look clearly different from any other
|
||||||
|
// value expression that might appear in a formula. In addition these error strings should
|
||||||
|
// look unlike the standard Excel errors. Hence tilde ('~') was used.
|
||||||
|
switch(errorCode) {
|
||||||
|
case CIRCULAR_REF_ERROR_CODE: return "~CIRCULAR~REF~";
|
||||||
|
case FUNCTION_NOT_IMPLEMENTED_CODE: return "~FUNCTION~NOT~IMPLEMENTED~";
|
||||||
|
}
|
||||||
|
return "~non~std~err(" + errorCode + ")~";
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _errorCode;
|
||||||
|
/**
|
||||||
|
* @param errorCode an 8-bit value
|
||||||
|
*/
|
||||||
|
private ErrorEval(int errorCode) {
|
||||||
|
_errorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorCode() {
|
||||||
|
return _errorCode;
|
||||||
|
}
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(getText(_errorCode));
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to simplify error handling logic <i>within</i> operator and function
|
||||||
|
* implementations. Note - <tt>OperationEval.evaluate()</tt> and <tt>Function.evaluate()</tt>
|
||||||
|
* method signatures do not throw this exception so it cannot propagate outside.<p/>
|
||||||
|
*
|
||||||
|
* Here is an example coded without <tt>EvaluationException</tt>, to show how it can help:
|
||||||
|
* <pre>
|
||||||
|
* public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
|
||||||
|
* // ...
|
||||||
|
* Eval arg0 = args[0];
|
||||||
|
* if(arg0 instanceof ErrorEval) {
|
||||||
|
* return arg0;
|
||||||
|
* }
|
||||||
|
* if(!(arg0 instanceof AreaEval)) {
|
||||||
|
* return ErrorEval.VALUE_INVALID;
|
||||||
|
* }
|
||||||
|
* double temp = 0;
|
||||||
|
* AreaEval area = (AreaEval)arg0;
|
||||||
|
* ValueEval[] values = area.getValues();
|
||||||
|
* for (int i = 0; i < values.length; i++) {
|
||||||
|
* ValueEval ve = values[i];
|
||||||
|
* if(ve instanceof ErrorEval) {
|
||||||
|
* return ve;
|
||||||
|
* }
|
||||||
|
* if(!(ve instanceof NumericValueEval)) {
|
||||||
|
* return ErrorEval.VALUE_INVALID;
|
||||||
|
* }
|
||||||
|
* temp += ((NumericValueEval)ve).getNumberValue();
|
||||||
|
* }
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* In this example, if any error is encountered while processing the arguments, an error is
|
||||||
|
* returned immediately. This code is difficult to refactor due to all the points where errors
|
||||||
|
* are returned.<br/>
|
||||||
|
* Using <tt>EvaluationException</tt> allows the error returning code to be consolidated to one
|
||||||
|
* place.<p/>
|
||||||
|
* <pre>
|
||||||
|
* public Eval evaluate(Eval[] args, int srcRow, short srcCol) {
|
||||||
|
* try {
|
||||||
|
* // ...
|
||||||
|
* AreaEval area = getAreaArg(args[0]);
|
||||||
|
* double temp = sumValues(area.getValues());
|
||||||
|
* // ...
|
||||||
|
* } catch (EvaluationException e) {
|
||||||
|
* return e.getErrorEval();
|
||||||
|
* }
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
*private static AreaEval getAreaArg(Eval arg0) throws EvaluationException {
|
||||||
|
* if (arg0 instanceof ErrorEval) {
|
||||||
|
* throw new EvaluationException((ErrorEval) arg0);
|
||||||
|
* }
|
||||||
|
* if (arg0 instanceof AreaEval) {
|
||||||
|
* return (AreaEval) arg0;
|
||||||
|
* }
|
||||||
|
* throw EvaluationException.invalidValue();
|
||||||
|
*}
|
||||||
|
*
|
||||||
|
*private double sumValues(ValueEval[] values) throws EvaluationException {
|
||||||
|
* double temp = 0;
|
||||||
|
* for (int i = 0; i < values.length; i++) {
|
||||||
|
* ValueEval ve = values[i];
|
||||||
|
* if (ve instanceof ErrorEval) {
|
||||||
|
* throw new EvaluationException((ErrorEval) ve);
|
||||||
|
* }
|
||||||
|
* if (!(ve instanceof NumericValueEval)) {
|
||||||
|
* throw EvaluationException.invalidValue();
|
||||||
|
* }
|
||||||
|
* temp += ((NumericValueEval) ve).getNumberValue();
|
||||||
|
* }
|
||||||
|
* return temp;
|
||||||
|
*}
|
||||||
|
* </pre>
|
||||||
|
* It is not mandatory to use EvaluationException, doing so might give the following advantages:<br/>
|
||||||
|
* - Methods can more easily be extracted, allowing for re-use.<br/>
|
||||||
|
* - Type management (typecasting etc) is simpler because error conditions have been separated from
|
||||||
|
* intermediate calculation values.<br/>
|
||||||
|
* - Fewer local variables are required. Local variables can have stronger types.<br/>
|
||||||
|
* - It is easier to mimic common Excel error handling behaviour (exit upon encountering first
|
||||||
|
* error), because exceptions conveniently propagate up the call stack regardless of execution
|
||||||
|
* points or the number of levels of nested calls.<p/>
|
||||||
|
*
|
||||||
|
* <b>Note</b> - Only standard evaluation errors are represented by <tt>EvaluationException</tt> (
|
||||||
|
* i.e. conditions expected to be encountered when evaluating arbitrary Excel formulas). Conditions
|
||||||
|
* that could never occur in an Excel spreadsheet should result in runtime exceptions. Care should
|
||||||
|
* be taken to not translate any POI internal error into an Excel evaluation error code.
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class EvaluationException extends Exception {
|
||||||
|
private final ErrorEval _errorEval;
|
||||||
|
|
||||||
|
public EvaluationException(ErrorEval errorEval) {
|
||||||
|
_errorEval = errorEval;
|
||||||
|
}
|
||||||
|
// some convenience factory methods
|
||||||
|
|
||||||
|
/** <b>#VALUE!</b> - Wrong type of operand */
|
||||||
|
public static EvaluationException invalidValue() {
|
||||||
|
return new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
/** <b>#REF!</b> - Illegal or deleted cell reference */
|
||||||
|
public static EvaluationException invalidRef() {
|
||||||
|
return new EvaluationException(ErrorEval.REF_INVALID);
|
||||||
|
}
|
||||||
|
/** <b>#NUM!</b> - Value range overflow */
|
||||||
|
public static EvaluationException numberError() {
|
||||||
|
return new EvaluationException(ErrorEval.NUM_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorEval getErrorEval() {
|
||||||
|
return _errorEval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,249 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.function.FunctionMetadata;
|
||||||
|
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
|
||||||
|
import org.apache.poi.ss.formula.functions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public final class FunctionEval {
|
||||||
|
/**
|
||||||
|
* Some function IDs that require special treatment
|
||||||
|
*/
|
||||||
|
private static final class FunctionID {
|
||||||
|
/** 1 */
|
||||||
|
public static final int IF = FunctionMetadataRegistry.FUNCTION_INDEX_IF;
|
||||||
|
/** 4 */
|
||||||
|
public static final int SUM = FunctionMetadataRegistry.FUNCTION_INDEX_SUM;
|
||||||
|
/** 78 */
|
||||||
|
public static final int OFFSET = 78;
|
||||||
|
/** 100 */
|
||||||
|
public static final int CHOOSE = FunctionMetadataRegistry.FUNCTION_INDEX_CHOOSE;
|
||||||
|
/** 148 */
|
||||||
|
public static final int INDIRECT = FunctionMetadataRegistry.FUNCTION_INDEX_INDIRECT;
|
||||||
|
/** 255 */
|
||||||
|
public static final int EXTERNAL_FUNC = FunctionMetadataRegistry.FUNCTION_INDEX_EXTERNAL;
|
||||||
|
}
|
||||||
|
// convenient access to namespace
|
||||||
|
private static final FunctionID ID = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array elements corresponding to unimplemented functions are <code>null</code>
|
||||||
|
*/
|
||||||
|
protected static final Function[] functions = produceFunctions();
|
||||||
|
|
||||||
|
private static Function[] produceFunctions() {
|
||||||
|
Function[] retval = new Function[368];
|
||||||
|
|
||||||
|
retval[0] = new Count();
|
||||||
|
retval[ID.IF] = new IfFunc();
|
||||||
|
retval[2] = LogicalFunction.ISNA;
|
||||||
|
retval[3] = LogicalFunction.ISERROR;
|
||||||
|
retval[ID.SUM] = AggregateFunction.SUM;
|
||||||
|
retval[5] = AggregateFunction.AVERAGE;
|
||||||
|
retval[6] = AggregateFunction.MIN;
|
||||||
|
retval[7] = AggregateFunction.MAX;
|
||||||
|
retval[8] = new RowFunc(); // ROW
|
||||||
|
retval[9] = new Column();
|
||||||
|
retval[10] = new Na();
|
||||||
|
retval[11] = new Npv();
|
||||||
|
retval[12] = AggregateFunction.STDEV;
|
||||||
|
retval[13] = NumericFunction.DOLLAR;
|
||||||
|
|
||||||
|
retval[15] = NumericFunction.SIN;
|
||||||
|
retval[16] = NumericFunction.COS;
|
||||||
|
retval[17] = NumericFunction.TAN;
|
||||||
|
retval[18] = NumericFunction.ATAN;
|
||||||
|
retval[19] = NumericFunction.PI;
|
||||||
|
retval[20] = NumericFunction.SQRT;
|
||||||
|
retval[21] = NumericFunction.EXP;
|
||||||
|
retval[22] = NumericFunction.LN;
|
||||||
|
retval[23] = NumericFunction.LOG10;
|
||||||
|
retval[24] = NumericFunction.ABS;
|
||||||
|
retval[25] = NumericFunction.INT;
|
||||||
|
retval[26] = NumericFunction.SIGN;
|
||||||
|
retval[27] = NumericFunction.ROUND;
|
||||||
|
retval[28] = new Lookup();
|
||||||
|
retval[29] = new Index();
|
||||||
|
|
||||||
|
retval[31] = TextFunction.MID;
|
||||||
|
retval[32] = TextFunction.LEN;
|
||||||
|
retval[33] = new Value();
|
||||||
|
retval[34] = BooleanFunction.TRUE;
|
||||||
|
retval[35] = BooleanFunction.FALSE;
|
||||||
|
retval[36] = BooleanFunction.AND;
|
||||||
|
retval[37] = BooleanFunction.OR;
|
||||||
|
retval[38] = BooleanFunction.NOT;
|
||||||
|
retval[39] = NumericFunction.MOD;
|
||||||
|
retval[48] = TextFunction.TEXT;
|
||||||
|
|
||||||
|
retval[56] = FinanceFunction.PV;
|
||||||
|
retval[57] = FinanceFunction.FV;
|
||||||
|
retval[58] = FinanceFunction.NPER;
|
||||||
|
retval[59] = FinanceFunction.PMT;
|
||||||
|
|
||||||
|
retval[63] = NumericFunction.RAND;
|
||||||
|
retval[64] = new Match();
|
||||||
|
retval[65] = DateFunc.instance;
|
||||||
|
retval[66] = new TimeFunc();
|
||||||
|
retval[67] = CalendarFieldFunction.DAY;
|
||||||
|
retval[68] = CalendarFieldFunction.MONTH;
|
||||||
|
retval[69] = CalendarFieldFunction.YEAR;
|
||||||
|
|
||||||
|
retval[74] = new Now();
|
||||||
|
|
||||||
|
retval[76] = new Rows();
|
||||||
|
retval[77] = new Columns();
|
||||||
|
retval[82] = TextFunction.SEARCH;
|
||||||
|
retval[ID.OFFSET] = new Offset();
|
||||||
|
retval[82] = TextFunction.SEARCH;
|
||||||
|
|
||||||
|
retval[97] = NumericFunction.ATAN2;
|
||||||
|
retval[98] = NumericFunction.ASIN;
|
||||||
|
retval[99] = NumericFunction.ACOS;
|
||||||
|
retval[ID.CHOOSE] = new Choose();
|
||||||
|
retval[101] = new Hlookup();
|
||||||
|
retval[102] = new Vlookup();
|
||||||
|
|
||||||
|
retval[105] = LogicalFunction.ISREF;
|
||||||
|
|
||||||
|
retval[109] = NumericFunction.LOG;
|
||||||
|
|
||||||
|
retval[112] = TextFunction.LOWER;
|
||||||
|
retval[113] = TextFunction.UPPER;
|
||||||
|
|
||||||
|
retval[115] = TextFunction.LEFT;
|
||||||
|
retval[116] = TextFunction.RIGHT;
|
||||||
|
retval[117] = TextFunction.EXACT;
|
||||||
|
retval[118] = TextFunction.TRIM;
|
||||||
|
retval[119] = new Replace();
|
||||||
|
retval[120] = new Substitute();
|
||||||
|
|
||||||
|
retval[124] = TextFunction.FIND;
|
||||||
|
|
||||||
|
retval[127] = LogicalFunction.ISTEXT;
|
||||||
|
retval[128] = LogicalFunction.ISNUMBER;
|
||||||
|
retval[129] = LogicalFunction.ISBLANK;
|
||||||
|
retval[130] = new T();
|
||||||
|
|
||||||
|
retval[ID.INDIRECT] = null; // Indirect.evaluate has different signature
|
||||||
|
|
||||||
|
retval[169] = new Counta();
|
||||||
|
|
||||||
|
retval[183] = AggregateFunction.PRODUCT;
|
||||||
|
retval[184] = NumericFunction.FACT;
|
||||||
|
|
||||||
|
retval[190] = LogicalFunction.ISNONTEXT;
|
||||||
|
retval[197] = NumericFunction.TRUNC;
|
||||||
|
retval[198] = LogicalFunction.ISLOGICAL;
|
||||||
|
|
||||||
|
retval[212] = NumericFunction.ROUNDUP;
|
||||||
|
retval[213] = NumericFunction.ROUNDDOWN;
|
||||||
|
|
||||||
|
retval[220] = new Days360();
|
||||||
|
retval[221] = new Today();
|
||||||
|
|
||||||
|
retval[227] = AggregateFunction.MEDIAN;
|
||||||
|
retval[228] = new Sumproduct();
|
||||||
|
retval[229] = NumericFunction.SINH;
|
||||||
|
retval[230] = NumericFunction.COSH;
|
||||||
|
retval[231] = NumericFunction.TANH;
|
||||||
|
retval[232] = NumericFunction.ASINH;
|
||||||
|
retval[233] = NumericFunction.ACOSH;
|
||||||
|
retval[234] = NumericFunction.ATANH;
|
||||||
|
|
||||||
|
retval[ID.EXTERNAL_FUNC] = null; // ExternalFunction is a FreeREfFunction
|
||||||
|
|
||||||
|
retval[261] = new Errortype();
|
||||||
|
|
||||||
|
retval[269] = AggregateFunction.AVEDEV;
|
||||||
|
|
||||||
|
retval[276] = NumericFunction.COMBIN;
|
||||||
|
|
||||||
|
retval[279] = new Even();
|
||||||
|
|
||||||
|
retval[285] = NumericFunction.FLOOR;
|
||||||
|
|
||||||
|
retval[288] = NumericFunction.CEILING;
|
||||||
|
|
||||||
|
retval[298] = new Odd();
|
||||||
|
|
||||||
|
retval[300] = NumericFunction.POISSON;
|
||||||
|
|
||||||
|
retval[303] = new Sumxmy2();
|
||||||
|
retval[304] = new Sumx2my2();
|
||||||
|
retval[305] = new Sumx2py2();
|
||||||
|
|
||||||
|
retval[318] = AggregateFunction.DEVSQ;
|
||||||
|
|
||||||
|
retval[321] = AggregateFunction.SUMSQ;
|
||||||
|
|
||||||
|
retval[325] = AggregateFunction.LARGE;
|
||||||
|
retval[326] = AggregateFunction.SMALL;
|
||||||
|
|
||||||
|
retval[330] = new Mode();
|
||||||
|
|
||||||
|
retval[336] = TextFunction.CONCATENATE;
|
||||||
|
retval[337] = NumericFunction.POWER;
|
||||||
|
|
||||||
|
retval[342] = NumericFunction.RADIANS;
|
||||||
|
retval[343] = NumericFunction.DEGREES;
|
||||||
|
|
||||||
|
retval[344] = new Subtotal();
|
||||||
|
retval[345] = new Sumif();
|
||||||
|
retval[346] = new Countif();
|
||||||
|
retval[347] = new Countblank();
|
||||||
|
|
||||||
|
retval[359] = new Hyperlink();
|
||||||
|
|
||||||
|
retval[362] = MinaMaxa.MAXA;
|
||||||
|
retval[363] = MinaMaxa.MINA;
|
||||||
|
|
||||||
|
for (int i = 0; i < retval.length; i++) {
|
||||||
|
Function f = retval[i];
|
||||||
|
if (f == null) {
|
||||||
|
FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByIndex(i);
|
||||||
|
if (fm == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
retval[i] = new NotImplementedFunction(fm.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return <code>null</code> if the specified functionIndex is for INDIRECT() or any external (add-in) function.
|
||||||
|
*/
|
||||||
|
public static Function getBasicFunction(int functionIndex) {
|
||||||
|
// check for 'free ref' functions first
|
||||||
|
switch (functionIndex) {
|
||||||
|
case FunctionID.INDIRECT:
|
||||||
|
case FunctionID.EXTERNAL_FUNC:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// else - must be plain function
|
||||||
|
Function result = functions[functionIndex];
|
||||||
|
if (result == null) {
|
||||||
|
throw new NotImplementedException("FuncIx=" + functionIndex);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class IntersectionEval extends Fixed2ArgFunction {
|
||||||
|
|
||||||
|
public static final Function instance = new IntersectionEval();
|
||||||
|
|
||||||
|
private IntersectionEval() {
|
||||||
|
// enforces singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
AreaEval reA = evaluateRef(arg0);
|
||||||
|
AreaEval reB = evaluateRef(arg1);
|
||||||
|
AreaEval result = resolveRange(reA, reB);
|
||||||
|
if (result == null) {
|
||||||
|
return ErrorEval.NULL_INTERSECTION;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return simple rectangular {@link AreaEval} which represents the intersection of areas
|
||||||
|
* <tt>aeA</tt> and <tt>aeB</tt>. If the two areas do not intersect, the result is <code>null</code>.
|
||||||
|
*/
|
||||||
|
private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
|
||||||
|
|
||||||
|
int aeAfr = aeA.getFirstRow();
|
||||||
|
int aeAfc = aeA.getFirstColumn();
|
||||||
|
int aeBlc = aeB.getLastColumn();
|
||||||
|
if (aeAfc > aeBlc) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int aeBfc = aeB.getFirstColumn();
|
||||||
|
if (aeBfc > aeA.getLastColumn()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int aeBlr = aeB.getLastRow();
|
||||||
|
if (aeAfr > aeBlr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int aeBfr = aeB.getFirstRow();
|
||||||
|
int aeAlr = aeA.getLastRow();
|
||||||
|
if (aeBfr > aeAlr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int top = Math.max(aeAfr, aeBfr);
|
||||||
|
int bottom = Math.min(aeAlr, aeBlr);
|
||||||
|
int left = Math.max(aeAfc, aeBfc);
|
||||||
|
int right = Math.min(aeA.getLastColumn(), aeBlc);
|
||||||
|
|
||||||
|
return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
|
||||||
|
if (arg instanceof AreaEval) {
|
||||||
|
return (AreaEval) arg;
|
||||||
|
}
|
||||||
|
if (arg instanceof RefEval) {
|
||||||
|
return ((RefEval) arg).offset(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
if (arg instanceof ErrorEval) {
|
||||||
|
throw new EvaluationException((ErrorEval)arg);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.ss.formula.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the (intermediate) evaluated result of a missing function argument. In most cases
|
||||||
|
* this can be translated into {@link BlankEval} but there are some notable exceptions. Functions
|
||||||
|
* COUNT and COUNTA <em>do</em> count their missing args. Note - the differences between
|
||||||
|
* {@link MissingArgEval} and {@link BlankEval} have not been investigated fully, so the POI
|
||||||
|
* evaluator may need to be updated to account for these as they are found.
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class MissingArgEval implements ValueEval {
|
||||||
|
|
||||||
|
public static final MissingArgEval instance = new MissingArgEval();
|
||||||
|
|
||||||
|
private MissingArgEval() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class NameEval implements ValueEval {
|
||||||
|
|
||||||
|
private final String _functionName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a NameEval representing a function name
|
||||||
|
*/
|
||||||
|
public NameEval(String functionName) {
|
||||||
|
_functionName = functionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getFunctionName() {
|
||||||
|
return _functionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(_functionName);
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class NameXEval implements ValueEval {
|
||||||
|
|
||||||
|
private final NameXPtg _ptg;
|
||||||
|
|
||||||
|
public NameXEval(NameXPtg ptg) {
|
||||||
|
_ptg = ptg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NameXPtg getPtg() {
|
||||||
|
return _ptg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(_ptg.getSheetRefIndex()).append(", ").append(_ptg.getNameIndex());
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Created on May 8, 2005
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.apache.poi.ss.formula.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.formula.IntPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.NumberPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
import org.apache.poi.ss.util.NumberToTextConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class NumberEval implements NumericValueEval, StringValueEval {
|
||||||
|
|
||||||
|
public static final NumberEval ZERO = new NumberEval(0);
|
||||||
|
|
||||||
|
private final double _value;
|
||||||
|
private String _stringValue;
|
||||||
|
|
||||||
|
public NumberEval(Ptg ptg) {
|
||||||
|
if (ptg == null) {
|
||||||
|
throw new IllegalArgumentException("ptg must not be null");
|
||||||
|
}
|
||||||
|
if (ptg instanceof IntPtg) {
|
||||||
|
_value = ((IntPtg) ptg).getValue();
|
||||||
|
} else if (ptg instanceof NumberPtg) {
|
||||||
|
_value = ((NumberPtg) ptg).getValue();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("bad argument type (" + ptg.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumberEval(double value) {
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getNumberValue() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringValue() {
|
||||||
|
if (_stringValue == null) {
|
||||||
|
_stringValue = NumberToTextConverter.toText(_value);
|
||||||
|
}
|
||||||
|
return _stringValue;
|
||||||
|
}
|
||||||
|
public final String toString() {
|
||||||
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(getStringValue());
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Created on May 8, 2005
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.apache.poi.ss.formula.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface NumericValueEval extends ValueEval {
|
||||||
|
|
||||||
|
public abstract double getNumberValue();
|
||||||
|
}
|
|
@ -0,0 +1,324 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides functionality for evaluating arguments to functions and operators.
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
* @author Brendan Nolan
|
||||||
|
*/
|
||||||
|
public final class OperandResolver {
|
||||||
|
|
||||||
|
// Based on regular expression defined in JavaDoc at {@link java.lang.Double#valueOf}
|
||||||
|
// modified to remove support for NaN, Infinity, Hexadecimal support and floating type suffixes
|
||||||
|
private static final String Digits = "(\\p{Digit}+)";
|
||||||
|
private static final String Exp = "[eE][+-]?"+Digits;
|
||||||
|
private static final String fpRegex =
|
||||||
|
("[\\x00-\\x20]*" +
|
||||||
|
"[+-]?(" +
|
||||||
|
"((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+
|
||||||
|
"(\\.("+Digits+")("+Exp+")?))))"+
|
||||||
|
"[\\x00-\\x20]*");
|
||||||
|
|
||||||
|
|
||||||
|
private OperandResolver() {
|
||||||
|
// no instances of this class
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a single value from a variety of different argument types according to standard
|
||||||
|
* Excel rules. Does not perform any type conversion.
|
||||||
|
* @param arg the evaluated argument as passed to the function or operator.
|
||||||
|
* @param srcCellRow used when arg is a single column AreaRef
|
||||||
|
* @param srcCellCol used when arg is a single row AreaRef
|
||||||
|
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt> or <tt>BlankEval</tt>.
|
||||||
|
* Never <code>null</code> or <tt>ErrorEval</tt>.
|
||||||
|
* @throws EvaluationException(#VALUE!) if srcCellRow or srcCellCol do not properly index into
|
||||||
|
* an AreaEval. If the actual value retrieved is an ErrorEval, a corresponding
|
||||||
|
* EvaluationException is thrown.
|
||||||
|
*/
|
||||||
|
public static ValueEval getSingleValue(ValueEval arg, int srcCellRow, int srcCellCol)
|
||||||
|
throws EvaluationException {
|
||||||
|
ValueEval result;
|
||||||
|
if (arg instanceof RefEval) {
|
||||||
|
result = ((RefEval) arg).getInnerValueEval();
|
||||||
|
} else if (arg instanceof AreaEval) {
|
||||||
|
result = chooseSingleElementFromArea((AreaEval) arg, srcCellRow, srcCellCol);
|
||||||
|
} else {
|
||||||
|
result = arg;
|
||||||
|
}
|
||||||
|
if (result instanceof ErrorEval) {
|
||||||
|
throw new EvaluationException((ErrorEval) result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements (some perhaps not well known) Excel functionality to select a single cell from an
|
||||||
|
* area depending on the coordinates of the calling cell. Here is an example demonstrating
|
||||||
|
* both selection from a single row area and a single column area in the same formula.
|
||||||
|
*
|
||||||
|
* <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
|
||||||
|
* <tr><th> </th><th> A </th><th> B </th><th> C </th><th> D </th></tr>
|
||||||
|
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td> </td></tr>
|
||||||
|
* <tr><th>2</th><td> </td><td> </td><td> </td><td>200</td></tr>
|
||||||
|
* <tr><th>3</th><td> </td><td> </td><td> </td><td>300</td></tr>
|
||||||
|
* <tr><th>3</th><td> </td><td> </td><td> </td><td>400</td></tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* If the formula "=1000+A1:B1+D2:D3" is put into the 9 cells from A2 to C4, the spreadsheet
|
||||||
|
* will look like this:
|
||||||
|
*
|
||||||
|
* <table border="1" cellpadding="1" cellspacing="1" summary="sample spreadsheet">
|
||||||
|
* <tr><th> </th><th> A </th><th> B </th><th> C </th><th> D </th></tr>
|
||||||
|
* <tr><th>1</th><td>15</td><td>20</td><td>25</td><td> </td></tr>
|
||||||
|
* <tr><th>2</th><td>1215</td><td>1220</td><td>#VALUE!</td><td>200</td></tr>
|
||||||
|
* <tr><th>3</th><td>1315</td><td>1320</td><td>#VALUE!</td><td>300</td></tr>
|
||||||
|
* <tr><th>4</th><td>#VALUE!</td><td>#VALUE!</td><td>#VALUE!</td><td>400</td></tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* Note that the row area (A1:B1) does not include column C and the column area (D2:D3) does
|
||||||
|
* not include row 4, so the values in C1(=25) and D4(=400) are not accessible to the formula
|
||||||
|
* as written, but in the 4 cells A2:B3, the row and column selection works ok.<p/>
|
||||||
|
*
|
||||||
|
* The same concept is extended to references across sheets, such that even multi-row,
|
||||||
|
* multi-column areas can be useful.<p/>
|
||||||
|
*
|
||||||
|
* Of course with carefully (or carelessly) chosen parameters, cyclic references can occur and
|
||||||
|
* hence this method <b>can</b> throw a 'circular reference' EvaluationException. Note that
|
||||||
|
* this method does not attempt to detect cycles. Every cell in the specified Area <tt>ae</tt>
|
||||||
|
* has already been evaluated prior to this method call. Any cell (or cell<b>s</b>) part of
|
||||||
|
* <tt>ae</tt> that would incur a cyclic reference error if selected by this method, will
|
||||||
|
* already have the value <t>ErrorEval.CIRCULAR_REF_ERROR</tt> upon entry to this method. It
|
||||||
|
* is assumed logic exists elsewhere to produce this behaviour.
|
||||||
|
*
|
||||||
|
* @return whatever the selected cell's evaluated value is. Never <code>null</code>. Never
|
||||||
|
* <tt>ErrorEval</tt>.
|
||||||
|
* @throws EvaluationException if there is a problem with indexing into the area, or if the
|
||||||
|
* evaluated cell has an error.
|
||||||
|
*/
|
||||||
|
public static ValueEval chooseSingleElementFromArea(AreaEval ae,
|
||||||
|
int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
ValueEval result = chooseSingleElementFromAreaInternal(ae, srcCellRow, srcCellCol);
|
||||||
|
if (result instanceof ErrorEval) {
|
||||||
|
throw new EvaluationException((ErrorEval) result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return possibly <tt>ErrorEval</tt>, and <code>null</code>
|
||||||
|
*/
|
||||||
|
private static ValueEval chooseSingleElementFromAreaInternal(AreaEval ae,
|
||||||
|
int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
|
||||||
|
if(false) {
|
||||||
|
// this is too simplistic
|
||||||
|
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
|
||||||
|
throw new EvaluationException(ErrorEval.CIRCULAR_REF_ERROR);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Circular references are not dealt with directly here, but it is worth noting some issues.
|
||||||
|
|
||||||
|
ANY one of the return statements in this method could return a cell that is identical
|
||||||
|
to the one immediately being evaluated. The evaluating cell is identified by srcCellRow,
|
||||||
|
srcCellRow AND sheet. The sheet is not available in any nearby calling method, so that's
|
||||||
|
one reason why circular references are not easy to detect here. (The sheet of the returned
|
||||||
|
cell can be obtained from ae if it is an Area3DEval.)
|
||||||
|
|
||||||
|
Another reason there's little value in attempting to detect circular references here is
|
||||||
|
that only direct circular references could be detected. If the cycle involved two or more
|
||||||
|
cells this method could not detect it.
|
||||||
|
|
||||||
|
Logic to detect evaluation cycles of all kinds has been coded in EvaluationCycleDetector
|
||||||
|
(and FormulaEvaluator).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ae.isColumn()) {
|
||||||
|
if(ae.isRow()) {
|
||||||
|
return ae.getRelativeValue(0, 0);
|
||||||
|
}
|
||||||
|
if(!ae.containsRow(srcCellRow)) {
|
||||||
|
throw EvaluationException.invalidValue();
|
||||||
|
}
|
||||||
|
return ae.getAbsoluteValue(srcCellRow, ae.getFirstColumn());
|
||||||
|
}
|
||||||
|
if(!ae.isRow()) {
|
||||||
|
// multi-column, multi-row area
|
||||||
|
if(ae.containsRow(srcCellRow) && ae.containsColumn(srcCellCol)) {
|
||||||
|
return ae.getAbsoluteValue(ae.getFirstRow(), ae.getFirstColumn());
|
||||||
|
}
|
||||||
|
throw EvaluationException.invalidValue();
|
||||||
|
}
|
||||||
|
if(!ae.containsColumn(srcCellCol)) {
|
||||||
|
throw EvaluationException.invalidValue();
|
||||||
|
}
|
||||||
|
return ae.getAbsoluteValue(ae.getFirstRow(), srcCellCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies some conversion rules if the supplied value is not already an integer.<br/>
|
||||||
|
* Value is first coerced to a <tt>double</tt> ( See <tt>coerceValueToDouble()</tt> ).
|
||||||
|
* Note - <tt>BlankEval</tt> is converted to <code>0</code>.<p/>
|
||||||
|
*
|
||||||
|
* Excel typically converts doubles to integers by truncating toward negative infinity.<br/>
|
||||||
|
* The equivalent java code is:<br/>
|
||||||
|
* <code>return (int)Math.floor(d);</code><br/>
|
||||||
|
* <b>not</b>:<br/>
|
||||||
|
* <code>return (int)d; // wrong - rounds toward zero</code>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static int coerceValueToInt(ValueEval ev) throws EvaluationException {
|
||||||
|
if (ev == BlankEval.instance) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
double d = coerceValueToDouble(ev);
|
||||||
|
// Note - the standard java type conversion from double to int truncates toward zero.
|
||||||
|
// but Math.floor() truncates toward negative infinity
|
||||||
|
return (int)Math.floor(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies some conversion rules if the supplied value is not already a number.
|
||||||
|
* Note - <tt>BlankEval</tt> is converted to {@link NumberEval#ZERO}.
|
||||||
|
* @param ev must be a {@link NumberEval}, {@link StringEval}, {@link BoolEval} or
|
||||||
|
* {@link BlankEval}
|
||||||
|
* @return actual, parsed or interpreted double value (respectively).
|
||||||
|
* @throws EvaluationException(#VALUE!) only if a StringEval is supplied and cannot be parsed
|
||||||
|
* as a double (See <tt>parseDouble()</tt> for allowable formats).
|
||||||
|
* @throws RuntimeException if the supplied parameter is not {@link NumberEval},
|
||||||
|
* {@link StringEval}, {@link BoolEval} or {@link BlankEval}
|
||||||
|
*/
|
||||||
|
public static double coerceValueToDouble(ValueEval ev) throws EvaluationException {
|
||||||
|
|
||||||
|
if (ev == BlankEval.instance) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (ev instanceof NumericValueEval) {
|
||||||
|
// this also handles booleans
|
||||||
|
return ((NumericValueEval)ev).getNumberValue();
|
||||||
|
}
|
||||||
|
if (ev instanceof StringEval) {
|
||||||
|
Double dd = parseDouble(((StringEval) ev).getStringValue());
|
||||||
|
if (dd == null) {
|
||||||
|
throw EvaluationException.invalidValue();
|
||||||
|
}
|
||||||
|
return dd.doubleValue();
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Unexpected arg eval type (" + ev.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string to a double using standard rules that Excel would use.<br/>
|
||||||
|
* Tolerates leading and trailing spaces, <p/>
|
||||||
|
*
|
||||||
|
* Doesn't support currency prefixes, commas, percentage signs or arithmetic operations strings.
|
||||||
|
*
|
||||||
|
* Some examples:<br/>
|
||||||
|
* " 123 " -> 123.0<br/>
|
||||||
|
* ".123" -> 0.123<br/>
|
||||||
|
* "1E4" -> 1000<br/>
|
||||||
|
* "-123" -> -123.0<br/>
|
||||||
|
* These not supported yet:<br/>
|
||||||
|
* " $ 1,000.00 " -> 1000.0<br/>
|
||||||
|
* "$1.25E4" -> 12500.0<br/>
|
||||||
|
* "5**2" -> 500<br/>
|
||||||
|
* "250%" -> 2.5<br/>
|
||||||
|
*
|
||||||
|
* @return <code>null</code> if the specified text cannot be parsed as a number
|
||||||
|
*/
|
||||||
|
public static Double parseDouble(String pText) {
|
||||||
|
|
||||||
|
if (Pattern.matches(fpRegex, pText))
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(pText);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ve must be a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>, or <tt>BlankEval</tt>
|
||||||
|
* @return the converted string value. never <code>null</code>
|
||||||
|
*/
|
||||||
|
public static String coerceValueToString(ValueEval ve) {
|
||||||
|
if (ve instanceof StringValueEval) {
|
||||||
|
StringValueEval sve = (StringValueEval) ve;
|
||||||
|
return sve.getStringValue();
|
||||||
|
}
|
||||||
|
if (ve == BlankEval.instance) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unexpected eval class (" + ve.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>null</code> to represent blank values
|
||||||
|
* @throws EvaluationException if ve is an ErrorEval, or if a string value cannot be converted
|
||||||
|
*/
|
||||||
|
public static Boolean coerceValueToBoolean(ValueEval ve, boolean stringsAreBlanks) throws EvaluationException {
|
||||||
|
|
||||||
|
if (ve == null || ve == BlankEval.instance) {
|
||||||
|
// TODO - remove 've == null' condition once AreaEval is fixed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ve instanceof BoolEval) {
|
||||||
|
return Boolean.valueOf(((BoolEval) ve).getBooleanValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ve == BlankEval.instance) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ve instanceof StringEval) {
|
||||||
|
if (stringsAreBlanks) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String str = ((StringEval) ve).getStringValue();
|
||||||
|
if (str.equalsIgnoreCase("true")) {
|
||||||
|
return Boolean.TRUE;
|
||||||
|
}
|
||||||
|
if (str.equalsIgnoreCase("false")) {
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
// else - string cannot be converted to boolean
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ve instanceof NumericValueEval) {
|
||||||
|
NumericValueEval ne = (NumericValueEval) ve;
|
||||||
|
double d = ne.getNumberValue();
|
||||||
|
if (Double.isNaN(d)) {
|
||||||
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
|
}
|
||||||
|
return Boolean.valueOf(d != 0);
|
||||||
|
}
|
||||||
|
if (ve instanceof ErrorEval) {
|
||||||
|
throw new EvaluationException((ErrorEval) ve);
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Unexpected eval (" + ve.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of Excel formula token '%'. <p/>
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class PercentEval extends Fixed1ArgFunction {
|
||||||
|
|
||||||
|
public static final Function instance = new PercentEval();
|
||||||
|
|
||||||
|
private PercentEval() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
|
||||||
|
double d;
|
||||||
|
try {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
|
||||||
|
d = OperandResolver.coerceValueToDouble(ve);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
if (d == 0.0) { // this '==' matches +0.0 and -0.0
|
||||||
|
return NumberEval.ZERO;
|
||||||
|
}
|
||||||
|
return new NumberEval(d / 100);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public final class RangeEval extends Fixed2ArgFunction {
|
||||||
|
|
||||||
|
public static final Function instance = new RangeEval();
|
||||||
|
|
||||||
|
private RangeEval() {
|
||||||
|
// enforces singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
AreaEval reA = evaluateRef(arg0);
|
||||||
|
AreaEval reB = evaluateRef(arg1);
|
||||||
|
return resolveRange(reA, reB);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return simple rectangular {@link AreaEval} which fully encloses both areas
|
||||||
|
* <tt>aeA</tt> and <tt>aeB</tt>
|
||||||
|
*/
|
||||||
|
private static AreaEval resolveRange(AreaEval aeA, AreaEval aeB) {
|
||||||
|
int aeAfr = aeA.getFirstRow();
|
||||||
|
int aeAfc = aeA.getFirstColumn();
|
||||||
|
|
||||||
|
int top = Math.min(aeAfr, aeB.getFirstRow());
|
||||||
|
int bottom = Math.max(aeA.getLastRow(), aeB.getLastRow());
|
||||||
|
int left = Math.min(aeAfc, aeB.getFirstColumn());
|
||||||
|
int right = Math.max(aeA.getLastColumn(), aeB.getLastColumn());
|
||||||
|
|
||||||
|
return aeA.offset(top-aeAfr, bottom-aeAfr, left-aeAfc, right-aeAfc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AreaEval evaluateRef(ValueEval arg) throws EvaluationException {
|
||||||
|
if (arg instanceof AreaEval) {
|
||||||
|
return (AreaEval) arg;
|
||||||
|
}
|
||||||
|
if (arg instanceof RefEval) {
|
||||||
|
return ((RefEval) arg).offset(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
if (arg instanceof ErrorEval) {
|
||||||
|
throw new EvaluationException((ErrorEval)arg);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unexpected ref arg class (" + arg.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*
|
||||||
|
* RefEval is the super interface for Ref2D and Ref3DEval. Basically a RefEval
|
||||||
|
* impl should contain reference to the original ReferencePtg or Ref3DPtg as
|
||||||
|
* well as the final "value" resulting from the evaluation of the cell
|
||||||
|
* reference. Thus if the Cell has type CELL_TYPE_NUMERIC, the contained
|
||||||
|
* value object should be of type NumberEval; if cell type is CELL_TYPE_STRING,
|
||||||
|
* contained value object should be of type StringEval
|
||||||
|
*/
|
||||||
|
public interface RefEval extends ValueEval {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the evaluated value of the cell referred to by this RefEval.
|
||||||
|
*/
|
||||||
|
ValueEval getInnerValueEval();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the zero based column index.
|
||||||
|
*/
|
||||||
|
int getColumn();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the zero based row index.
|
||||||
|
*/
|
||||||
|
int getRow();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link AreaEval} offset by a relative amount from this RefEval
|
||||||
|
*/
|
||||||
|
AreaEval offset(int relFirstRowIx, int relLastRowIx, int relFirstColIx, int relLastColIx);
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common base class for implementors of {@link RefEval}
|
||||||
|
*
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public abstract class RefEvalBase implements RefEval {
|
||||||
|
|
||||||
|
private final int _rowIndex;
|
||||||
|
private final int _columnIndex;
|
||||||
|
|
||||||
|
protected RefEvalBase(int rowIndex, int columnIndex) {
|
||||||
|
_rowIndex = rowIndex;
|
||||||
|
_columnIndex = columnIndex;
|
||||||
|
}
|
||||||
|
public final int getRow() {
|
||||||
|
return _rowIndex;
|
||||||
|
}
|
||||||
|
public final int getColumn() {
|
||||||
|
return _columnIndex;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
import org.apache.poi.ss.util.NumberComparer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all comparison operator evaluators
|
||||||
|
*
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public abstract class RelationalOperationEval extends Fixed2ArgFunction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a standard compare result (-1, 0, 1) to <code>true</code> or <code>false</code>
|
||||||
|
* according to subclass' comparison type.
|
||||||
|
*/
|
||||||
|
protected abstract boolean convertComparisonResult(int cmpResult);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a description of how the relational operators apply in MS Excel.
|
||||||
|
* Use this as a guideline when testing/implementing the evaluate methods
|
||||||
|
* for the relational operators Evals.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Bool.TRUE > any number.
|
||||||
|
* Bool > any string. ALWAYS
|
||||||
|
* Bool.TRUE > Bool.FALSE
|
||||||
|
* Bool.FALSE == Blank
|
||||||
|
*
|
||||||
|
* Strings are never converted to numbers or booleans
|
||||||
|
* String > any number. ALWAYS
|
||||||
|
* Non-empty String > Blank
|
||||||
|
* Empty String == Blank
|
||||||
|
* String are sorted dictionary wise
|
||||||
|
*
|
||||||
|
* Blank > Negative numbers
|
||||||
|
* Blank == 0
|
||||||
|
* Blank < Positive numbers
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
|
||||||
|
|
||||||
|
ValueEval vA;
|
||||||
|
ValueEval vB;
|
||||||
|
try {
|
||||||
|
vA = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
|
||||||
|
vB = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
int cmpResult = doCompare(vA, vB);
|
||||||
|
boolean result = convertComparisonResult(cmpResult);
|
||||||
|
return BoolEval.valueOf(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int doCompare(ValueEval va, ValueEval vb) {
|
||||||
|
// special cases when one operand is blank
|
||||||
|
if (va == BlankEval.instance) {
|
||||||
|
return compareBlank(vb);
|
||||||
|
}
|
||||||
|
if (vb == BlankEval.instance) {
|
||||||
|
return -compareBlank(va);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (va instanceof BoolEval) {
|
||||||
|
if (vb instanceof BoolEval) {
|
||||||
|
BoolEval bA = (BoolEval) va;
|
||||||
|
BoolEval bB = (BoolEval) vb;
|
||||||
|
if (bA.getBooleanValue() == bB.getBooleanValue()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return bA.getBooleanValue() ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (vb instanceof BoolEval) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (va instanceof StringEval) {
|
||||||
|
if (vb instanceof StringEval) {
|
||||||
|
StringEval sA = (StringEval) va;
|
||||||
|
StringEval sB = (StringEval) vb;
|
||||||
|
return sA.getStringValue().compareToIgnoreCase(sB.getStringValue());
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (vb instanceof StringEval) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (va instanceof NumberEval) {
|
||||||
|
if (vb instanceof NumberEval) {
|
||||||
|
NumberEval nA = (NumberEval) va;
|
||||||
|
NumberEval nB = (NumberEval) vb;
|
||||||
|
return NumberComparer.compare(nA.getNumberValue(), nB.getNumberValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Bad operand types (" + va.getClass().getName() + "), ("
|
||||||
|
+ vb.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int compareBlank(ValueEval v) {
|
||||||
|
if (v == BlankEval.instance) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (v instanceof BoolEval) {
|
||||||
|
BoolEval boolEval = (BoolEval) v;
|
||||||
|
return boolEval.getBooleanValue() ? -1 : 0;
|
||||||
|
}
|
||||||
|
if (v instanceof NumberEval) {
|
||||||
|
NumberEval ne = (NumberEval) v;
|
||||||
|
return NumberComparer.compare(0.0, ne.getNumberValue());
|
||||||
|
}
|
||||||
|
if (v instanceof StringEval) {
|
||||||
|
StringEval se = (StringEval) v;
|
||||||
|
return se.getStringValue().length() < 1 ? 0 : -1;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("bad value class (" + v.getClass().getName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Function EqualEval = new RelationalOperationEval() {
|
||||||
|
protected boolean convertComparisonResult(int cmpResult) {
|
||||||
|
return cmpResult == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function GreaterEqualEval = new RelationalOperationEval() {
|
||||||
|
protected boolean convertComparisonResult(int cmpResult) {
|
||||||
|
return cmpResult >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function GreaterThanEval = new RelationalOperationEval() {
|
||||||
|
protected boolean convertComparisonResult(int cmpResult) {
|
||||||
|
return cmpResult > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function LessEqualEval = new RelationalOperationEval() {
|
||||||
|
protected boolean convertComparisonResult(int cmpResult) {
|
||||||
|
return cmpResult <= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function LessThanEval = new RelationalOperationEval() {
|
||||||
|
protected boolean convertComparisonResult(int cmpResult) {
|
||||||
|
return cmpResult < 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function NotEqualEval = new RelationalOperationEval() {
|
||||||
|
protected boolean convertComparisonResult(int cmpResult) {
|
||||||
|
return cmpResult != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
import org.apache.poi.hssf.record.formula.StringPtg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public final class StringEval implements StringValueEval {
|
||||||
|
|
||||||
|
public static final StringEval EMPTY_INSTANCE = new StringEval("");
|
||||||
|
|
||||||
|
private final String _value;
|
||||||
|
|
||||||
|
public StringEval(Ptg ptg) {
|
||||||
|
this(((StringPtg) ptg).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringEval(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("value must not be null");
|
||||||
|
}
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringValue() {
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder(64);
|
||||||
|
sb.append(getClass().getName()).append(" [");
|
||||||
|
sb.append(_value);
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface StringValueEval extends ValueEval {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return never <code>null</code>, possibly empty string.
|
||||||
|
*/
|
||||||
|
String getStringValue();
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed2ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Josh Micich
|
||||||
|
*/
|
||||||
|
public abstract class TwoOperandNumericOperation extends Fixed2ArgFunction {
|
||||||
|
|
||||||
|
protected final double singleOperandEvaluate(ValueEval arg, int srcCellRow, int srcCellCol) throws EvaluationException {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg, srcCellRow, srcCellCol);
|
||||||
|
return OperandResolver.coerceValueToDouble(ve);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
if (result == 0.0) { // this '==' matches +0.0 and -0.0
|
||||||
|
// Excel converts -0.0 to +0.0 for '*', '/', '%', '+' and '^'
|
||||||
|
if (!(this instanceof SubtractEvalClass)) {
|
||||||
|
return NumberEval.ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Double.isNaN(result) || Double.isInfinite(result)) {
|
||||||
|
return ErrorEval.NUM_ERROR;
|
||||||
|
}
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
return new NumberEval(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract double evaluate(double d0, double d1) throws EvaluationException;
|
||||||
|
|
||||||
|
public static final Function AddEval = new TwoOperandNumericOperation() {
|
||||||
|
protected double evaluate(double d0, double d1) {
|
||||||
|
return d0+d1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function DivideEval = new TwoOperandNumericOperation() {
|
||||||
|
protected double evaluate(double d0, double d1) throws EvaluationException {
|
||||||
|
if (d1 == 0.0) {
|
||||||
|
throw new EvaluationException(ErrorEval.DIV_ZERO);
|
||||||
|
}
|
||||||
|
return d0/d1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function MultiplyEval = new TwoOperandNumericOperation() {
|
||||||
|
protected double evaluate(double d0, double d1) {
|
||||||
|
return d0*d1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static final Function PowerEval = new TwoOperandNumericOperation() {
|
||||||
|
protected double evaluate(double d0, double d1) {
|
||||||
|
return Math.pow(d0, d1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final class SubtractEvalClass extends TwoOperandNumericOperation {
|
||||||
|
public SubtractEvalClass() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
protected double evaluate(double d0, double d1) {
|
||||||
|
return d0-d1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static final Function SubtractEval = new SubtractEvalClass();
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public final class UnaryMinusEval extends Fixed1ArgFunction {
|
||||||
|
|
||||||
|
public static final Function instance = new UnaryMinusEval();
|
||||||
|
|
||||||
|
private UnaryMinusEval() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
|
||||||
|
double d;
|
||||||
|
try {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg0, srcRowIndex, srcColumnIndex);
|
||||||
|
d = OperandResolver.coerceValueToDouble(ve);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
if (d == 0.0) { // this '==' matches +0.0 and -0.0
|
||||||
|
return NumberEval.ZERO;
|
||||||
|
}
|
||||||
|
return new NumberEval(-d);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.eval;
|
||||||
|
|
||||||
|
import org.apache.poi.ss.formula.functions.Fixed1ArgFunction;
|
||||||
|
import org.apache.poi.ss.formula.functions.Function;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public final class UnaryPlusEval extends Fixed1ArgFunction {
|
||||||
|
|
||||||
|
public static final Function instance = new UnaryPlusEval();
|
||||||
|
|
||||||
|
private UnaryPlusEval() {
|
||||||
|
// enforce singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueEval evaluate(int srcCellRow, int srcCellCol, ValueEval arg0) {
|
||||||
|
double d;
|
||||||
|
try {
|
||||||
|
ValueEval ve = OperandResolver.getSingleValue(arg0, srcCellRow, srcCellCol);
|
||||||
|
if(ve instanceof StringEval) {
|
||||||
|
// Note - asymmetric with UnaryMinus
|
||||||
|
// -"hello" evaluates to #VALUE!
|
||||||
|
// but +"hello" evaluates to "hello"
|
||||||
|
return ve;
|
||||||
|
}
|
||||||
|
d = OperandResolver.coerceValueToDouble(ve);
|
||||||
|
} catch (EvaluationException e) {
|
||||||
|
return e.getErrorEval();
|
||||||
|
}
|
||||||
|
return new NumberEval(+d);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.eval;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Amol S. Deshmukh < amolweb at ya hoo dot com >
|
||||||
|
*/
|
||||||
|
public interface ValueEval {
|
||||||
|
// no methods
|
||||||
|
}
|
|
@ -17,13 +17,12 @@
|
||||||
|
|
||||||
package org.apache.poi.ss.formula.eval.forked;
|
package org.apache.poi.ss.formula.eval.forked;
|
||||||
|
|
||||||
import org.apache.poi.hssf.record.formula.eval.BlankEval;
|
import org.apache.poi.ss.formula.eval.BlankEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.BoolEval;
|
import org.apache.poi.ss.formula.eval.BoolEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
import org.apache.poi.ss.formula.eval.NumberEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
import org.apache.poi.ss.formula.eval.StringEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFCell;
|
|
||||||
import org.apache.poi.ss.formula.EvaluationCell;
|
import org.apache.poi.ss.formula.EvaluationCell;
|
||||||
import org.apache.poi.ss.formula.EvaluationSheet;
|
import org.apache.poi.ss.formula.EvaluationSheet;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
@ -60,27 +59,27 @@ final class ForkedEvaluationCell implements EvaluationCell {
|
||||||
Class<? extends ValueEval> cls = value.getClass();
|
Class<? extends ValueEval> cls = value.getClass();
|
||||||
|
|
||||||
if (cls == NumberEval.class) {
|
if (cls == NumberEval.class) {
|
||||||
_cellType = HSSFCell.CELL_TYPE_NUMERIC;
|
_cellType = Cell.CELL_TYPE_NUMERIC;
|
||||||
_numberValue = ((NumberEval)value).getNumberValue();
|
_numberValue = ((NumberEval)value).getNumberValue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cls == StringEval.class) {
|
if (cls == StringEval.class) {
|
||||||
_cellType = HSSFCell.CELL_TYPE_STRING;
|
_cellType = Cell.CELL_TYPE_STRING;
|
||||||
_stringValue = ((StringEval)value).getStringValue();
|
_stringValue = ((StringEval)value).getStringValue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cls == BoolEval.class) {
|
if (cls == BoolEval.class) {
|
||||||
_cellType = HSSFCell.CELL_TYPE_BOOLEAN;
|
_cellType = Cell.CELL_TYPE_BOOLEAN;
|
||||||
_booleanValue = ((BoolEval)value).getBooleanValue();
|
_booleanValue = ((BoolEval)value).getBooleanValue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cls == ErrorEval.class) {
|
if (cls == ErrorEval.class) {
|
||||||
_cellType = HSSFCell.CELL_TYPE_ERROR;
|
_cellType = Cell.CELL_TYPE_ERROR;
|
||||||
_errorValue = ((ErrorEval)value).getErrorCode();
|
_errorValue = ((ErrorEval)value).getErrorCode();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cls == BlankEval.class) {
|
if (cls == BlankEval.class) {
|
||||||
_cellType = HSSFCell.CELL_TYPE_BLANK;
|
_cellType = Cell.CELL_TYPE_BLANK;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")");
|
throw new IllegalArgumentException("Unexpected value class (" + cls.getName() + ")");
|
||||||
|
@ -105,19 +104,19 @@ final class ForkedEvaluationCell implements EvaluationCell {
|
||||||
return _cellType;
|
return _cellType;
|
||||||
}
|
}
|
||||||
public boolean getBooleanCellValue() {
|
public boolean getBooleanCellValue() {
|
||||||
checkCellType(HSSFCell.CELL_TYPE_BOOLEAN);
|
checkCellType(Cell.CELL_TYPE_BOOLEAN);
|
||||||
return _booleanValue;
|
return _booleanValue;
|
||||||
}
|
}
|
||||||
public int getErrorCellValue() {
|
public int getErrorCellValue() {
|
||||||
checkCellType(HSSFCell.CELL_TYPE_ERROR);
|
checkCellType(Cell.CELL_TYPE_ERROR);
|
||||||
return _errorValue;
|
return _errorValue;
|
||||||
}
|
}
|
||||||
public double getNumericCellValue() {
|
public double getNumericCellValue() {
|
||||||
checkCellType(HSSFCell.CELL_TYPE_NUMERIC);
|
checkCellType(Cell.CELL_TYPE_NUMERIC);
|
||||||
return _numberValue;
|
return _numberValue;
|
||||||
}
|
}
|
||||||
public String getStringCellValue() {
|
public String getStringCellValue() {
|
||||||
checkCellType(HSSFCell.CELL_TYPE_STRING);
|
checkCellType(Cell.CELL_TYPE_STRING);
|
||||||
return _stringValue;
|
return _stringValue;
|
||||||
}
|
}
|
||||||
public EvaluationSheet getSheet() {
|
public EvaluationSheet getSheet() {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import java.util.Map;
|
||||||
import org.apache.poi.hssf.record.formula.NamePtg;
|
import org.apache.poi.hssf.record.formula.NamePtg;
|
||||||
import org.apache.poi.hssf.record.formula.NameXPtg;
|
import org.apache.poi.hssf.record.formula.NameXPtg;
|
||||||
import org.apache.poi.hssf.record.formula.Ptg;
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
|
|
||||||
import org.apache.poi.ss.formula.EvaluationCell;
|
import org.apache.poi.ss.formula.EvaluationCell;
|
||||||
import org.apache.poi.ss.formula.EvaluationName;
|
import org.apache.poi.ss.formula.EvaluationName;
|
||||||
import org.apache.poi.ss.formula.EvaluationSheet;
|
import org.apache.poi.ss.formula.EvaluationSheet;
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
|
|
||||||
package org.apache.poi.ss.formula.eval.forked;
|
package org.apache.poi.ss.formula.eval.forked;
|
||||||
|
|
||||||
import org.apache.poi.hssf.record.formula.eval.BoolEval;
|
import org.apache.poi.ss.formula.eval.BoolEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
import org.apache.poi.ss.formula.eval.NumberEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
import org.apache.poi.ss.formula.eval.StringEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
import org.apache.poi.ss.formula.eval.ValueEval;
|
||||||
import org.apache.poi.hssf.record.formula.udf.UDFFinder;
|
import org.apache.poi.ss.formula.udf.UDFFinder;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFCell;
|
|
||||||
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
|
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment;
|
||||||
|
@ -31,6 +30,7 @@ import org.apache.poi.ss.formula.EvaluationCell;
|
||||||
import org.apache.poi.ss.formula.EvaluationWorkbook;
|
import org.apache.poi.ss.formula.EvaluationWorkbook;
|
||||||
import org.apache.poi.ss.formula.IStabilityClassifier;
|
import org.apache.poi.ss.formula.IStabilityClassifier;
|
||||||
import org.apache.poi.ss.formula.WorkbookEvaluator;
|
import org.apache.poi.ss.formula.WorkbookEvaluator;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,17 +113,17 @@ public final class ForkedEvaluator {
|
||||||
EvaluationCell cell = _sewb.getEvaluationCell(sheetName, rowIndex, columnIndex);
|
EvaluationCell cell = _sewb.getEvaluationCell(sheetName, rowIndex, columnIndex);
|
||||||
|
|
||||||
switch (cell.getCellType()) {
|
switch (cell.getCellType()) {
|
||||||
case HSSFCell.CELL_TYPE_BOOLEAN:
|
case Cell.CELL_TYPE_BOOLEAN:
|
||||||
return BoolEval.valueOf(cell.getBooleanCellValue());
|
return BoolEval.valueOf(cell.getBooleanCellValue());
|
||||||
case HSSFCell.CELL_TYPE_ERROR:
|
case Cell.CELL_TYPE_ERROR:
|
||||||
return ErrorEval.valueOf(cell.getErrorCellValue());
|
return ErrorEval.valueOf(cell.getErrorCellValue());
|
||||||
case HSSFCell.CELL_TYPE_FORMULA:
|
case Cell.CELL_TYPE_FORMULA:
|
||||||
return _evaluator.evaluate(cell);
|
return _evaluator.evaluate(cell);
|
||||||
case HSSFCell.CELL_TYPE_NUMERIC:
|
case Cell.CELL_TYPE_NUMERIC:
|
||||||
return new NumberEval(cell.getNumericCellValue());
|
return new NumberEval(cell.getNumericCellValue());
|
||||||
case HSSFCell.CELL_TYPE_STRING:
|
case Cell.CELL_TYPE_STRING:
|
||||||
return new StringEval(cell.getStringCellValue());
|
return new StringEval(cell.getStringCellValue());
|
||||||
case HSSFCell.CELL_TYPE_BLANK:
|
case Cell.CELL_TYPE_BLANK:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
|
throw new IllegalStateException("Bad cell type (" + cell.getCellType() + ")");
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
|
|
||||||
package org.apache.poi.ss.usermodel;
|
package org.apache.poi.ss.usermodel;
|
||||||
|
|
||||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
import org.apache.poi.ss.formula.eval.ErrorEval;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mimics the 'data view' of a cell. This allows formula evaluator
|
* Mimics the 'data view' of a cell. This allows formula evaluator
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue