Big formula update from Josh from bug #44364 - support for Match, NA and SumProduct functions, and initial error support in functions

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@627788 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-02-14 16:01:10 +00:00
parent 3acc95e057
commit 4a7276e4ee
31 changed files with 1781 additions and 536 deletions

View File

@ -36,6 +36,7 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
<action dev="POI-DEVELOPERS" type="fix">44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored.</action>
<action dev="POI-DEVELOPERS" type="fix">38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour)</action>
<action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>

View File

@ -33,6 +33,7 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.1-beta1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">44364 - Support for Match, NA and SumProduct functions, as well as initial function error support</action>
<action dev="POI-DEVELOPERS" type="fix">44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored.</action>
<action dev="POI-DEVELOPERS" type="fix">38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour)</action>
<action dev="POI-DEVELOPERS" type="fix">44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates</action>

View File

@ -947,20 +947,28 @@ end;
// Excel allows to have AttrPtg at position 0 (such as Blanks) which
// do not have any operands. Skip them.
int i;
if(ptgs[0] instanceof AttrPtg) {
// TODO -this requirement is unclear and is not addressed by any junits
stack.push(ptgs[0].toFormulaString(book));
i=1;
} else {
i=0;
}
for (int i = 1; i < ptgs.length; i++) {
if (! (ptgs[i] instanceof OperationPtg)) {
stack.push(ptgs[i].toFormulaString(book));
for ( ; i < ptgs.length; i++) {
Ptg ptg = ptgs[i];
if (! (ptg instanceof OperationPtg)) {
stack.push(ptg.toFormulaString(book));
continue;
}
if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
ifptg = (AttrPtg) ptgs[i];
if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
ifptg = (AttrPtg) ptg;
continue;
}
final OperationPtg o = (OperationPtg) ptgs[i];
final OperationPtg o = (OperationPtg) ptg;
final String[] operands = new String[o.getNumberOfOperands()];
for (int j = operands.length; j > 0; j--) {

View File

@ -62,6 +62,10 @@ public class FuncPtg extends AbstractFunctionPtg{
numParams=0;
}
}
public FuncPtg(int functionIndex, int numberOfParameters) {
field_2_fnc_index = (short) functionIndex;
numParams = numberOfParameters;
}
public void writeBytes(byte[] array, int offset) {

View File

@ -35,6 +35,16 @@ public class BoolEval implements NumericValueEval, StringValueEval {
public static final BoolEval TRUE = new BoolEval(true);
/**
* Convenience method for the following:<br/>
* <code>(b ? BoolEval.TRUE : BoolEval.FALSE)</code>
* @return a <tt>BoolEval</tt> instance representing <tt>b</tt>.
*/
public static final BoolEval valueOf(boolean b) {
// TODO - find / replace all occurrences
return b ? TRUE : FALSE;
}
public BoolEval(Ptg ptg) {
this.value = ((BoolPtg) ptg).getValue();
}
@ -48,10 +58,17 @@ public class BoolEval implements NumericValueEval, StringValueEval {
}
public double getNumberValue() {
return value ? (short) 1 : (short) 0;
return value ? 1 : 0;
}
public String getStringValue() {
return value ? "TRUE" : "FALSE";
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(getStringValue());
sb.append("]");
return sb.toString();
}
}

View File

@ -24,31 +24,91 @@ package org.apache.poi.hssf.record.formula.eval;
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class ErrorEval implements ValueEval {
public final class ErrorEval implements ValueEval {
/**
* Contains raw Excel error codes (as defined in OOO's excelfileformat.pdf (2.5.6)
*/
private static final class ErrorCode {
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
public static final int NULL = 0x00;
/** <b>#DIV/0!</b> - Division by zero */
public static final int DIV_0 = 0x07;
/** <b>#VALUE!</b> - Wrong type of operand */
public static final int VALUE = 0x0F;
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static final int REF = 0x17;
/** <b>#NAME?</b> - Wrong function or range name */
public static final int NAME = 0x1D;
/** <b>#NUM!</b> - Value range overflow */
public static final int NUM = 0x24;
/** <b>#N/A</b> - Argument or function not available */
public static final int N_A = 0x2A;
private int errorCode;
public static final String getText(int errorCode) {
switch(errorCode) {
case NULL: return "#NULL!";
case DIV_0: return "#DIV/0!";
case VALUE: return "#VALUE!";
case REF: return "#REF!";
case NAME: return "#NAME?";
case NUM: return "#NUM!";
case N_A: return "#N/A";
}
return "???";
}
}
/** <b>#NULL!</b> - Intersection of two cell ranges is empty */
public static final ErrorEval NULL_INTERSECTION = new ErrorEval(ErrorCode.NULL);
/** <b>#DIV/0!</b> - Division by zero */
public static final ErrorEval DIV_ZERO = new ErrorEval(ErrorCode.DIV_0);
/** <b>#VALUE!</b> - Wrong type of operand */
public static final ErrorEval VALUE_INVALID = new ErrorEval(ErrorCode.VALUE);
/** <b>#REF!</b> - Illegal or deleted cell reference */
public static final ErrorEval REF_INVALID = new ErrorEval(ErrorCode.REF);
/** <b>#NAME?</b> - Wrong function or range name */
public static final ErrorEval NAME_INVALID = new ErrorEval(ErrorCode.NAME);
/** <b>#NUM!</b> - Value range overflow */
public static final ErrorEval NUM_ERROR = new ErrorEval(ErrorCode.NUM);
/** <b>#N/A</b> - Argument or function not available */
public static final ErrorEval NA = new ErrorEval(ErrorCode.N_A);
public static final ErrorEval NAME_INVALID = new ErrorEval(525);
/**
* Translates an Excel internal error code into the corresponding POI ErrorEval instance
* @param errorCode
*/
public static ErrorEval valueOf(int errorCode) {
switch(errorCode) {
case ErrorCode.NULL: return NULL_INTERSECTION;
case ErrorCode.DIV_0: return DIV_ZERO;
case ErrorCode.VALUE: return VALUE_INVALID;
// case ErrorCode.REF: return REF_INVALID;
case ErrorCode.REF: return UNKNOWN_ERROR;
case ErrorCode.NAME: return NAME_INVALID;
case ErrorCode.NUM: return NUM_ERROR;
case ErrorCode.N_A: return NA;
public static final ErrorEval VALUE_INVALID = new ErrorEval(519);
// these cases probably shouldn't be coming through here
// but (as of Jan-2008) a lot of code depends on it.
// case -20: return UNKNOWN_ERROR;
// case -30: return FUNCTION_NOT_IMPLEMENTED;
// case -60: return CIRCULAR_REF_ERROR;
}
throw new RuntimeException("Unexpected error code (" + errorCode + ")");
}
// Non std error codes
// POI internal error codes
public static final ErrorEval UNKNOWN_ERROR = new ErrorEval(-20);
public static final ErrorEval FUNCTION_NOT_IMPLEMENTED = new ErrorEval(-30);
public static final ErrorEval REF_INVALID = new ErrorEval(-40);
public static final ErrorEval NA = new ErrorEval(-50);
// Note - Excel does not seem to represent this condition with an error code
public static final ErrorEval CIRCULAR_REF_ERROR = new ErrorEval(-60);
public static final ErrorEval DIV_ZERO = new ErrorEval(-70);
public static final ErrorEval NUM_ERROR = new ErrorEval(-80);
private int errorCode;
/**
* @param errorCode an 8-bit value
*/
private ErrorEval(int errorCode) {
this.errorCode = errorCode;
}
@ -56,9 +116,11 @@ public class ErrorEval implements ValueEval {
public int getErrorCode() {
return errorCode;
}
public String getStringValue() {
return "Err:" + Integer.toString(errorCode);
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(ErrorCode.getText(errorCode));
sb.append("]");
return sb.toString();
}
}

View File

@ -58,6 +58,9 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
// ));
/**
* called by reflection
*/
public UnaryPlusEval(Ptg ptg) {
this.delegate = (UnaryPlusPtg) ptg;
}
@ -108,7 +111,7 @@ public class UnaryPlusEval implements OperationEval /*extends NumericOperationEv
}
else if (ae.isColumn()) {
if (ae.containsRow(srcRow)) {
ValueEval ve = ae.getValueAt(ae.getFirstRow(), srcCol);
ValueEval ve = ae.getValueAt(srcRow, ae.getFirstColumn());
if (ve instanceof RefEval) {
ve = ((RefEval) ve).getInnerValueEval();
}

View File

@ -14,12 +14,300 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
public class Match extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.util.AreaReference;
/**
* Implementation for the MATCH() Excel function.<p/>
*
* <b>Syntax:</b><br/>
* <b>MATCH</b>(<b>lookup_value</b>, <b>lookup_array</b>, match_type)<p/>
*
* Returns a 1-based index specifying at what position in the <b>lookup_array</b> the specified
* <b>lookup_value</b> is found.<p/>
*
* Specific matching behaviour can be modified with the optional <b>match_type</b> parameter.
*
* <table border="0" cellpadding="1" cellspacing="0" summary="match_type parameter description">
* <tr><th>Value</th><th>Matching Behaviour</th></tr>
* <tr><td>1</td><td>(default) find the largest value that is less than or equal to lookup_value.
* The lookup_array must be in ascending <i>order</i>*.</td></tr>
* <tr><td>0</td><td>find the first value that is exactly equal to lookup_value.
* The lookup_array can be in any order.</td></tr>
* <tr><td>-1</td><td>find the smallest value that is greater than or equal to lookup_value.
* The lookup_array must be in descending <i>order</i>*.</td></tr>
* </table>
*
* * Note regarding <i>order</i> - For the <b>match_type</b> cases that require the lookup_array to
* be ordered, MATCH() can produce incorrect results if this requirement is not met. Observed
* behaviour in Excel is to return the lowest index value for which every item after that index
* breaks the match rule.<br>
* The (ascending) sort order expected by MATCH() is:<br/>
* numbers (low to high), strings (A to Z), boolean (FALSE to TRUE)<br/>
* MATCH() ignores all elements in the lookup_array with a different type to the lookup_value.
* Type conversion of the lookup_array elements is never performed.
*
*
* @author Josh Micich
*/
public final class Match implements Function {
private static final class EvalEx extends Exception {
private final ErrorEval _error;
public EvalEx(ErrorEval error) {
_error = error;
}
public ErrorEval getError() {
return _error;
}
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
double match_type = 1; // default
switch(args.length) {
case 3:
try {
match_type = evaluateMatchTypeArg(args[2], srcCellRow, srcCellCol);
} catch (EvalEx e) {
// Excel/MATCH() seems to have slightly abnormal handling of errors with
// the last parameter. Errors do not propagate up. Every error gets
// translated into #REF!
return ErrorEval.REF_INVALID;
}
case 2:
break;
default:
return ErrorEval.VALUE_INVALID;
}
boolean matchExact = match_type == 0;
// Note - Excel does not strictly require -1 and +1
boolean findLargestLessThanOrEqual = match_type > 0;
try {
ValueEval lookupValue = evaluateLookupValue(args[0], srcCellRow, srcCellCol);
ValueEval[] lookupRange = evaluateLookupRange(args[1]);
int index = findIndexOfValue(lookupValue, lookupRange, matchExact, findLargestLessThanOrEqual);
return new NumberEval(index + 1); // +1 to convert to 1-based
} catch (EvalEx e) {
return e.getError();
}
}
private static ValueEval chooseSingleElementFromArea(AreaEval ae,
int srcCellRow, short srcCellCol) throws EvalEx {
if (ae.isColumn()) {
if(ae.isRow()) {
return ae.getValues()[0];
}
if(!ae.containsRow(srcCellRow)) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return ae.getValueAt(srcCellRow, ae.getFirstColumn());
}
if(!ae.isRow()) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
if(!ae.containsColumn(srcCellCol)) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return ae.getValueAt(ae.getFirstRow(), srcCellCol);
}
private static ValueEval evaluateLookupValue(Eval eval, int srcCellRow, short srcCellCol)
throws EvalEx {
if (eval instanceof RefEval) {
RefEval re = (RefEval) eval;
return re.getInnerValueEval();
}
if (eval instanceof AreaEval) {
return chooseSingleElementFromArea((AreaEval) eval, srcCellRow, srcCellCol);
}
if (eval instanceof ValueEval) {
return (ValueEval) eval;
}
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
}
private static ValueEval[] evaluateLookupRange(Eval eval) throws EvalEx {
if (eval instanceof RefEval) {
RefEval re = (RefEval) eval;
return new ValueEval[] { re.getInnerValueEval(), };
}
if (eval instanceof AreaEval) {
AreaEval ae = (AreaEval) eval;
if(!ae.isColumn() && !ae.isRow()) {
throw new EvalEx(ErrorEval.NA);
}
return ae.getValues();
}
// Error handling for lookup_range arg is also unusual
if(eval instanceof NumericValueEval) {
throw new EvalEx(ErrorEval.NA);
}
if (eval instanceof StringEval) {
StringEval se = (StringEval) eval;
Double d = parseDouble(se.getStringValue());
if(d == null) {
// plain string
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
// else looks like a number
throw new EvalEx(ErrorEval.NA);
}
throw new RuntimeException("Unexpected eval type (" + eval.getClass().getName() + ")");
}
private static Double parseDouble(String stringValue) {
// TODO find better home for parseDouble
return Countif.parseDouble(stringValue);
}
private static double evaluateMatchTypeArg(Eval arg, int srcCellRow, short srcCellCol)
throws EvalEx {
Eval match_type = arg;
if(arg instanceof AreaReference) {
AreaEval ae = (AreaEval) arg;
// an area ref can work as a scalar value if it is 1x1
if(ae.isColumn() && ae.isRow()) {
match_type = ae.getValues()[0];
} else {
match_type = chooseSingleElementFromArea(ae, srcCellRow, srcCellCol);
}
}
if(match_type instanceof RefEval) {
RefEval re = (RefEval) match_type;
match_type = re.getInnerValueEval();
}
if(match_type instanceof ErrorEval) {
throw new EvalEx((ErrorEval)match_type);
}
if(match_type instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) match_type;
return ne.getNumberValue();
}
if (match_type instanceof StringEval) {
StringEval se = (StringEval) match_type;
Double d = parseDouble(se.getStringValue());
if(d == null) {
// plain string
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
// if the string parses as a number, it is ok
return d.doubleValue();
}
throw new RuntimeException("Unexpected match_type type (" + match_type.getClass().getName() + ")");
}
/**
* @return zero based index
*/
private static int findIndexOfValue(ValueEval lookupValue, ValueEval[] lookupRange,
boolean matchExact, boolean findLargestLessThanOrEqual) throws EvalEx {
// TODO - wildcard matching when matchExact and lookupValue is text containing * or ?
if(matchExact) {
for (int i = 0; i < lookupRange.length; i++) {
ValueEval lri = lookupRange[i];
if(lri.getClass() != lookupValue.getClass()) {
continue;
}
if(compareValues(lookupValue, lri) == 0) {
return i;
}
}
} else {
// Note - backward iteration
if(findLargestLessThanOrEqual) {
for (int i = lookupRange.length - 1; i>=0; i--) {
ValueEval lri = lookupRange[i];
if(lri.getClass() != lookupValue.getClass()) {
continue;
}
int cmp = compareValues(lookupValue, lri);
if(cmp == 0) {
return i;
}
if(cmp > 0) {
return i;
}
}
} else {
// find smallest greater than or equal to
for (int i = 0; i<lookupRange.length; i++) {
ValueEval lri = lookupRange[i];
if(lri.getClass() != lookupValue.getClass()) {
continue;
}
int cmp = compareValues(lookupValue, lri);
if(cmp == 0) {
return i;
}
if(cmp > 0) {
if(i<1) {
throw new EvalEx(ErrorEval.NA);
}
return i-1;
}
}
}
}
throw new EvalEx(ErrorEval.NA);
}
/**
* This method can only compare a pair of <tt>NumericValueEval</tt>s, <tt>StringEval</tt>s
* or <tt>BoolEval</tt>s
* @return negative for a&lt;b, positive for a&gt;b and 0 for a = b
*/
private static int compareValues(ValueEval a, ValueEval b) {
if (a instanceof StringEval) {
StringEval sa = (StringEval) a;
StringEval sb = (StringEval) b;
return sa.getStringValue().compareToIgnoreCase(sb.getStringValue());
}
if (a instanceof NumericValueEval) {
NumericValueEval na = (NumericValueEval) a;
NumericValueEval nb = (NumericValueEval) b;
return Double.compare(na.getNumberValue(), nb.getNumberValue());
}
if (a instanceof BoolEval) {
boolean ba = ((BoolEval) a).getBooleanValue();
boolean bb = ((BoolEval) b).getBooleanValue();
if(ba == bb) {
return 0;
}
// TRUE > FALSE
if(ba) {
return +1;
}
return -1;
}
throw new RuntimeException("bad eval type (" + a.getClass().getName() + ")");
}
}

View File

@ -14,10 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 22, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
@ -36,6 +33,49 @@ import org.apache.poi.hssf.record.formula.eval.ValueEvalToNumericXlator;
* where the order of operands does not matter
*/
public abstract class MultiOperandNumericFunction extends NumericFunction {
static final double[] EMPTY_DOUBLE_ARRAY = { };
private static class DoubleList {
private double[] _array;
private int _count;
public DoubleList() {
_array = new double[8];
_count = 0;
}
public double[] toArray() {
if(_count < 1) {
return EMPTY_DOUBLE_ARRAY;
}
double[] result = new double[_count];
System.arraycopy(_array, 0, result, 0, _count);
return result;
}
public void add(double[] values) {
int addLen = values.length;
ensureCapacity(_count + addLen);
System.arraycopy(values, 0, _array, _count, addLen);
_count += addLen;
}
private void ensureCapacity(int reqSize) {
if(reqSize > _array.length) {
int newSize = reqSize * 3 / 2; // grow with 50% extra
double[] newArr = new double[newSize];
System.arraycopy(_array, 0, newArr, 0, _count);
_array = newArr;
}
}
public void add(double value) {
ensureCapacity(_count + 1);
_array[_count] = value;
_count++;
}
}
private static final ValueEvalToNumericXlator DEFAULT_NUM_XLATOR =
new ValueEvalToNumericXlator((short) (
@ -76,40 +116,26 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
* from among the list of operands. Blanks and Blank equivalent cells
* are ignored. Error operands or cells containing operands of type
* that are considered invalid and would result in #VALUE! error in
* excel cause this function to return null.
* excel cause this function to return <code>null</code>.
*
* @param operands
* @param srcRow
* @param srcCol
*/
protected double[] getNumberArray(Eval[] operands, int srcRow, short srcCol) {
double[] retval = new double[30];
int count = 0;
outer: do { // goto simulator loop
if (operands.length > getMaxNumOperands()) {
break outer;
return null;
}
else {
DoubleList retval = new DoubleList();
for (int i=0, iSize=operands.length; i<iSize; i++) {
double[] temp = getNumberArray(operands[i], srcRow, srcCol);
if (temp == null) {
retval = null; // error occurred.
break;
return null; // error occurred.
}
retval = putInArray(retval, count, temp);
count += temp.length;
retval.add(temp);
}
}
} while (false); // end goto simulator loop
if (retval != null) {
double[] temp = retval;
retval = new double[count];
System.arraycopy(temp, 0, retval, 0, count);
}
return retval;
return retval.toArray();
}
/**
@ -120,13 +146,11 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
* @param srcCol
*/
protected double[] getNumberArray(Eval operand, int srcRow, short srcCol) {
double[] retval;
int count = 0;
if (operand instanceof AreaEval) {
AreaEval ae = (AreaEval) operand;
ValueEval[] values = ae.getValues();
retval = new double[values.length];
DoubleList retval = new DoubleList();
for (int j=0, jSize=values.length; j<jSize; j++) {
/*
* TODO: For an AreaEval, we are constructing a RefEval
@ -143,91 +167,61 @@ public abstract class MultiOperandNumericFunction extends NumericFunction {
if (ve instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) ve;
retval = putInArray(retval, count++, nve.getNumberValue());
retval.add(nve.getNumberValue());
}
else if (ve instanceof BlankEval) {
// note - blanks are ignored, so returned array will be smaller.
}
else if (ve instanceof BlankEval) {} // ignore operand
else {
retval = null; // null => indicate to calling subclass that error occurred
break;
return null; // indicate to calling subclass that error occurred
}
}
return retval.toArray();
}
else { // for ValueEvals other than AreaEval
retval = new double[1];
// for ValueEvals other than AreaEval
ValueEval ve = singleOperandEvaluate(operand, srcRow, srcCol);
if (ve instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) ve;
retval = putInArray(retval, count++, nve.getNumberValue());
}
else if (ve instanceof BlankEval) {} // ignore operand
else {
retval = null; // null => indicate to calling subclass that error occurred
}
return new double[] { nve.getNumberValue(), };
}
if (retval != null && retval.length >= 1) {
double[] temp = retval;
retval = new double[count];
System.arraycopy(temp, 0, retval, 0, count);
if (ve instanceof BlankEval) {
// ignore blanks
return EMPTY_DOUBLE_ARRAY;
}
return retval;
return null;
}
/**
* puts d at position pos in array arr. If pos is greater than arr, the
* array is dynamically resized (using a simple doubling rule).
* @param arr
* @param pos
* @param d
* Ensures that a two dimensional array has all sub-arrays present and the same length
* @return <code>false</code> if any sub-array is missing, or is of different length
*/
private static double[] putInArray(double[] arr, int pos, double d) {
double[] tarr = arr;
while (pos >= arr.length) {
arr = new double[arr.length << 1];
}
if (tarr.length != arr.length) {
System.arraycopy(tarr, 0, arr, 0, tarr.length);
}
arr[pos] = d;
return arr;
protected static final boolean areSubArraysConsistent(double[][] values) {
if (values == null || values.length < 1) {
// TODO this doesn't seem right. Fix or add comment.
return true;
}
private static double[] putInArray(double[] arr, int pos, double[] d) {
double[] tarr = arr;
while (pos+d.length >= arr.length) {
arr = new double[arr.length << 1];
if (values[0] == null) {
return false;
}
if (tarr.length != arr.length) {
System.arraycopy(tarr, 0, arr, 0, tarr.length);
int outerMax = values.length;
int innerMax = values[0].length;
for (int i=1; i<outerMax; i++) { // note - 'i=1' start at second sub-array
double[] subArr = values[i];
if (subArr == null) {
return false;
}
for (int i=0, iSize=d.length; i<iSize; i++) {
arr[pos+i] = d[i];
if (innerMax != subArr.length) {
return false;
}
return arr;
}
return true;
}
protected static boolean areSubArraysConsistent(double[][] values) {
boolean retval = false;
outer: do {
if (values != null && values.length > 0) {
if (values[0] == null)
break outer;
int len = values[0].length;
for (int i=1, iSize=values.length; i<iSize; i++) {
if (values[i] == null)
break outer;
int tlen = values[i].length;
if (len != tlen) {
break outer;
}
}
}
retval = true;
} while (false);
return retval;
}
}

View File

@ -14,12 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
public class Na extends NotImplementedFunction {
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
/**
* Implementation of Excel function NA()
*
* @author Josh Micich
*/
public final class Na implements Function {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
return ErrorEval.NA;
}
}

View File

@ -40,6 +40,9 @@ public class Rounddown extends NumericFunction {
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
if(ve instanceof ErrorEval) {
return ve;
}
if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve;
d0 = ne.getNumberValue();

View File

@ -40,6 +40,9 @@ public class Roundup extends NumericFunction {
break;
case 2:
ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
if(ve instanceof ErrorEval) {
return ve;
}
if (ve instanceof NumericValueEval) {
NumericValueEval ne = (NumericValueEval) ve;
d0 = ne.getNumberValue();

View File

@ -14,16 +14,228 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on May 15, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
* Implementation for the Excel function SUMPRODUCT<p/>
*
* Syntax : <br/>
* SUMPRODUCT ( array1[, array2[, array3[, ...]]])
* <table border="0" cellpadding="1" cellspacing="0" summary="Parameter descriptions">
* <tr><th>array1, ... arrayN&nbsp;&nbsp;</th><td>typically area references,
* possibly cell references or scalar values</td></tr>
* </table><br/>
*
* Let A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub> represent the element in the <b>i</b>th row <b>j</b>th column
* of the <b>n</b>th array<br/>
* Assuming each array has the same dimensions (W, H), the result is defined as:<br/>
* SUMPRODUCT = &Sigma;<sub><b>i</b>: 1..H</sub> &nbsp;
* (&nbsp; &Sigma;<sub><b>j</b>: 1..W</sub> &nbsp;
* (&nbsp; &Pi;<sub><b>n</b>: 1..N</sub>
* A<b>n</b><sub>(<b>i</b>,<b>j</b>)</sub>&nbsp;
* )&nbsp;
* )
*
* @author Josh Micich
*/
public class Sumproduct extends NotImplementedFunction {
public final class Sumproduct implements Function {
private static final class EvalEx extends Exception {
private final ErrorEval _error;
public EvalEx(ErrorEval error) {
_error = error;
}
public ErrorEval getError() {
return _error;
}
}
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
int maxN = args.length;
if(maxN < 1) {
return ErrorEval.VALUE_INVALID;
}
Eval firstArg = args[0];
try {
if(firstArg instanceof NumericValueEval) {
return evaluateSingleProduct(args);
}
if(firstArg instanceof RefEval) {
return evaluateSingleProduct(args);
}
if(firstArg instanceof AreaEval) {
AreaEval ae = (AreaEval) firstArg;
if(ae.isRow() && ae.isColumn()) {
return evaluateSingleProduct(args);
}
return evaluateAreaSumProduct(args);
}
} catch (EvalEx e) {
return e.getError();
}
throw new RuntimeException("Invalid arg type for SUMPRODUCT: ("
+ firstArg.getClass().getName() + ")");
}
private Eval evaluateSingleProduct(Eval[] evalArgs) throws EvalEx {
int maxN = evalArgs.length;
double term = 1D;
for(int n=0; n<maxN; n++) {
double val = getScalarValue(evalArgs[n]);
term *= val;
}
return new NumberEval(term);
}
private double getScalarValue(Eval arg) throws EvalEx {
Eval eval;
if (arg instanceof RefEval) {
RefEval re = (RefEval) arg;
eval = re.getInnerValueEval();
} else {
eval = arg;
}
if (eval == null) {
throw new RuntimeException("parameter may not be null");
}
if (eval instanceof AreaEval) {
AreaEval ae = (AreaEval) eval;
// an area ref can work as a scalar value if it is 1x1
if(!ae.isColumn() || !ae.isRow()) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
eval = ae.getValues()[0];
}
if (!(eval instanceof ValueEval)) {
throw new RuntimeException("Unexpected value eval class ("
+ eval.getClass().getName() + ")");
}
return getProductTerm((ValueEval) eval, true);
}
private Eval evaluateAreaSumProduct(Eval[] evalArgs) throws EvalEx {
int maxN = evalArgs.length;
AreaEval[] args = new AreaEval[maxN];
try {
System.arraycopy(evalArgs, 0, args, 0, maxN);
} catch (ArrayStoreException e) {
// one of the other args was not an AreaRef
return ErrorEval.VALUE_INVALID;
}
AreaEval firstArg = args[0];
int height = firstArg.getLastRow() - firstArg.getFirstRow() + 1;
int width = firstArg.getLastColumn() - firstArg.getFirstColumn() + 1; // TODO - junit
double[][][] elements = new double[maxN][][];
for (int n = 0; n < maxN; n++) {
elements[n] = evaluateArea(args[n], height, width);
}
double acc = 0;
for(int r=0; r<height; r++) {
for(int c=0; c<width; c++) {
double term = 1D;
for(int n=0; n<maxN; n++) {
term *= elements[n][r][c];
}
acc += term;
}
}
return new NumberEval(acc);
}
/**
* @return a 2-D array of the specified height and width corresponding to the evaluated cell
* values of the specified areaEval
* @throws EvalEx if any ErrorEval value was encountered while evaluating the area
*/
private static double[][] evaluateArea(AreaEval areaEval, int height, int width) throws EvalEx {
int fr =areaEval.getFirstRow();
int fc =areaEval.getFirstColumn();
// check that height and width match
if(areaEval.getLastRow() - fr + 1 != height) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
if(areaEval.getLastColumn() - fc + 1 != width) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
ValueEval[] values = areaEval.getValues();
double[][] result = new double[height][width];
for(int r=0; r<height; r++) {
for(int c=0; c<width; c++) {
ValueEval ve = values[r*width + c];
result[r][c] = getProductTerm(ve, false);
}
}
return result;
}
/**
* Determines a <code>double</code> value for the specified <code>ValueEval</code>.
* @param isScalarProduct <code>false</code> for SUMPRODUCTs over area refs.
* @throws EvalEx if <code>ve</code> represents an error value.
* <p/>
* Note - string values and empty cells are interpreted differently depending on
* <code>isScalarProduct</code>. For scalar products, if any term is blank or a string, the
* error (#VALUE!) is raised. For area (sum)products, if any term is blank or a string, the
* result is zero.
*/
private static double getProductTerm(ValueEval ve, boolean isScalarProduct) throws EvalEx {
if(ve instanceof BlankEval || ve == null) {
// TODO - shouldn't BlankEval.INSTANCE be used always instead of null?
// null seems to occur when the blank cell is part of an area ref (but not reliably)
if(isScalarProduct) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
return 0;
}
if(ve instanceof ErrorEval) {
throw new EvalEx((ErrorEval)ve);
}
if(ve instanceof StringEval) {
if(isScalarProduct) {
throw new EvalEx(ErrorEval.VALUE_INVALID);
}
// Note for area SUMPRODUCTs, string values are interpreted as zero
// even if they would parse as valid numeric values
return 0;
}
if(ve instanceof NumericValueEval) {
NumericValueEval nve = (NumericValueEval) ve;
return nve.getNumberValue();
}
throw new RuntimeException("Unexpected value eval class ("
+ ve.getClass().getName() + ")");
}
}

View File

@ -22,28 +22,34 @@ package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
public class T implements Function {
public final class T implements Function {
public Eval evaluate(Eval[] operands, int srcCellRow, short srcCellCol) {
ValueEval retval = null;
switch (operands.length) {
public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) {
switch (args.length) {
default:
retval = ErrorEval.VALUE_INVALID;
break;
return ErrorEval.VALUE_INVALID;
case 1:
if (operands[0] instanceof StringEval
|| operands[0] instanceof ErrorEval) {
retval = (ValueEval) operands[0];
break;
}
else if (operands[0] instanceof ErrorEval) {
retval = StringEval.EMPTY_INSTANCE;
}
}
return retval;
Eval arg = args[0];
if (arg instanceof RefEval) {
RefEval re = (RefEval) arg;
arg = re.getInnerValueEval();
}
if (arg instanceof StringEval) {
// Text values are returned unmodified
return arg;
}
if (arg instanceof ErrorEval) {
// Error values also returned unmodified
return arg;
}
// for all other argument types the result is empty string
return StringEval.EMPTY_INSTANCE;
}
}

View File

@ -337,6 +337,11 @@ public class HSSFFormulaEvaluator {
else if (eval instanceof BlankEval) {
retval = new CellValue(HSSFCell.CELL_TYPE_BLANK);
}
else if (eval instanceof ErrorEval) {
retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
retval.setErrorValue((byte)((ErrorEval)eval).getErrorCode());
// retval.setRichTextStringValue(new HSSFRichTextString("#An error occurred. check cell.getErrorCode()"));
}
else {
retval = new CellValue(HSSFCell.CELL_TYPE_ERROR);
}
@ -401,7 +406,7 @@ public class HSSFFormulaEvaluator {
short rownum = ptg.getRow();
HSSFRow row = sheet.getRow(rownum);
HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
pushRef2DEval(ptg, stack, cell, row, sheet, workbook);
stack.push(createRef2DEval(ptg, cell, row, sheet, workbook));
}
else if (ptgs[i] instanceof Ref3DPtg) {
Ref3DPtg ptg = (Ref3DPtg) ptgs[i];
@ -411,7 +416,7 @@ public class HSSFFormulaEvaluator {
HSSFSheet xsheet = workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(ptg.getExternSheetIndex()));
HSSFRow row = xsheet.getRow(rownum);
HSSFCell cell = (row != null) ? row.getCell(colnum) : null;
pushRef3DEval(ptg, stack, cell, row, xsheet, workbook);
stack.push(createRef3DEval(ptg, cell, row, xsheet, workbook));
}
else if (ptgs[i] instanceof AreaPtg) {
AreaPtg ap = (AreaPtg) ptgs[i];
@ -544,104 +549,77 @@ public class HSSFFormulaEvaluator {
* @param workbook
*/
protected static ValueEval getEvalForCell(HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
ValueEval retval = BlankEval.INSTANCE;
if (cell != null) {
if (cell == null) {
return BlankEval.INSTANCE;
}
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
retval = new NumberEval(cell.getNumericCellValue());
break;
return new NumberEval(cell.getNumericCellValue());
case HSSFCell.CELL_TYPE_STRING:
retval = new StringEval(cell.getRichStringCellValue().getString());
break;
return new StringEval(cell.getRichStringCellValue().getString());
case HSSFCell.CELL_TYPE_FORMULA:
retval = internalEvaluate(cell, row, sheet, workbook);
break;
return internalEvaluate(cell, row, sheet, workbook);
case HSSFCell.CELL_TYPE_BOOLEAN:
retval = cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE;
break;
return BoolEval.valueOf(cell.getBooleanCellValue());
case HSSFCell.CELL_TYPE_BLANK:
retval = BlankEval.INSTANCE;
break;
return BlankEval.INSTANCE;
case HSSFCell.CELL_TYPE_ERROR:
retval = ErrorEval.UNKNOWN_ERROR; // TODO: think about this...
break;
return ErrorEval.valueOf(cell.getErrorCellValue());
}
}
return retval;
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
* create a Ref2DEval for ReferencePtg and push it on the stack.
* Creates a Ref2DEval for ReferencePtg.
* Non existent cells are treated as RefEvals containing BlankEval.
* @param ptg
* @param stack
* @param cell
* @param sheet
* @param workbook
*/
protected static void pushRef2DEval(ReferencePtg ptg, Stack stack,
HSSFCell cell, HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
if (cell != null)
private static Ref2DEval createRef2DEval(ReferencePtg ptg, HSSFCell cell,
HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
if (cell == null) {
return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
}
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
stack.push(new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
break;
return new Ref2DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
case HSSFCell.CELL_TYPE_STRING:
stack.push(new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
break;
return new Ref2DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
case HSSFCell.CELL_TYPE_FORMULA:
stack.push(new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
break;
return new Ref2DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
case HSSFCell.CELL_TYPE_BOOLEAN:
stack.push(new Ref2DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
break;
return new Ref2DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
case HSSFCell.CELL_TYPE_BLANK:
stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
break;
return new Ref2DEval(ptg, BlankEval.INSTANCE, false);
case HSSFCell.CELL_TYPE_ERROR:
stack.push(new Ref2DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
break;
}
else {
stack.push(new Ref2DEval(ptg, BlankEval.INSTANCE, false));
return new Ref2DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
}
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**
* create a Ref3DEval for Ref3DPtg and push it on the stack.
*
* @param ptg
* @param stack
* @param cell
* @param sheet
* @param workbook
* create a Ref3DEval for Ref3DPtg.
*/
protected static void pushRef3DEval(Ref3DPtg ptg, Stack stack, HSSFCell cell,
private static Ref3DEval createRef3DEval(Ref3DPtg ptg, HSSFCell cell,
HSSFRow row, HSSFSheet sheet, HSSFWorkbook workbook) {
if (cell != null)
if (cell == null) {
return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
}
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
stack.push(new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false));
break;
return new Ref3DEval(ptg, new NumberEval(cell.getNumericCellValue()), false);
case HSSFCell.CELL_TYPE_STRING:
stack.push(new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false));
break;
return new Ref3DEval(ptg, new StringEval(cell.getRichStringCellValue().getString()), false);
case HSSFCell.CELL_TYPE_FORMULA:
stack.push(new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true));
break;
return new Ref3DEval(ptg, internalEvaluate(cell, row, sheet, workbook), true);
case HSSFCell.CELL_TYPE_BOOLEAN:
stack.push(new Ref3DEval(ptg, cell.getBooleanCellValue() ? BoolEval.TRUE : BoolEval.FALSE, false));
break;
return new Ref3DEval(ptg, BoolEval.valueOf(cell.getBooleanCellValue()), false);
case HSSFCell.CELL_TYPE_BLANK:
stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
break;
return new Ref3DEval(ptg, BlankEval.INSTANCE, false);
case HSSFCell.CELL_TYPE_ERROR:
stack.push(new Ref3DEval(ptg, ErrorEval.UNKNOWN_ERROR, false)); // TODO: think abt this
break;
}
else {
stack.push(new Ref3DEval(ptg, BlankEval.INSTANCE, false));
return new Ref3DEval(ptg, ErrorEval.valueOf(cell.getErrorCellValue()), false);
}
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/**

View File

@ -1,147 +0,0 @@
/*
* 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 11, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import java.io.FileInputStream;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.functions.TestMathX;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.CellReference;
/**
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*
*/
public class GenericFormulaTestCase extends TestCase {
protected final static String FILENAME = System.getProperty("HSSF.testdata.path")+ "/FormulaEvalTestData.xls";
protected static HSSFWorkbook workbook = null;
protected CellReference beginCell;
protected int getBeginRow() {
return beginCell.getRow();
}
protected short getBeginCol() {
return beginCell.getCol();
}
protected final HSSFCell getExpectedValueCell(HSSFSheet sheet, HSSFRow row, HSSFCell cell) {
HSSFCell retval = null;
if (sheet != null) {
row = sheet.getRow(row.getRowNum()+1);
if (row != null) {
retval = row.getCell(cell.getCellNum());
}
}
return retval;
}
protected void assertEquals(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
if (expected != null && actual!=null) {
if (expected!=null && expected.getCellType() == HSSFCell.CELL_TYPE_STRING) {
String value = expected.getRichStringCellValue().getString();
if (value.startsWith("#")) {
expected.setCellType(HSSFCell.CELL_TYPE_ERROR);
}
}
if (!(expected == null || actual == null)) {
switch (expected.getCellType()) {
case HSSFCell.CELL_TYPE_BLANK:
assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType());
break;
case HSSFCell.CELL_TYPE_BOOLEAN:
assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType());
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
break;
case HSSFCell.CELL_TYPE_ERROR:
assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType()); // TODO: check if exact error matches
break;
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
case HSSFCell.CELL_TYPE_NUMERIC:
assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType());
TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR);
// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue());
// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue());
// assertTrue(msg, delta <= pctExpected);
break;
case HSSFCell.CELL_TYPE_STRING:
assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType());
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
break;
}
}
else {
throw new AssertionFailedError("expected: " + expected + " got:" + actual);
}
}
}
public GenericFormulaTestCase(String beginCell) throws Exception {
super("genericTest");
if (workbook == null) {
FileInputStream fin = new FileInputStream( FILENAME );
workbook = new HSSFWorkbook( fin );
fin.close();
}
this.beginCell = new CellReference(beginCell);
}
public void setUp() {
}
public void genericTest() throws Exception {
HSSFSheet s = workbook.getSheetAt( 0 );
HSSFRow r = s.getRow(getBeginRow());
short endcolnum = r.getLastCellNum();
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(s, workbook);
evaluator.setCurrentRow(r);
HSSFCell c = null;
for (short colnum=getBeginCol(); colnum < endcolnum; colnum++) {
try {
c = r.getCell(colnum);
if (c==null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA)
continue;
HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
HSSFCell expectedValueCell = getExpectedValueCell(s, r, c);
assertEquals("Formula: " + c.getCellFormula()
+ " @ " + getBeginRow() + ":" + colnum,
expectedValueCell, actualValue);
} catch (RuntimeException re) {
throw new RuntimeException("CELL["+getBeginRow()+","+colnum+"]: "+re.getMessage(), re);
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
* 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 11, 2005
*
*/
package org.apache.poi.hssf.record.formula.eval;
import junit.framework.TestSuite;
/**
* This is a test of all the Eval functions we have implemented.
* Add newly implemented Eval functions in here to have them
* tested.
* For newly implemented functions,
* @see org.apache.poi.hssf.record.formula.functions.TestEverything
*
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*/
public class TestEverything extends TestSuite {
public static TestSuite suite() throws Exception {
TestSuite suite = new TestSuite("Tests for OperationEval concrete implementation classes.");
suite.addTest(new GenericFormulaTestCase("D23")); // Add
suite.addTest(new GenericFormulaTestCase("D27")); // ConcatEval
suite.addTest(new GenericFormulaTestCase("D31")); // DivideEval
suite.addTest(new GenericFormulaTestCase("D35")); // EqualEval
suite.addTest(new GenericFormulaTestCase("D39")); // GreaterEqualEval
suite.addTest(new GenericFormulaTestCase("D43")); // GreaterThanEval
suite.addTest(new GenericFormulaTestCase("D47")); // LessEqualEval
suite.addTest(new GenericFormulaTestCase("D51")); // LessThanEval
suite.addTest(new GenericFormulaTestCase("D55")); // MultiplyEval
suite.addTest(new GenericFormulaTestCase("D59")); // NotEqualEval
suite.addTest(new GenericFormulaTestCase("D63")); // PowerEval
suite.addTest(new GenericFormulaTestCase("D67")); // SubtractEval
suite.addTest(new GenericFormulaTestCase("D71")); // UnaryMinusEval
suite.addTest(new GenericFormulaTestCase("D75")); // UnaryPlusEval
// Add newly implemented Eval functions here
// (Formula functions go in
// @see org.apache.poi.hssf.record.formula.functions.TestEverything )
return suite;
}
}

View File

@ -0,0 +1,329 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.poi.hssf.record.formula.eval;
import java.io.FileInputStream;
import java.io.PrintStream;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.functions.TestMathX;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/**
* Tests formulas and operators as loaded from a test data spreadsheet.<p/>
* This class does not test implementors of <tt>Function</tt> and <tt>OperationEval</tt> in
* isolation. Much of the evaluation engine (i.e. <tt>HSSFFormulaEvaluator</tt>, ...) gets
* exercised as well. Tests for bug fixes and specific/tricky behaviour can be found in the
* corresponding test class (<tt>TestXxxx</tt>) of the target (<tt>Xxxx</tt>) implementor,
* where execution can be observed more easily.
*
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*/
public final class TestFormulasFromSpreadsheet extends TestCase {
private static final class Result {
public static final int SOME_EVALUATIONS_FAILED = -1;
public static final int ALL_EVALUATIONS_SUCCEEDED = +1;
public static final int NO_EVALUATIONS_FOUND = 0;
}
/**
* This class defines constants for navigating around the test data spreadsheet used for these tests.
*/
private static final class SS {
/**
* Name of the test spreadsheet (found in the standard test data folder)
*/
public final static String FILENAME = "FormulaEvalTestData.xls";
/**
* Row (zero-based) in the test spreadsheet where the operator examples start.
*/
public static final int START_OPERATORS_ROW_INDEX = 22; // Row '23'
/**
* Row (zero-based) in the test spreadsheet where the function examples start.
*/
public static final int START_FUNCTIONS_ROW_INDEX = 83; // Row '84'
/**
* Index of the column that contains the function names
*/
public static final short COLUMN_INDEX_FUNCTION_NAME = 1; // Column 'B'
/**
* Used to indicate when there are no more functions left
*/
public static final String FUNCTION_NAMES_END_SENTINEL = "<END-OF-FUNCTIONS>";
/**
* Index of the column where the test values start (for each function)
*/
public static final short COLUMN_INDEX_FIRST_TEST_VALUE = 3; // Column 'D'
/**
* Each function takes 4 rows in the test spreadsheet
*/
public static final int NUMBER_OF_ROWS_PER_FUNCTION = 4;
}
private HSSFWorkbook workbook;
private HSSFSheet sheet;
// Note - multiple failures are aggregated before ending.
// If one or more functions fail, a single AssertionFailedError is thrown at the end
private int _functionFailureCount;
private int _functionSuccessCount;
private int _evaluationFailureCount;
private int _evaluationSuccessCount;
private static final HSSFCell getExpectedValueCell(HSSFRow row, short columnIndex) {
if (row == null) {
return null;
}
return row.getCell(columnIndex);
}
private static void confirmExpectedResult(String msg, HSSFCell expected, HSSFFormulaEvaluator.CellValue actual) {
if (expected == null) {
throw new AssertionFailedError(msg + " - Bad setup data expected value is null");
}
if(actual == null) {
throw new AssertionFailedError(msg + " - actual value was null");
}
if (expected.getCellType() == HSSFCell.CELL_TYPE_STRING) {
String value = expected.getRichStringCellValue().getString();
if (value.startsWith("#")) {
// TODO - this code never called
expected.setCellType(HSSFCell.CELL_TYPE_ERROR);
// expected.setCellErrorValue(...?);
}
}
switch (expected.getCellType()) {
case HSSFCell.CELL_TYPE_BLANK:
assertEquals(msg, HSSFCell.CELL_TYPE_BLANK, actual.getCellType());
break;
case HSSFCell.CELL_TYPE_BOOLEAN:
assertEquals(msg, HSSFCell.CELL_TYPE_BOOLEAN, actual.getCellType());
assertEquals(msg, expected.getBooleanCellValue(), actual.getBooleanValue());
break;
case HSSFCell.CELL_TYPE_ERROR:
assertEquals(msg, HSSFCell.CELL_TYPE_ERROR, actual.getCellType());
if(false) { // TODO: fix ~45 functions which are currently returning incorrect error values
assertEquals(msg, expected.getErrorCellValue(), actual.getErrorValue());
}
break;
case HSSFCell.CELL_TYPE_FORMULA: // will never be used, since we will call method after formula evaluation
throw new AssertionFailedError("Cannot expect formula as result of formula evaluation: " + msg);
case HSSFCell.CELL_TYPE_NUMERIC:
assertEquals(msg, HSSFCell.CELL_TYPE_NUMERIC, actual.getCellType());
TestMathX.assertEquals(msg, expected.getNumericCellValue(), actual.getNumberValue(), TestMathX.POS_ZERO, TestMathX.DIFF_TOLERANCE_FACTOR);
// double delta = Math.abs(expected.getNumericCellValue()-actual.getNumberValue());
// double pctExpected = Math.abs(0.00001*expected.getNumericCellValue());
// assertTrue(msg, delta <= pctExpected);
break;
case HSSFCell.CELL_TYPE_STRING:
assertEquals(msg, HSSFCell.CELL_TYPE_STRING, actual.getCellType());
assertEquals(msg, expected.getRichStringCellValue().getString(), actual.getRichTextStringValue().getString());
break;
}
}
protected void setUp() throws Exception {
if (workbook == null) {
String filePath = System.getProperty("HSSF.testdata.path")+ "/" + SS.FILENAME;
FileInputStream fin = new FileInputStream( filePath );
workbook = new HSSFWorkbook( fin );
sheet = workbook.getSheetAt( 0 );
}
_functionFailureCount = 0;
_functionSuccessCount = 0;
_evaluationFailureCount = 0;
_evaluationSuccessCount = 0;
}
public void testFunctionsFromTestSpreadsheet() {
processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, null);
processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, null);
// example for debugging individual functions/operators:
// processFunctionGroup(SS.START_OPERATORS_ROW_INDEX, "ConcatEval");
// processFunctionGroup(SS.START_FUNCTIONS_ROW_INDEX, "AVERAGE");
// confirm results
String successMsg = "There were "
+ _evaluationSuccessCount + " successful evaluation(s) and "
+ _functionSuccessCount + " function(s) without error";
if(_functionFailureCount > 0) {
String msg = _functionFailureCount + " function(s) failed in "
+ _evaluationFailureCount + " evaluation(s). " + successMsg;
throw new AssertionFailedError(msg);
}
if(false) { // normally no output for successful tests
System.out.println(getClass().getName() + ": " + successMsg);
}
}
/**
* @param startRowIndex row index in the spreadsheet where the first function/operator is found
* @param testFocusFunctionName name of a single function/operator to test alone.
* Typically pass <code>null</code> to test all functions
*/
private void processFunctionGroup(int startRowIndex, String testFocusFunctionName) {
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet, workbook);
int rowIndex = startRowIndex;
while (true) {
HSSFRow r = sheet.getRow(rowIndex);
String targetFunctionName = getTargetFunctionName(r);
if(targetFunctionName == null) {
throw new AssertionFailedError("Test spreadsheet cell empty on row ("
+ (rowIndex+1) + "). Expected function name or '"
+ SS.FUNCTION_NAMES_END_SENTINEL + "'");
}
if(targetFunctionName.equals(SS.FUNCTION_NAMES_END_SENTINEL)) {
// found end of functions list
break;
}
if(testFocusFunctionName == null || targetFunctionName.equalsIgnoreCase(testFocusFunctionName)) {
// expected results are on the row below
HSSFRow expectedValuesRow = sheet.getRow(rowIndex + 1);
if(expectedValuesRow == null) {
int missingRowNum = rowIndex + 2; //+1 for 1-based, +1 for next row
throw new AssertionFailedError("Missing expected values row for function '"
+ targetFunctionName + " (row " + missingRowNum + ")");
}
switch(processFunctionRow(evaluator, targetFunctionName, r, expectedValuesRow)) {
case Result.ALL_EVALUATIONS_SUCCEEDED: _functionSuccessCount++; break;
case Result.SOME_EVALUATIONS_FAILED: _functionFailureCount++; break;
default:
throw new RuntimeException("unexpected result");
case Result.NO_EVALUATIONS_FOUND: // do nothing
}
}
rowIndex += SS.NUMBER_OF_ROWS_PER_FUNCTION;
}
}
/**
*
* @return a constant from the local Result class denoting whether there were any evaluation
* cases, and whether they all succeeded.
*/
private int processFunctionRow(HSSFFormulaEvaluator evaluator, String targetFunctionName,
HSSFRow formulasRow, HSSFRow expectedValuesRow) {
int result = Result.NO_EVALUATIONS_FOUND; // so far
short endcolnum = formulasRow.getLastCellNum();
evaluator.setCurrentRow(formulasRow);
// iterate across the row for all the evaluation cases
for (short colnum=SS.COLUMN_INDEX_FIRST_TEST_VALUE; colnum < endcolnum; colnum++) {
HSSFCell c = formulasRow.getCell(colnum);
if (c == null || c.getCellType() != HSSFCell.CELL_TYPE_FORMULA) {
continue;
}
HSSFFormulaEvaluator.CellValue actualValue = evaluator.evaluate(c);
HSSFCell expectedValueCell = getExpectedValueCell(expectedValuesRow, colnum);
try {
confirmExpectedResult("Function '" + targetFunctionName + "': Formula: " + c.getCellFormula() + " @ " + formulasRow.getRowNum() + ":" + colnum,
expectedValueCell, actualValue);
_evaluationSuccessCount ++;
if(result != Result.SOME_EVALUATIONS_FAILED) {
result = Result.ALL_EVALUATIONS_SUCCEEDED;
}
} catch (AssertionFailedError e) {
_evaluationFailureCount ++;
printShortStackTrace(System.err, e);
result = Result.SOME_EVALUATIONS_FAILED;
}
}
return result;
}
/**
* Useful to keep output concise when expecting many failures to be reported by this test case
*/
private static void printShortStackTrace(PrintStream ps, AssertionFailedError e) {
StackTraceElement[] stes = e.getStackTrace();
int startIx = 0;
// skip any top frames inside junit.framework.Assert
while(startIx<stes.length) {
if(!stes[startIx].getClassName().equals(Assert.class.getName())) {
break;
}
startIx++;
}
// skip bottom frames (part of junit framework)
int endIx = startIx+1;
while(endIx < stes.length) {
if(stes[endIx].getClassName().equals(TestCase.class.getName())) {
break;
}
endIx++;
}
if(startIx >= endIx) {
// something went wrong. just print the whole stack trace
e.printStackTrace(ps);
}
endIx -= 4; // skip 4 frames of reflection invocation
ps.println(e.toString());
for(int i=startIx; i<endIx; i++) {
ps.println("\tat " + stes[i].toString());
}
}
/**
* @return <code>null</code> if cell is missing, empty or blank
*/
private static String getTargetFunctionName(HSSFRow r) {
if(r == null) {
System.err.println("Warning - given null row, can't figure out function name");
return null;
}
HSSFCell cell = r.getCell(SS.COLUMN_INDEX_FUNCTION_NAME);
if(cell == null) {
System.err.println("Warning - Row " + r.getRowNum() + " has no cell " + SS.COLUMN_INDEX_FUNCTION_NAME + ", can't figure out function name");
return null;
}
if(cell.getCellType() == HSSFCell.CELL_TYPE_BLANK) {
return null;
}
if(cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
return cell.getRichStringCellValue().getString();
}
throw new AssertionFailedError("Bad cell type for 'function name' column: ("
+ cell.getCellType() + ") row (" + (r.getRowNum() +1) + ")");
}
}

View File

@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.record.formula.functions.NumericFunctionInvoker;
import junit.framework.TestCase;
/**
* Test for unary plus operator evaluator.
*
* @author Josh Micich
*/
public final class TestUnaryPlusEval extends TestCase {
/**
* Test for bug observable at svn revision 618865 (5-Feb-2008)<br/>
* The code for handling column operands had been copy-pasted from the row handling code.
*/
public void testColumnOperand() {
short firstRow = (short)8;
short lastRow = (short)12;
short colNum = (short)5;
AreaPtg areaPtg = new AreaPtg(firstRow, lastRow, colNum, colNum, false, false, false, false);
ValueEval[] values = {
new NumberEval(27),
new NumberEval(29),
new NumberEval(35), // value in row 10
new NumberEval(37),
new NumberEval(38),
};
Eval areaEval = new Area2DEval(areaPtg, values);
Eval[] args = {
areaEval,
};
double result = NumericFunctionInvoker.invoke(new UnaryPlusEval(new UnaryPlusPtg()), args, 10, (short)20);
assertEquals(35, result, 0);
}
}

View File

@ -36,8 +36,11 @@ public final class AllIndividualFunctionEvaluationTests {
result.addTestSuite(TestFinanceLib.class);
result.addTestSuite(TestIndex.class);
result.addTestSuite(TestMathX.class);
result.addTestSuite(TestMatch.class);
result.addTestSuite(TestRowCol.class);
result.addTestSuite(TestSumproduct.class);
result.addTestSuite(TestStatsLib.class);
result.addTestSuite(TestTFunc.class);
return result;
}

View File

@ -23,13 +23,14 @@ import junit.framework.AssertionFailedError;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
/**
* Test helper class for invoking functions with numeric results.
*
* @author Josh Micich
*/
final class NumericFunctionInvoker {
public final class NumericFunctionInvoker {
private NumericFunctionInvoker() {
// no instances of this class
@ -59,13 +60,37 @@ final class NumericFunctionInvoker {
+ ") failed: " + e.getMessage());
}
}
/**
* Invokes the specified operator with the arguments.
* <p/>
* This method cannot be used for confirming error return codes. Any non-numeric evaluation
* result causes the current junit test to fail.
*/
public static double invoke(OperationEval f, Eval[] args, int srcCellRow, int srcCellCol) {
try {
return invokeInternal(f, args, srcCellRow, srcCellCol);
} catch (NumericEvalEx e) {
throw new AssertionFailedError("Evaluation of function (" + f.getClass().getName()
+ ") failed: " + e.getMessage());
}
}
/**
* Formats nicer error messages for the junit output
*/
private static double invokeInternal(Function f, Eval[] args, int srcCellRow, int srcCellCol)
private static double invokeInternal(Object target, Eval[] args, int srcCellRow, int srcCellCol)
throws NumericEvalEx {
Eval evalResult = f.evaluate(args, srcCellRow, (short)srcCellCol);
Eval evalResult;
// TODO - make OperationEval extend Function
if (target instanceof Function) {
Function ff = (Function) target;
evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
} else {
OperationEval ff = (OperationEval) target;
evalResult = ff.evaluate(args, srcCellRow, (short)srcCellCol);
}
if(evalResult == null) {
throw new NumericEvalEx("Result object was null");
}
@ -89,6 +114,9 @@ final class NumericFunctionInvoker {
if(errorCodesAreEqual(ee, ErrorEval.UNKNOWN_ERROR)) {
return "Unknown error";
}
if(errorCodesAreEqual(ee, ErrorEval.VALUE_INVALID)) {
return "Error code: #VALUE! (invalid value)";
}
return "Error code=" + ee.getErrorCode();
}
private static boolean errorCodesAreEqual(ErrorEval a, ErrorEval b) {

View File

@ -1,48 +0,0 @@
/*
* 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 11, 2005
*
*/
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.GenericFormulaTestCase;
import junit.framework.TestSuite;
/**
* This is a test of all the normal formula functions we have implemented.
* It should pick up newly implemented functions which are correctly added
* to the test formula excel file, but tweak the rows below if you
* add any past the end of what's currently checked.
* For newly implemented eval functions,
* @see org.apache.poi.hssf.record.formula.eval.TestEverything
*
* @author Amol S. Deshmukh &lt; amolweb at ya hoo dot com &gt;
*/
public class TestEverything extends TestSuite {
public static TestSuite suite() throws Exception {
TestSuite suite = new TestSuite("Tests for individual function classes");
String s;
for(int i=80; i<1485;i=i+4) {
s = "D"+Integer.toString(i).trim();
suite.addTest(new GenericFormulaTestCase(s));
}
// suite.addTest(new GenericFormulaTestCase("D1164"));
return suite;
}
}

View File

@ -0,0 +1,184 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
/**
* Test cases for MATCH()
*
* @author Josh Micich
*/
public final class TestMatch extends TestCase {
/** less than or equal to */
private static final NumberEval MATCH_LARGEST_LTE = new NumberEval(1);
private static final NumberEval MATCH_EXACT = new NumberEval(0);
/** greater than or equal to */
private static final NumberEval MATCH_SMALLEST_GTE = new NumberEval(-1);
private static Eval invokeMatch(Eval lookup_value, Eval lookup_array, Eval match_type) {
Eval[] args = { lookup_value, lookup_array, match_type, };
return new Match().evaluate(args, -1, (short)-1);
}
private static void confirmInt(int expected, Eval actualEval) {
if(!(actualEval instanceof NumericValueEval)) {
fail("Expected numeric result");
}
NumericValueEval nve = (NumericValueEval)actualEval;
assertEquals(expected, nve.getNumberValue(), 0);
}
public void testSimpleNumber() {
ValueEval[] values = {
new NumberEval(4),
new NumberEval(5),
new NumberEval(10),
new NumberEval(10),
new NumberEval(25),
};
AreaPtg areaPtg = new AreaPtg("A1:A5");
Area2DEval ae = new Area2DEval(areaPtg, values);
confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE));
confirmInt(2, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
confirmInt(4, invokeMatch(new NumberEval(10), ae, MATCH_LARGEST_LTE));
confirmInt(3, invokeMatch(new NumberEval(10), ae, MATCH_EXACT));
confirmInt(4, invokeMatch(new NumberEval(20), ae, MATCH_LARGEST_LTE));
assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT));
}
public void testReversedNumber() {
ValueEval[] values = {
new NumberEval(25),
new NumberEval(10),
new NumberEval(10),
new NumberEval(10),
new NumberEval(4),
};
AreaPtg areaPtg = new AreaPtg("A1:A5");
Area2DEval ae = new Area2DEval(areaPtg, values);
confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_SMALLEST_GTE));
confirmInt(2, invokeMatch(new NumberEval(10), ae, MATCH_EXACT));
confirmInt(4, invokeMatch(new NumberEval(9), ae, MATCH_SMALLEST_GTE));
confirmInt(1, invokeMatch(new NumberEval(20), ae, MATCH_SMALLEST_GTE));
assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(20), ae, MATCH_EXACT));
assertEquals(ErrorEval.NA, invokeMatch(new NumberEval(26), ae, MATCH_SMALLEST_GTE));
}
public void testSimpleString() {
ValueEval[] values = {
new StringEval("Albert"),
new StringEval("Charles"),
new StringEval("Ed"),
new StringEval("Greg"),
new StringEval("Ian"),
};
AreaPtg areaPtg = new AreaPtg("A1:A5");
Area2DEval ae = new Area2DEval(areaPtg, values);
// Note String comparisons are case insensitive
confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_LARGEST_LTE));
confirmInt(3, invokeMatch(new StringEval("eD"), ae, MATCH_LARGEST_LTE));
confirmInt(3, invokeMatch(new StringEval("Ed"), ae, MATCH_EXACT));
confirmInt(3, invokeMatch(new StringEval("ed"), ae, MATCH_EXACT));
confirmInt(4, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT));
}
public void testSimpleBoolean() {
ValueEval[] values = {
BoolEval.FALSE,
BoolEval.FALSE,
BoolEval.TRUE,
BoolEval.TRUE,
};
AreaPtg areaPtg = new AreaPtg("A1:A4");
Area2DEval ae = new Area2DEval(areaPtg, values);
// Note String comparisons are case insensitive
confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE));
confirmInt(1, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT));
confirmInt(4, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE));
confirmInt(3, invokeMatch(BoolEval.TRUE, ae, MATCH_EXACT));
}
public void testHeterogeneous() {
ValueEval[] values = {
new NumberEval(4),
BoolEval.FALSE,
new NumberEval(5),
new StringEval("Albert"),
BoolEval.FALSE,
BoolEval.TRUE,
new NumberEval(10),
new StringEval("Charles"),
new StringEval("Ed"),
new NumberEval(10),
new NumberEval(25),
BoolEval.TRUE,
new StringEval("Ed"),
};
AreaPtg areaPtg = new AreaPtg("A1:A13");
Area2DEval ae = new Area2DEval(areaPtg, values);
assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Aaron"), ae, MATCH_LARGEST_LTE));
confirmInt(5, invokeMatch(BoolEval.FALSE, ae, MATCH_LARGEST_LTE));
confirmInt(2, invokeMatch(BoolEval.FALSE, ae, MATCH_EXACT));
confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_LARGEST_LTE));
confirmInt(3, invokeMatch(new NumberEval(5), ae, MATCH_EXACT));
confirmInt(8, invokeMatch(new StringEval("CHARLES"), ae, MATCH_EXACT));
confirmInt(4, invokeMatch(new StringEval("Ben"), ae, MATCH_LARGEST_LTE));
confirmInt(13, invokeMatch(new StringEval("ED"), ae, MATCH_LARGEST_LTE));
confirmInt(9, invokeMatch(new StringEval("ED"), ae, MATCH_EXACT));
confirmInt(13, invokeMatch(new StringEval("Hugh"), ae, MATCH_LARGEST_LTE));
assertEquals(ErrorEval.NA, invokeMatch(new StringEval("Hugh"), ae, MATCH_EXACT));
confirmInt(11, invokeMatch(new NumberEval(30), ae, MATCH_LARGEST_LTE));
confirmInt(12, invokeMatch(BoolEval.TRUE, ae, MATCH_LARGEST_LTE));
}
}

View File

@ -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.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import junit.framework.TestCase;
/**
* Test cases for ROUND(), ROUNDUP(), ROUNDDOWN()
*
* @author Josh Micich
*/
public final class TestRoundFuncs extends TestCase {
public void testRounddownWithStringArg() {
Eval strArg = new StringEval("abc");
Eval[] args = { strArg, new NumberEval(2), };
Eval result = new Rounddown().evaluate(args, -1, (short)-1);
assertEquals(ErrorEval.VALUE_INVALID, result);
}
public void testRoundupWithStringArg() {
Eval strArg = new StringEval("abc");
Eval[] args = { strArg, new NumberEval(2), };
Eval result = new Roundup().evaluate(args, -1, (short)-1);
assertEquals(ErrorEval.VALUE_INVALID, result);
}
}

View File

@ -0,0 +1,120 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.ReferencePtg;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import junit.framework.TestCase;
/**
* Test cases for SUMPRODUCT()
*
* @author Josh Micich
*/
public final class TestSumproduct extends TestCase {
private static Eval invokeSumproduct(Eval[] args) {
// srcCellRow and srcCellColumn are ignored by SUMPRODUCT
return new Sumproduct().evaluate(args, -1, (short)-1);
}
private static void confirmDouble(double expected, Eval actualEval) {
if(!(actualEval instanceof NumericValueEval)) {
fail("Expected numeric result");
}
NumericValueEval nve = (NumericValueEval)actualEval;
assertEquals(expected, nve.getNumberValue(), 0);
}
public void testScalarSimple() {
RefEval refEval = new Ref2DEval(new ReferencePtg("A1"), new NumberEval(3), true);
Eval[] args = {
refEval,
new NumberEval(2),
};
Eval result = invokeSumproduct(args);
confirmDouble(6D, result);
}
public void testAreaSimple() {
AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3);
AreaEval aeB = EvalFactory.createAreaEval("B1:B3", 1, 3);
ValueEval[] aValues = aeA.getValues();
ValueEval[] bValues = aeB.getValues();
aValues[0] = new NumberEval(2);
aValues[1] = new NumberEval(4);
aValues[2] = new NumberEval(5);
bValues[0] = new NumberEval(3);
bValues[1] = new NumberEval(6);
bValues[2] = new NumberEval(7);
Eval[] args = { aeA, aeB, };
Eval result = invokeSumproduct(args);
confirmDouble(65D, result);
}
/**
* For scalar products, the terms may be 1x1 area refs
*/
public void testOneByOneArea() {
AreaEval ae = EvalFactory.createAreaEval("A1:A1", 1, 1);
ae.getValues()[0] = new NumberEval(7);
Eval[] args = {
ae,
new NumberEval(2),
};
Eval result = invokeSumproduct(args);
confirmDouble(14D, result);
}
public void testMismatchAreaDimensions() {
AreaEval aeA = EvalFactory.createAreaEval("A1:A3", 1, 3);
AreaEval aeB = EvalFactory.createAreaEval("B1:D1", 3, 1);
Eval[] args;
args = new Eval[] { aeA, aeB, };
assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args));
args = new Eval[] { aeA, new NumberEval(5), };
assertEquals(ErrorEval.VALUE_INVALID, invokeSumproduct(args));
}
public void testAreaWithErrorCell() {
AreaEval aeA = EvalFactory.createAreaEval("A1:A2", 1, 2);
AreaEval aeB = EvalFactory.createAreaEval("B1:B2", 1, 2);
aeB.getValues()[1] = ErrorEval.REF_INVALID;
Eval[] args = { aeA, aeB, };
assertEquals(ErrorEval.REF_INVALID, invokeSumproduct(args));
}
}

View File

@ -0,0 +1,118 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.ReferencePtg;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.Ref2DEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import junit.framework.TestCase;
/**
* Test cases for Excel function T()
*
* @author Josh Micich
*/
public final class TestTFunc extends TestCase {
/**
* @return the result of calling function T() with the specified argument
*/
private static Eval invokeT(Eval arg) {
Eval[] args = { arg, };
Eval result = new T().evaluate(args, -1, (short)-1);
assertNotNull("result may never be null", result);
return result;
}
/**
* Simulates call: T(A1)
* where cell A1 has the specified innerValue
*/
private Eval invokeTWithReference(ValueEval innerValue) {
Eval arg = new Ref2DEval(new ReferencePtg((short)1, (short)1, false, false), innerValue, true);
return invokeT(arg);
}
private static void confirmText(String text) {
Eval arg = new StringEval(text);
Eval eval = invokeT(arg);
StringEval se = (StringEval) eval;
assertEquals(text, se.getStringValue());
}
public void testTextValues() {
confirmText("abc");
confirmText("");
confirmText(" ");
confirmText("~");
confirmText("123");
confirmText("TRUE");
}
private static void confirmError(Eval arg) {
Eval eval = invokeT(arg);
assertTrue(arg == eval);
}
public void testErrorValues() {
confirmError(ErrorEval.VALUE_INVALID);
confirmError(ErrorEval.NA);
confirmError(ErrorEval.REF_INVALID);
}
private static void confirmString(Eval eval, String expected) {
assertTrue(eval instanceof StringEval);
assertEquals(expected, ((StringEval)eval).getStringValue());
}
private static void confirmOther(Eval arg) {
Eval eval = invokeT(arg);
confirmString(eval, "");
}
public void testOtherValues() {
confirmOther(new NumberEval(2));
confirmOther(BoolEval.FALSE);
confirmOther(BlankEval.INSTANCE); // can this particular case be verified?
}
public void testRefValues() {
Eval eval;
eval = invokeTWithReference(new StringEval("def"));
confirmString(eval, "def");
eval = invokeTWithReference(new StringEval(" "));
confirmString(eval, " ");
eval = invokeTWithReference(new NumberEval(2));
confirmString(eval, "");
eval = invokeTWithReference(BoolEval.TRUE);
confirmString(eval, "");
eval = invokeTWithReference(ErrorEval.NAME_INVALID);
assertTrue(eval == ErrorEval.NAME_INVALID);
}
}

View File

@ -18,6 +18,8 @@
package org.apache.poi.hssf.model;
import java.util.List;
import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
@ -26,6 +28,7 @@ import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.DividePtg;
import org.apache.poi.hssf.record.formula.EqualPtg;
import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.LessEqualPtg;
@ -397,7 +400,7 @@ public class TestFormulaParser extends TestCase {
public void testUnderscore() {
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Cash_Flow");;
wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@ -438,7 +441,7 @@ public class TestFormulaParser extends TestCase {
public void testExponentialInSheet() throws Exception {
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Cash_Flow");;
wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@ -514,7 +517,7 @@ public class TestFormulaParser extends TestCase {
public void testNumbers() {
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Cash_Flow");;
wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@ -553,7 +556,7 @@ public class TestFormulaParser extends TestCase {
public void testRanges() {
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Cash_Flow");;
wb.createSheet("Cash_Flow");
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
@ -572,4 +575,18 @@ public class TestFormulaParser extends TestCase {
formula = cell.getCellFormula();
assertEquals("A1:A2", formula);
}
/**
* Test for bug observable at svn revision 618865 (5-Feb-2008)<br/>
* a formula consisting of a single no-arg function got rendered without the function braces
*/
public void testToFormulaStringZeroArgFunction() {
Workbook book = Workbook.createWorkbook(); // not really used in this test
Ptg[] ptgs = {
new FuncPtg(10, 0),
};
assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs));
}
}