mirror of https://github.com/apache/poi.git
Patch 45289 - finished support for special comparison operators in COUNTIF
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@675853 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
6e7addf38b
commit
09fc45feb9
|
@ -37,6 +37,7 @@
|
||||||
|
|
||||||
<!-- Don't forget to update status.xml too! -->
|
<!-- Don't forget to update status.xml too! -->
|
||||||
<release version="3.1.1-alpha1" date="2008-??-??">
|
<release version="3.1.1-alpha1" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">45289 - finished support for special comparison operators in COUNTIF</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action>
|
<action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action>
|
<action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>
|
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
<!-- Don't forget to update changes.xml too! -->
|
<!-- Don't forget to update changes.xml too! -->
|
||||||
<changes>
|
<changes>
|
||||||
<release version="3.1.1-alpha1" date="2008-??-??">
|
<release version="3.1.1-alpha1" date="2008-??-??">
|
||||||
|
<action dev="POI-DEVELOPERS" type="fix">45289 - finished support for special comparison operators in COUNTIF</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action>
|
<action dev="POI-DEVELOPERS" type="fix">45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action>
|
<action dev="POI-DEVELOPERS" type="fix">Fix cell.getRichStringCellValue() for formula cells with string results</action>
|
||||||
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>
|
<action dev="POI-DEVELOPERS" type="fix">45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra</action>
|
||||||
|
|
|
@ -15,14 +15,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package org.apache.poi.hssf.record.formula.functions;
|
package org.apache.poi.hssf.record.formula.functions;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.poi.hssf.record.formula.eval.AreaEval;
|
import org.apache.poi.hssf.record.formula.eval.AreaEval;
|
||||||
|
import org.apache.poi.hssf.record.formula.eval.BlankEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.BoolEval;
|
import org.apache.poi.hssf.record.formula.eval.BoolEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
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.Eval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
import org.apache.poi.hssf.record.formula.eval.NumberEval;
|
||||||
|
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
|
||||||
import org.apache.poi.hssf.record.formula.eval.RefEval;
|
import org.apache.poi.hssf.record.formula.eval.RefEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||||
|
@ -41,84 +44,287 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||||
*/
|
*/
|
||||||
public final class Countif implements Function {
|
public final class Countif implements Function {
|
||||||
|
|
||||||
|
private static final class CmpOp {
|
||||||
|
public static final int NONE = 0;
|
||||||
|
public static final int EQ = 1;
|
||||||
|
public static final int NE = 2;
|
||||||
|
public static final int LE = 3;
|
||||||
|
public static final int LT = 4;
|
||||||
|
public static final int GT = 5;
|
||||||
|
public static final int GE = 6;
|
||||||
|
|
||||||
|
public static final CmpOp OP_NONE = op("", NONE);
|
||||||
|
public static final CmpOp OP_EQ = op("=", EQ);
|
||||||
|
public static final CmpOp OP_NE = op("<>", NE);
|
||||||
|
public static final CmpOp OP_LE = op("<=", LE);
|
||||||
|
public static final CmpOp OP_LT = op("<", LT);
|
||||||
|
public static final CmpOp OP_GT = op(">", GT);
|
||||||
|
public static final CmpOp OP_GE = op(">=", GE);
|
||||||
|
private final String _representation;
|
||||||
|
private final int _code;
|
||||||
|
|
||||||
|
private static CmpOp op(String rep, int code) {
|
||||||
|
return new CmpOp(rep, code);
|
||||||
|
}
|
||||||
|
private CmpOp(String representation, int code) {
|
||||||
|
_representation = representation;
|
||||||
|
_code = code;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return number of characters used to represent this operator
|
||||||
|
*/
|
||||||
|
public int getLength() {
|
||||||
|
return _representation.length();
|
||||||
|
}
|
||||||
|
public int getCode() {
|
||||||
|
return _code;
|
||||||
|
}
|
||||||
|
public static CmpOp getOperator(String value) {
|
||||||
|
int len = value.length();
|
||||||
|
if (len < 1) {
|
||||||
|
return OP_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
char firstChar = value.charAt(0);
|
||||||
|
|
||||||
|
switch(firstChar) {
|
||||||
|
case '=':
|
||||||
|
return OP_EQ;
|
||||||
|
case '>':
|
||||||
|
if (len > 1) {
|
||||||
|
switch(value.charAt(1)) {
|
||||||
|
case '=':
|
||||||
|
return OP_GE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OP_GT;
|
||||||
|
case '<':
|
||||||
|
if (len > 1) {
|
||||||
|
switch(value.charAt(1)) {
|
||||||
|
case '=':
|
||||||
|
return OP_LE;
|
||||||
|
case '>':
|
||||||
|
return OP_NE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OP_LT;
|
||||||
|
}
|
||||||
|
return OP_NONE;
|
||||||
|
}
|
||||||
|
public boolean evaluate(boolean cmpResult) {
|
||||||
|
switch (_code) {
|
||||||
|
case NONE:
|
||||||
|
case EQ:
|
||||||
|
return cmpResult;
|
||||||
|
case NE:
|
||||||
|
return !cmpResult;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
|
||||||
|
+ _representation + "'");
|
||||||
|
}
|
||||||
|
public boolean evaluate(int cmpResult) {
|
||||||
|
switch (_code) {
|
||||||
|
case NONE:
|
||||||
|
case EQ:
|
||||||
|
return cmpResult == 0;
|
||||||
|
case NE: return cmpResult == 0;
|
||||||
|
case LT: return cmpResult < 0;
|
||||||
|
case LE: return cmpResult <= 0;
|
||||||
|
case GT: return cmpResult > 0;
|
||||||
|
case GE: return cmpResult <= 0;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Cannot call boolean evaluate on non-equality operator '"
|
||||||
|
+ _representation + "'");
|
||||||
|
}
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName());
|
||||||
|
sb.append(" [").append(_representation).append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common interface for the matching criteria.
|
* Common interface for the matching criteria.
|
||||||
*/
|
*/
|
||||||
private interface I_MatchPredicate {
|
/* package */ interface I_MatchPredicate {
|
||||||
boolean matches(Eval x);
|
boolean matches(Eval x);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class NumberMatcher implements I_MatchPredicate {
|
private static final class NumberMatcher implements I_MatchPredicate {
|
||||||
|
|
||||||
private final double _value;
|
private final double _value;
|
||||||
|
private final CmpOp _operator;
|
||||||
|
|
||||||
public NumberMatcher(double value) {
|
public NumberMatcher(double value, CmpOp operator) {
|
||||||
_value = value;
|
_value = value;
|
||||||
|
_operator = operator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(Eval x) {
|
public boolean matches(Eval x) {
|
||||||
|
double testValue;
|
||||||
if(x instanceof StringEval) {
|
if(x instanceof StringEval) {
|
||||||
// if the target(x) is a string, but parses as a number
|
// if the target(x) is a string, but parses as a number
|
||||||
// it may still count as a match
|
// it may still count as a match
|
||||||
StringEval se = (StringEval)x;
|
StringEval se = (StringEval)x;
|
||||||
Double val = parseDouble(se.getStringValue());
|
Double val = OperandResolver.parseDouble(se.getStringValue());
|
||||||
if(val == null) {
|
if(val == null) {
|
||||||
// x is text that is not a number
|
// x is text that is not a number
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return val.doubleValue() == _value;
|
testValue = val.doubleValue();
|
||||||
}
|
} else if((x instanceof NumberEval)) {
|
||||||
if(!(x instanceof NumberEval)) {
|
NumberEval ne = (NumberEval) x;
|
||||||
|
testValue = ne.getNumberValue();
|
||||||
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
NumberEval ne = (NumberEval) x;
|
return _operator.evaluate(Double.compare(testValue, _value));
|
||||||
return ne.getNumberValue() == _value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static final class BooleanMatcher implements I_MatchPredicate {
|
private static final class BooleanMatcher implements I_MatchPredicate {
|
||||||
|
|
||||||
private final boolean _value;
|
private final int _value;
|
||||||
|
private final CmpOp _operator;
|
||||||
|
|
||||||
public BooleanMatcher(boolean value) {
|
public BooleanMatcher(boolean value, CmpOp operator) {
|
||||||
_value = value;
|
_value = boolToInt(value);
|
||||||
|
_operator = operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int boolToInt(boolean value) {
|
||||||
|
return value ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(Eval x) {
|
public boolean matches(Eval x) {
|
||||||
|
int testValue;
|
||||||
if(x instanceof StringEval) {
|
if(x instanceof StringEval) {
|
||||||
|
if (true) { // change to false to observe more intuitive behaviour
|
||||||
|
// Note - Unlike with numbers, it seems that COUNTIF never matches
|
||||||
|
// boolean values when the target(x) is a string
|
||||||
|
return false;
|
||||||
|
}
|
||||||
StringEval se = (StringEval)x;
|
StringEval se = (StringEval)x;
|
||||||
Boolean val = parseBoolean(se.getStringValue());
|
Boolean val = parseBoolean(se.getStringValue());
|
||||||
if(val == null) {
|
if(val == null) {
|
||||||
// x is text that is not a boolean
|
// x is text that is not a boolean
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (true) { // change to false to observe more intuitive behaviour
|
testValue = boolToInt(val.booleanValue());
|
||||||
// Note - Unlike with numbers, it seems that COUNTA never matches
|
} else if((x instanceof BoolEval)) {
|
||||||
// boolean values when the target(x) is a string
|
BoolEval be = (BoolEval) x;
|
||||||
return false;
|
testValue = boolToInt(be.getBooleanValue());
|
||||||
}
|
} else {
|
||||||
return val.booleanValue() == _value;
|
|
||||||
}
|
|
||||||
if(!(x instanceof BoolEval)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BoolEval be = (BoolEval) x;
|
return _operator.evaluate(testValue - _value);
|
||||||
return be.getBooleanValue() == _value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static final class StringMatcher implements I_MatchPredicate {
|
private static final class StringMatcher implements I_MatchPredicate {
|
||||||
|
|
||||||
private final String _value;
|
private final String _value;
|
||||||
|
private final CmpOp _operator;
|
||||||
|
private final Pattern _pattern;
|
||||||
|
|
||||||
public StringMatcher(String value) {
|
public StringMatcher(String value, CmpOp operator) {
|
||||||
_value = value;
|
_value = value;
|
||||||
|
_operator = operator;
|
||||||
|
switch(operator.getCode()) {
|
||||||
|
case CmpOp.NONE:
|
||||||
|
case CmpOp.EQ:
|
||||||
|
case CmpOp.NE:
|
||||||
|
_pattern = getWildCardPattern(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_pattern = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(Eval x) {
|
public boolean matches(Eval x) {
|
||||||
if(!(x instanceof StringEval)) {
|
if (x instanceof BlankEval) {
|
||||||
|
switch(_operator.getCode()) {
|
||||||
|
case CmpOp.NONE:
|
||||||
|
case CmpOp.EQ:
|
||||||
|
return _value.length() == 0;
|
||||||
|
}
|
||||||
|
// no other criteria matches a blank cell
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
StringEval se = (StringEval) x;
|
if(!(x instanceof StringEval)) {
|
||||||
return se.getStringValue() == _value;
|
// must always be string
|
||||||
|
// even if match str is wild, but contains only digits
|
||||||
|
// e.g. '4*7', NumberEval(4567) does not match
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String testedValue = ((StringEval) x).getStringValue();
|
||||||
|
if (testedValue.length() < 1 && _value.length() < 1) {
|
||||||
|
// odd case: criteria '=' behaves differently to criteria ''
|
||||||
|
|
||||||
|
switch(_operator.getCode()) {
|
||||||
|
case CmpOp.NONE: return true;
|
||||||
|
case CmpOp.EQ: return false;
|
||||||
|
case CmpOp.NE: return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_pattern != null) {
|
||||||
|
return _operator.evaluate(_pattern.matcher(testedValue).matches());
|
||||||
|
}
|
||||||
|
return _operator.evaluate(testedValue.compareTo(_value));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Translates Excel countif wildcard strings into java regex strings
|
||||||
|
* @return <code>null</code> if the specified value contains no special wildcard characters.
|
||||||
|
*/
|
||||||
|
private static Pattern getWildCardPattern(String value) {
|
||||||
|
int len = value.length();
|
||||||
|
StringBuffer sb = new StringBuffer(len);
|
||||||
|
boolean hasWildCard = false;
|
||||||
|
for(int i=0; i<len; i++) {
|
||||||
|
char ch = value.charAt(i);
|
||||||
|
switch(ch) {
|
||||||
|
case '?':
|
||||||
|
hasWildCard = true;
|
||||||
|
// match exactly one character
|
||||||
|
sb.append('.');
|
||||||
|
continue;
|
||||||
|
case '*':
|
||||||
|
hasWildCard = true;
|
||||||
|
// match one or more occurrences of any character
|
||||||
|
sb.append(".*");
|
||||||
|
continue;
|
||||||
|
case '~':
|
||||||
|
if (i+1<len) {
|
||||||
|
ch = value.charAt(i+1);
|
||||||
|
switch (ch) {
|
||||||
|
case '?':
|
||||||
|
case '*':
|
||||||
|
hasWildCard = true;
|
||||||
|
sb.append('[').append(ch).append(']');
|
||||||
|
i++; // Note - incrementing loop variable here
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else not '~?' or '~*'
|
||||||
|
sb.append('~'); // just plain '~'
|
||||||
|
continue;
|
||||||
|
case '.':
|
||||||
|
case '$':
|
||||||
|
case '^':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
// escape literal characters that would have special meaning in regex
|
||||||
|
sb.append("\\").append(ch);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sb.append(ch);
|
||||||
|
}
|
||||||
|
if (hasWildCard) {
|
||||||
|
return Pattern.compile(sb.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +339,6 @@ public final class Countif implements Function {
|
||||||
return ErrorEval.VALUE_INVALID;
|
return ErrorEval.VALUE_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
AreaEval range = (AreaEval) args[0];
|
|
||||||
Eval criteriaArg = args[1];
|
Eval criteriaArg = args[1];
|
||||||
if(criteriaArg instanceof RefEval) {
|
if(criteriaArg instanceof RefEval) {
|
||||||
// criteria is not a literal value, but a cell reference
|
// criteria is not a literal value, but a cell reference
|
||||||
|
@ -144,29 +349,45 @@ public final class Countif implements Function {
|
||||||
// other non literal tokens such as function calls, have been fully evaluated
|
// other non literal tokens such as function calls, have been fully evaluated
|
||||||
// for example COUNTIF(B2:D4, COLUMN(E1))
|
// for example COUNTIF(B2:D4, COLUMN(E1))
|
||||||
}
|
}
|
||||||
|
if(criteriaArg instanceof BlankEval) {
|
||||||
|
// If the criteria arg is a reference to a blank cell, countif always returns zero.
|
||||||
|
return NumberEval.ZERO;
|
||||||
|
}
|
||||||
I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
|
I_MatchPredicate mp = createCriteriaPredicate(criteriaArg);
|
||||||
return countMatchingCellsInArea(range, mp);
|
return countMatchingCellsInArea(args[0], mp);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @return the number of evaluated cells in the range that match the specified criteria
|
* @return the number of evaluated cells in the range that match the specified criteria
|
||||||
*/
|
*/
|
||||||
private Eval countMatchingCellsInArea(AreaEval range, I_MatchPredicate criteriaPredicate) {
|
private Eval countMatchingCellsInArea(Eval rangeArg, I_MatchPredicate criteriaPredicate) {
|
||||||
ValueEval[] values = range.getValues();
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
for (int i = 0; i < values.length; i++) {
|
if (rangeArg instanceof RefEval) {
|
||||||
if(criteriaPredicate.matches(values[i])) {
|
RefEval refEval = (RefEval) rangeArg;
|
||||||
|
if(criteriaPredicate.matches(refEval.getInnerValueEval())) {
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
|
} else if (rangeArg instanceof AreaEval) {
|
||||||
|
|
||||||
|
AreaEval range = (AreaEval) rangeArg;
|
||||||
|
ValueEval[] values = range.getValues();
|
||||||
|
for (int i = 0; i < values.length; i++) {
|
||||||
|
if(criteriaPredicate.matches(values[i])) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bad range arg type (" + rangeArg.getClass().getName() + ")");
|
||||||
}
|
}
|
||||||
return new NumberEval(result);
|
return new NumberEval(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
|
/* package */ static I_MatchPredicate createCriteriaPredicate(Eval evaluatedCriteriaArg) {
|
||||||
|
|
||||||
if(evaluatedCriteriaArg instanceof NumberEval) {
|
if(evaluatedCriteriaArg instanceof NumberEval) {
|
||||||
return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue());
|
return new NumberMatcher(((NumberEval)evaluatedCriteriaArg).getNumberValue(), CmpOp.OP_NONE);
|
||||||
}
|
}
|
||||||
if(evaluatedCriteriaArg instanceof BoolEval) {
|
if(evaluatedCriteriaArg instanceof BoolEval) {
|
||||||
return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue());
|
return new BooleanMatcher(((BoolEval)evaluatedCriteriaArg).getBooleanValue(), CmpOp.OP_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(evaluatedCriteriaArg instanceof StringEval) {
|
if(evaluatedCriteriaArg instanceof StringEval) {
|
||||||
|
@ -181,50 +402,29 @@ public final class Countif implements Function {
|
||||||
*/
|
*/
|
||||||
private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
|
private static I_MatchPredicate createGeneralMatchPredicate(StringEval stringEval) {
|
||||||
String value = stringEval.getStringValue();
|
String value = stringEval.getStringValue();
|
||||||
char firstChar = value.charAt(0);
|
CmpOp operator = CmpOp.getOperator(value);
|
||||||
|
value = value.substring(operator.getLength());
|
||||||
|
|
||||||
Boolean booleanVal = parseBoolean(value);
|
Boolean booleanVal = parseBoolean(value);
|
||||||
if(booleanVal != null) {
|
if(booleanVal != null) {
|
||||||
return new BooleanMatcher(booleanVal.booleanValue());
|
return new BooleanMatcher(booleanVal.booleanValue(), operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
Double doubleVal = parseDouble(value);
|
Double doubleVal = OperandResolver.parseDouble(value);
|
||||||
if(doubleVal != null) {
|
if(doubleVal != null) {
|
||||||
return new NumberMatcher(doubleVal.doubleValue());
|
return new NumberMatcher(doubleVal.doubleValue(), operator);
|
||||||
}
|
|
||||||
switch(firstChar) {
|
|
||||||
case '>':
|
|
||||||
case '<':
|
|
||||||
case '=':
|
|
||||||
throw new RuntimeException("Incomplete code - criteria expressions such as '"
|
|
||||||
+ value + "' not supported yet");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//else - just a plain string with no interpretation.
|
//else - just a plain string with no interpretation.
|
||||||
return new StringMatcher(value);
|
return new StringMatcher(value, operator);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Under certain circumstances COUNTA will equate a plain number with a string representation of that number
|
|
||||||
*/
|
|
||||||
/* package */ static Double parseDouble(String strRep) {
|
|
||||||
if(!Character.isDigit(strRep.charAt(0))) {
|
|
||||||
// avoid using NumberFormatException to tell when string is not a number
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// TODO - support notation like '1E3' (==1000)
|
|
||||||
|
|
||||||
double val;
|
|
||||||
try {
|
|
||||||
val = Double.parseDouble(strRep);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new Double(val);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
|
* Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers.
|
||||||
*/
|
*/
|
||||||
/* package */ static Boolean parseBoolean(String strRep) {
|
/* package */ static Boolean parseBoolean(String strRep) {
|
||||||
|
if (strRep.length() < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
switch(strRep.charAt(0)) {
|
switch(strRep.charAt(0)) {
|
||||||
case 't':
|
case 't':
|
||||||
case 'T':
|
case 'T':
|
||||||
|
|
|
@ -25,7 +25,9 @@ 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.BoolEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
|
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.Eval;
|
||||||
|
import org.apache.poi.hssf.record.formula.eval.EvaluationException;
|
||||||
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
|
import org.apache.poi.hssf.record.formula.eval.NumericValueEval;
|
||||||
|
import org.apache.poi.hssf.record.formula.eval.OperandResolver;
|
||||||
import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
|
import org.apache.poi.hssf.record.formula.eval.Ref3DEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.RefEval;
|
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.StringEval;
|
||||||
|
@ -55,21 +57,6 @@ public final class Offset implements FreeRefFunction {
|
||||||
private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
|
private static final int LAST_VALID_COLUMN_INDEX = 0xFF;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exceptions are used within this class to help simplify flow control when error conditions
|
|
||||||
* are encountered
|
|
||||||
*/
|
|
||||||
private static final class EvalEx extends Exception {
|
|
||||||
private final ErrorEval _error;
|
|
||||||
|
|
||||||
public EvalEx(ErrorEval error) {
|
|
||||||
_error = error;
|
|
||||||
}
|
|
||||||
public ErrorEval getError() {
|
|
||||||
return _error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A one dimensional base + offset. Represents either a row range or a column range.
|
* A one dimensional base + offset. Represents either a row range or a column range.
|
||||||
* Two instances of this class together specify an area range.
|
* Two instances of this class together specify an area range.
|
||||||
|
@ -134,7 +121,6 @@ public final class Offset implements FreeRefFunction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates either an area or cell reference which may be 2d or 3d.
|
* Encapsulates either an area or cell reference which may be 2d or 3d.
|
||||||
*/
|
*/
|
||||||
|
@ -175,19 +161,15 @@ public final class Offset implements FreeRefFunction {
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return _width;
|
return _width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return _height;
|
return _height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFirstRowIndex() {
|
public int getFirstRowIndex() {
|
||||||
return _firstRowIndex;
|
return _firstRowIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFirstColumnIndex() {
|
public int getFirstColumnIndex() {
|
||||||
return _firstColumnIndex;
|
return _firstColumnIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIs3d() {
|
public boolean isIs3d() {
|
||||||
return _externalSheetIndex > 0;
|
return _externalSheetIndex > 0;
|
||||||
}
|
}
|
||||||
|
@ -198,7 +180,6 @@ public final class Offset implements FreeRefFunction {
|
||||||
}
|
}
|
||||||
return (short) _externalSheetIndex;
|
return (short) _externalSheetIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
|
public ValueEval evaluate(Eval[] args, int srcCellRow, short srcCellCol, HSSFWorkbook workbook, HSSFSheet sheet) {
|
||||||
|
@ -207,7 +188,6 @@ public final class Offset implements FreeRefFunction {
|
||||||
return ErrorEval.VALUE_INVALID;
|
return ErrorEval.VALUE_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BaseRef baseRef = evaluateBaseRef(args[0]);
|
BaseRef baseRef = evaluateBaseRef(args[0]);
|
||||||
int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
|
int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol);
|
||||||
|
@ -227,24 +207,23 @@ public final class Offset implements FreeRefFunction {
|
||||||
LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
|
LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height);
|
||||||
LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
|
LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width);
|
||||||
return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
|
return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet);
|
||||||
} catch (EvalEx e) {
|
} catch (EvaluationException e) {
|
||||||
return e.getError();
|
return e.getErrorEval();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static AreaEval createOffset(BaseRef baseRef,
|
private static AreaEval createOffset(BaseRef baseRef,
|
||||||
LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
|
LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange,
|
||||||
HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx {
|
HSSFWorkbook workbook, HSSFSheet sheet) throws EvaluationException {
|
||||||
|
|
||||||
LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
|
LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex());
|
||||||
LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
|
LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex());
|
||||||
|
|
||||||
if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
|
if(rows.isOutOfBounds(0, LAST_VALID_ROW_INDEX)) {
|
||||||
throw new EvalEx(ErrorEval.REF_INVALID);
|
throw new EvaluationException(ErrorEval.REF_INVALID);
|
||||||
}
|
}
|
||||||
if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
|
if(cols.isOutOfBounds(0, LAST_VALID_COLUMN_INDEX)) {
|
||||||
throw new EvalEx(ErrorEval.REF_INVALID);
|
throw new EvaluationException(ErrorEval.REF_INVALID);
|
||||||
}
|
}
|
||||||
if(baseRef.isIs3d()) {
|
if(baseRef.isIs3d()) {
|
||||||
Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
|
Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(),
|
||||||
|
@ -260,8 +239,7 @@ public final class Offset implements FreeRefFunction {
|
||||||
return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
|
return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BaseRef evaluateBaseRef(Eval eval) throws EvaluationException {
|
||||||
private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx {
|
|
||||||
|
|
||||||
if(eval instanceof RefEval) {
|
if(eval instanceof RefEval) {
|
||||||
return new BaseRef((RefEval)eval);
|
return new BaseRef((RefEval)eval);
|
||||||
|
@ -270,16 +248,15 @@ public final class Offset implements FreeRefFunction {
|
||||||
return new BaseRef((AreaEval)eval);
|
return new BaseRef((AreaEval)eval);
|
||||||
}
|
}
|
||||||
if (eval instanceof ErrorEval) {
|
if (eval instanceof ErrorEval) {
|
||||||
throw new EvalEx((ErrorEval) eval);
|
throw new EvaluationException((ErrorEval) eval);
|
||||||
}
|
}
|
||||||
throw new EvalEx(ErrorEval.VALUE_INVALID);
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OFFSET's numeric arguments (2..5) have similar processing rules
|
* OFFSET's numeric arguments (2..5) have similar processing rules
|
||||||
*/
|
*/
|
||||||
private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
|
private static int evaluateIntArg(Eval eval, int srcCellRow, short srcCellCol) throws EvaluationException {
|
||||||
|
|
||||||
double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
|
double d = evaluateDoubleArg(eval, srcCellRow, srcCellCol);
|
||||||
return convertDoubleToInt(d);
|
return convertDoubleToInt(d);
|
||||||
|
@ -295,18 +272,17 @@ public final class Offset implements FreeRefFunction {
|
||||||
return (int)Math.floor(d);
|
return (int)Math.floor(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvaluationException {
|
||||||
private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
|
ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol);
|
||||||
ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol);
|
|
||||||
|
|
||||||
if (ve instanceof NumericValueEval) {
|
if (ve instanceof NumericValueEval) {
|
||||||
return ((NumericValueEval) ve).getNumberValue();
|
return ((NumericValueEval) ve).getNumberValue();
|
||||||
}
|
}
|
||||||
if (ve instanceof StringEval) {
|
if (ve instanceof StringEval) {
|
||||||
StringEval se = (StringEval) ve;
|
StringEval se = (StringEval) ve;
|
||||||
Double d = parseDouble(se.getStringValue());
|
Double d = OperandResolver.parseDouble(se.getStringValue());
|
||||||
if(d == null) {
|
if(d == null) {
|
||||||
throw new EvalEx(ErrorEval.VALUE_INVALID);
|
throw new EvaluationException(ErrorEval.VALUE_INVALID);
|
||||||
}
|
}
|
||||||
return d.doubleValue();
|
return d.doubleValue();
|
||||||
}
|
}
|
||||||
|
@ -319,44 +295,4 @@ public final class Offset implements FreeRefFunction {
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
|
throw new RuntimeException("Unexpected eval type (" + ve.getClass().getName() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Double parseDouble(String s) {
|
|
||||||
// TODO - find a home for this method
|
|
||||||
// TODO - support various number formats: sign char, dollars, commas
|
|
||||||
// OFFSET and COUNTIF seem to handle these
|
|
||||||
return Countif.parseDouble(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ValueEval evaluateSingleValue(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx {
|
|
||||||
if(eval instanceof RefEval) {
|
|
||||||
return ((RefEval)eval).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() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - this code seems to get repeated a bit
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -18,8 +18,10 @@
|
||||||
|
|
||||||
package org.apache.poi.hssf.record.formula.functions;
|
package org.apache.poi.hssf.record.formula.functions;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||||
import org.apache.poi.hssf.record.formula.AreaPtg;
|
import org.apache.poi.hssf.record.formula.AreaPtg;
|
||||||
import org.apache.poi.hssf.record.formula.RefPtg;
|
import org.apache.poi.hssf.record.formula.RefPtg;
|
||||||
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
|
import org.apache.poi.hssf.record.formula.eval.Area2DEval;
|
||||||
|
@ -31,6 +33,13 @@ 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.Ref2DEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
import org.apache.poi.hssf.record.formula.eval.StringEval;
|
||||||
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
import org.apache.poi.hssf.record.formula.eval.ValueEval;
|
||||||
|
import org.apache.poi.hssf.record.formula.functions.Countif.I_MatchPredicate;
|
||||||
|
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.usermodel.HSSFFormulaEvaluator.CellValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
|
* Test cases for COUNT(), COUNTA() COUNTIF(), COUNTBLANK()
|
||||||
|
@ -146,4 +155,154 @@ public final class TestCountFuncs extends TestCase {
|
||||||
double result = NumericFunctionInvoker.invoke(new Countif(), args);
|
double result = NumericFunctionInvoker.invoke(new Countif(), args);
|
||||||
assertEquals(expected, result, 0);
|
assertEquals(expected, result, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCountIfEmptyStringCriteria() {
|
||||||
|
I_MatchPredicate mp;
|
||||||
|
|
||||||
|
// pred '=' matches blank cell but not empty string
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("="));
|
||||||
|
confirmPredicate(false, mp, "");
|
||||||
|
confirmPredicate(true, mp, null);
|
||||||
|
|
||||||
|
// pred '' matches both blank cell but not empty string
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval(""));
|
||||||
|
confirmPredicate(true, mp, "");
|
||||||
|
confirmPredicate(true, mp, null);
|
||||||
|
|
||||||
|
// pred '<>' matches empty string but not blank cell
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("<>"));
|
||||||
|
confirmPredicate(false, mp, null);
|
||||||
|
confirmPredicate(true, mp, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCountifComparisons() {
|
||||||
|
I_MatchPredicate mp;
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval(">5"));
|
||||||
|
confirmPredicate(false, mp, 4);
|
||||||
|
confirmPredicate(false, mp, 5);
|
||||||
|
confirmPredicate(true, mp, 6);
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("<=5"));
|
||||||
|
confirmPredicate(true, mp, 4);
|
||||||
|
confirmPredicate(true, mp, 5);
|
||||||
|
confirmPredicate(false, mp, 6);
|
||||||
|
confirmPredicate(true, mp, "4.9");
|
||||||
|
confirmPredicate(false, mp, "4.9t");
|
||||||
|
confirmPredicate(false, mp, "5.1");
|
||||||
|
confirmPredicate(false, mp, null);
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("=abc"));
|
||||||
|
confirmPredicate(true, mp, "abc");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("=42"));
|
||||||
|
confirmPredicate(false, mp, 41);
|
||||||
|
confirmPredicate(true, mp, 42);
|
||||||
|
confirmPredicate(true, mp, "42");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval(">abc"));
|
||||||
|
confirmPredicate(false, mp, 4);
|
||||||
|
confirmPredicate(false, mp, "abc");
|
||||||
|
confirmPredicate(true, mp, "abd");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval(">4t3"));
|
||||||
|
confirmPredicate(false, mp, 4);
|
||||||
|
confirmPredicate(false, mp, 500);
|
||||||
|
confirmPredicate(true, mp, "500");
|
||||||
|
confirmPredicate(true, mp, "4t4");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWildCards() {
|
||||||
|
I_MatchPredicate mp;
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("a*b"));
|
||||||
|
confirmPredicate(false, mp, "abc");
|
||||||
|
confirmPredicate(true, mp, "ab");
|
||||||
|
confirmPredicate(true, mp, "axxb");
|
||||||
|
confirmPredicate(false, mp, "xab");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("a?b"));
|
||||||
|
confirmPredicate(false, mp, "abc");
|
||||||
|
confirmPredicate(false, mp, "ab");
|
||||||
|
confirmPredicate(false, mp, "axxb");
|
||||||
|
confirmPredicate(false, mp, "xab");
|
||||||
|
confirmPredicate(true, mp, "axb");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("a~?"));
|
||||||
|
confirmPredicate(false, mp, "a~a");
|
||||||
|
confirmPredicate(false, mp, "a~?");
|
||||||
|
confirmPredicate(true, mp, "a?");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("~*a"));
|
||||||
|
confirmPredicate(false, mp, "~aa");
|
||||||
|
confirmPredicate(false, mp, "~*a");
|
||||||
|
confirmPredicate(true, mp, "*a");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("12?12"));
|
||||||
|
confirmPredicate(false, mp, 12812);
|
||||||
|
confirmPredicate(true, mp, "12812");
|
||||||
|
confirmPredicate(false, mp, "128812");
|
||||||
|
}
|
||||||
|
public void testNotQuiteWildCards() {
|
||||||
|
I_MatchPredicate mp;
|
||||||
|
|
||||||
|
// make sure special reg-ex chars are treated like normal chars
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("a.b"));
|
||||||
|
confirmPredicate(false, mp, "aab");
|
||||||
|
confirmPredicate(true, mp, "a.b");
|
||||||
|
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval("a~b"));
|
||||||
|
confirmPredicate(false, mp, "ab");
|
||||||
|
confirmPredicate(false, mp, "axb");
|
||||||
|
confirmPredicate(false, mp, "a~~b");
|
||||||
|
confirmPredicate(true, mp, "a~b");
|
||||||
|
|
||||||
|
mp = Countif.createCriteriaPredicate(new StringEval(">a*b"));
|
||||||
|
confirmPredicate(false, mp, "a(b");
|
||||||
|
confirmPredicate(true, mp, "aab");
|
||||||
|
confirmPredicate(false, mp, "a*a");
|
||||||
|
confirmPredicate(true, mp, "a*c");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void confirmPredicate(boolean expectedResult, I_MatchPredicate matchPredicate, int value) {
|
||||||
|
assertEquals(expectedResult, matchPredicate.matches(new NumberEval(value)));
|
||||||
|
}
|
||||||
|
private static void confirmPredicate(boolean expectedResult, I_MatchPredicate matchPredicate, String value) {
|
||||||
|
Eval ev = value == null ? (Eval)BlankEval.INSTANCE : new StringEval(value);
|
||||||
|
assertEquals(expectedResult, matchPredicate.matches(ev));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCountifFromSpreadsheet() {
|
||||||
|
final String FILE_NAME = "countifExamples.xls";
|
||||||
|
final int START_ROW_IX = 1;
|
||||||
|
final int COL_IX_ACTUAL = 2;
|
||||||
|
final int COL_IX_EXPECTED = 3;
|
||||||
|
|
||||||
|
int failureCount = 0;
|
||||||
|
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(FILE_NAME);
|
||||||
|
HSSFSheet sheet = wb.getSheetAt(0);
|
||||||
|
HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(sheet, wb);
|
||||||
|
int maxRow = sheet.getLastRowNum();
|
||||||
|
for (int rowIx=START_ROW_IX; rowIx<maxRow; rowIx++) {
|
||||||
|
HSSFRow row = sheet.getRow(rowIx);
|
||||||
|
if(row == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
HSSFCell cell = row.getCell(COL_IX_ACTUAL);
|
||||||
|
fe.setCurrentRow(row);
|
||||||
|
CellValue cv = fe.evaluate(cell);
|
||||||
|
double actualValue = cv.getNumberValue();
|
||||||
|
double expectedValue = row.getCell(COL_IX_EXPECTED).getNumericCellValue();
|
||||||
|
if (actualValue != expectedValue) {
|
||||||
|
System.err.println("Problem with test case on row " + (rowIx+1) + " "
|
||||||
|
+ "Expected = (" + expectedValue + ") Actual=(" + actualValue + ") ");
|
||||||
|
failureCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCount > 0) {
|
||||||
|
throw new AssertionFailedError(failureCount + " countif evaluations failed. See stderr for more details");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue