From: Josh Micich Date: Fri, 11 Jul 2008 07:59:44 +0000 (+0000) Subject: Patch 45289 - finished support for special comparison operators in COUNTIF X-Git-Tag: REL_3_2_FINAL~247 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=09fc45feb9e3d7816dd19dd6eda0dfda92b61a10;p=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 --- diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index e484ebbf1a..a9cee30b25 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45289 - finished support for special comparison operators in COUNTIF 45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes Fix cell.getRichStringCellValue() for formula cells with string results 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 767b33521b..6d5d51ea81 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45289 - finished support for special comparison operators in COUNTIF 45126 - Avoid generating multiple NamedRanges with the same name, which Excel dislikes Fix cell.getRichStringCellValue() for formula cells with string results 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java b/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java index 2e445a8bf6..902a991b37 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Countif.java @@ -15,14 +15,17 @@ * limitations under the License. */ - 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.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.OperandResolver; import org.apache.poi.hssf.record.formula.eval.RefEval; import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.ValueEval; @@ -40,85 +43,288 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval; * @author Josh Micich */ 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. */ - private interface I_MatchPredicate { + /* package */ interface I_MatchPredicate { boolean matches(Eval x); } - + private static final class NumberMatcher implements I_MatchPredicate { private final double _value; + private final CmpOp _operator; - public NumberMatcher(double value) { + public NumberMatcher(double value, CmpOp operator) { _value = value; + _operator = operator; } public boolean matches(Eval x) { + double testValue; if(x instanceof StringEval) { // if the target(x) is a string, but parses as a number // it may still count as a match StringEval se = (StringEval)x; - Double val = parseDouble(se.getStringValue()); + Double val = OperandResolver.parseDouble(se.getStringValue()); if(val == null) { // x is text that is not a number return false; } - return val.doubleValue() == _value; - } - if(!(x instanceof NumberEval)) { + testValue = val.doubleValue(); + } else if((x instanceof NumberEval)) { + NumberEval ne = (NumberEval) x; + testValue = ne.getNumberValue(); + } else { return false; } - NumberEval ne = (NumberEval) x; - return ne.getNumberValue() == _value; + return _operator.evaluate(Double.compare(testValue, _value)); } } private static final class BooleanMatcher implements I_MatchPredicate { - private final boolean _value; + private final int _value; + private final CmpOp _operator; - public BooleanMatcher(boolean value) { - _value = value; + public BooleanMatcher(boolean value, CmpOp operator) { + _value = boolToInt(value); + _operator = operator; + } + + private static int boolToInt(boolean value) { + return value ? 1 : 0; } public boolean matches(Eval x) { + int testValue; if(x instanceof StringEval) { + if (true) { // change to false to observe more intuitive behaviour + // Note - Unlike with numbers, it seems that COUNTIF never matches + // boolean values when the target(x) is a string + return false; + } StringEval se = (StringEval)x; Boolean val = parseBoolean(se.getStringValue()); if(val == null) { // x is text that is not a boolean return false; } - if (true) { // change to false to observe more intuitive behaviour - // Note - Unlike with numbers, it seems that COUNTA never matches - // boolean values when the target(x) is a string - return false; - } - return val.booleanValue() == _value; - } - if(!(x instanceof BoolEval)) { + testValue = boolToInt(val.booleanValue()); + } else if((x instanceof BoolEval)) { + BoolEval be = (BoolEval) x; + testValue = boolToInt(be.getBooleanValue()); + } else { return false; } - BoolEval be = (BoolEval) x; - return be.getBooleanValue() == _value; + return _operator.evaluate(testValue - _value); } } private static final class StringMatcher implements I_MatchPredicate { private final String _value; + private final CmpOp _operator; + private final Pattern _pattern; - public StringMatcher(String value) { + public StringMatcher(String value, CmpOp operator) { _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) { + 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; + } if(!(x instanceof StringEval)) { + // must always be string + // even if match str is wild, but contains only digits + // e.g. '4*7', NumberEval(4567) does not match return false; } - StringEval se = (StringEval) x; - return se.getStringValue() == _value; + 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 null if the specified value contains no special wildcard characters. + */ + private static Pattern getWildCardPattern(String value) { + int len = value.length(); + StringBuffer sb = new StringBuffer(len); + boolean hasWildCard = false; + for(int i=0; i': - case '<': - case '=': - throw new RuntimeException("Incomplete code - criteria expressions such as '" - + value + "' not supported yet"); + return new NumberMatcher(doubleVal.doubleValue(), operator); } - - //else - just a plain string with no interpretation. - return new StringMatcher(value); - } - /** - * 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); + //else - just a plain string with no interpretation. + return new StringMatcher(value, operator); } /** * Boolean literals ('TRUE', 'FALSE') treated similarly but NOT same as numbers. */ /* package */ static Boolean parseBoolean(String strRep) { + if (strRep.length() < 1) { + return null; + } switch(strRep.charAt(0)) { case 't': case 'T': diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java b/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java index 9497a5f21a..f61434d740 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Offset.java @@ -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.ErrorEval; 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.OperandResolver; 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.StringEval; @@ -55,21 +57,6 @@ public final class Offset implements FreeRefFunction { 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. * Two instances of this class together specify an area range. @@ -133,8 +120,7 @@ public final class Offset implements FreeRefFunction { return sb.toString(); } } - - + /** * 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() { return _width; } - public int getHeight() { return _height; } - public int getFirstRowIndex() { return _firstRowIndex; } - public int getFirstColumnIndex() { return _firstColumnIndex; } - public boolean isIs3d() { return _externalSheetIndex > 0; } @@ -198,7 +180,6 @@ public final class Offset implements FreeRefFunction { } return (short) _externalSheetIndex; } - } 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; } - try { BaseRef baseRef = evaluateBaseRef(args[0]); int rowOffset = evaluateIntArg(args[1], srcCellRow, srcCellCol); @@ -227,24 +207,23 @@ public final class Offset implements FreeRefFunction { LinearOffsetRange rowOffsetRange = new LinearOffsetRange(rowOffset, height); LinearOffsetRange colOffsetRange = new LinearOffsetRange(columnOffset, width); return createOffset(baseRef, rowOffsetRange, colOffsetRange, workbook, sheet); - } catch (EvalEx e) { - return e.getError(); + } catch (EvaluationException e) { + return e.getErrorEval(); } } - private static AreaEval createOffset(BaseRef baseRef, LinearOffsetRange rowOffsetRange, LinearOffsetRange colOffsetRange, - HSSFWorkbook workbook, HSSFSheet sheet) throws EvalEx { + HSSFWorkbook workbook, HSSFSheet sheet) throws EvaluationException { LinearOffsetRange rows = rowOffsetRange.normaliseAndTranslate(baseRef.getFirstRowIndex()); LinearOffsetRange cols = colOffsetRange.normaliseAndTranslate(baseRef.getFirstColumnIndex()); 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)) { - throw new EvalEx(ErrorEval.REF_INVALID); + throw new EvaluationException(ErrorEval.REF_INVALID); } if(baseRef.isIs3d()) { Area3DPtg a3dp = new Area3DPtg(rows.getFirstIndex(), rows.getLastIndex(), @@ -260,8 +239,7 @@ public final class Offset implements FreeRefFunction { return HSSFFormulaEvaluator.evaluateAreaPtg(sheet, workbook, ap); } - - private static BaseRef evaluateBaseRef(Eval eval) throws EvalEx { + private static BaseRef evaluateBaseRef(Eval eval) throws EvaluationException { if(eval instanceof RefEval) { return new BaseRef((RefEval)eval); @@ -270,16 +248,15 @@ public final class Offset implements FreeRefFunction { return new BaseRef((AreaEval)eval); } 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 */ - 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); return convertDoubleToInt(d); @@ -295,18 +272,17 @@ public final class Offset implements FreeRefFunction { return (int)Math.floor(d); } - - private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvalEx { - ValueEval ve = evaluateSingleValue(eval, srcCellRow, srcCellCol); + private static double evaluateDoubleArg(Eval eval, int srcCellRow, short srcCellCol) throws EvaluationException { + ValueEval ve = OperandResolver.getSingleValue(eval, srcCellRow, srcCellCol); if (ve instanceof NumericValueEval) { return ((NumericValueEval) ve).getNumberValue(); } if (ve instanceof StringEval) { StringEval se = (StringEval) ve; - Double d = parseDouble(se.getStringValue()); + Double d = OperandResolver.parseDouble(se.getStringValue()); if(d == null) { - throw new EvalEx(ErrorEval.VALUE_INVALID); + throw new EvaluationException(ErrorEval.VALUE_INVALID); } return d.doubleValue(); } @@ -319,44 +295,4 @@ public final class Offset implements FreeRefFunction { } 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); - } } diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls index 7be92c5fa4..eba6607ade 100644 Binary files a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls and b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls differ diff --git a/src/testcases/org/apache/poi/hssf/data/countifExamples.xls b/src/testcases/org/apache/poi/hssf/data/countifExamples.xls new file mode 100644 index 0000000000..b15bd162a2 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/countifExamples.xls differ diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java index 1ec657dfe2..4d6468a819 100755 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java @@ -18,8 +18,10 @@ package org.apache.poi.hssf.record.formula.functions; +import junit.framework.AssertionFailedError; 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.RefPtg; 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.StringEval; 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() @@ -146,4 +155,154 @@ public final class TestCountFuncs extends TestCase { double result = NumericFunctionInvoker.invoke(new Countif(), args); 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 0) { + throw new AssertionFailedError(failureCount + " countif evaluations failed. See stderr for more details"); + } + } }